initial commit
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
package usbip
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"escpos-emulator/internal/escpos"
|
||||
)
|
||||
|
||||
func startServer(t *testing.T) (*Server, *escpos.Printer) {
|
||||
t.Helper()
|
||||
prof, _ := escpos.FindProfile("80mm")
|
||||
p, err := escpos.NewPrinter(prof)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s, err := Listen("127.0.0.1:0", DeviceFromProfile(prof), p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { s.Close() })
|
||||
return s, p
|
||||
}
|
||||
|
||||
func dial(t *testing.T, s *Server) net.Conn {
|
||||
t.Helper()
|
||||
c, err := net.Dial("tcp", s.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = c.SetDeadline(time.Now().Add(5 * time.Second))
|
||||
t.Cleanup(func() { c.Close() })
|
||||
return c
|
||||
}
|
||||
|
||||
func readN(t *testing.T, c net.Conn, n int) []byte {
|
||||
t.Helper()
|
||||
b := make([]byte, n)
|
||||
if _, err := io.ReadFull(c, b); err != nil {
|
||||
t.Fatalf("leyendo %d bytes: %v", n, err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func TestDevlist(t *testing.T) {
|
||||
s, _ := startServer(t)
|
||||
c := dial(t, s)
|
||||
c.Write([]byte{0x01, 0x11, 0x80, 0x05, 0, 0, 0, 0})
|
||||
h := readN(t, c, 12)
|
||||
if binary.BigEndian.Uint16(h[2:]) != opRepDevlist || binary.BigEndian.Uint32(h[8:]) != 1 {
|
||||
t.Fatalf("cabecera DEVLIST inesperada: % X", h)
|
||||
}
|
||||
dev := readN(t, c, 312+4)
|
||||
if got := string(bytes.TrimRight(dev[256:288], "\x00")); got != BusID {
|
||||
t.Fatalf("busid = %q", got)
|
||||
}
|
||||
if !bytes.Equal(dev[312:], []byte{7, 1, 2, 0}) {
|
||||
t.Fatalf("interfaz = % X, want clase impresora 07 01 02", dev[312:])
|
||||
}
|
||||
}
|
||||
|
||||
type client struct {
|
||||
t *testing.T
|
||||
c net.Conn
|
||||
seq uint32
|
||||
isIn map[uint32]bool
|
||||
}
|
||||
|
||||
func attach(t *testing.T, s *Server) *client {
|
||||
c := dial(t, s)
|
||||
req := []byte{0x01, 0x11, 0x80, 0x03, 0, 0, 0, 0}
|
||||
bus := make([]byte, 32)
|
||||
copy(bus, BusID)
|
||||
c.Write(append(req, bus...))
|
||||
h := readN(t, c, 8)
|
||||
if binary.BigEndian.Uint32(h[4:]) != 0 {
|
||||
t.Fatalf("IMPORT rechazado: % X", h)
|
||||
}
|
||||
readN(t, c, 312)
|
||||
return &client{t: t, c: c, isIn: map[uint32]bool{}}
|
||||
}
|
||||
|
||||
func (cl *client) submit(dir, ep uint32, length int, setup []byte, data []byte) uint32 {
|
||||
cl.seq++
|
||||
cl.isIn[cl.seq] = dir == dirIn
|
||||
h := make([]byte, 48)
|
||||
binary.BigEndian.PutUint32(h[0:], cmdSubmit)
|
||||
binary.BigEndian.PutUint32(h[4:], cl.seq)
|
||||
binary.BigEndian.PutUint32(h[8:], 0x00010002)
|
||||
binary.BigEndian.PutUint32(h[12:], dir)
|
||||
binary.BigEndian.PutUint32(h[16:], ep)
|
||||
binary.BigEndian.PutUint32(h[24:], uint32(length))
|
||||
binary.BigEndian.PutUint32(h[32:], 0xFFFFFFFF)
|
||||
copy(h[40:], setup)
|
||||
cl.c.Write(append(h, data...))
|
||||
return cl.seq
|
||||
}
|
||||
|
||||
func (cl *client) ret() (seq uint32, status int32, data []byte) {
|
||||
h := readN(cl.t, cl.c, 48)
|
||||
seq = binary.BigEndian.Uint32(h[4:])
|
||||
status = int32(binary.BigEndian.Uint32(h[20:]))
|
||||
if n := int(binary.BigEndian.Uint32(h[24:])); cl.isIn[seq] && n > 0 {
|
||||
data = readN(cl.t, cl.c, n)
|
||||
}
|
||||
return
|
||||
}
|
||||
func TestControlAndBulk(t *testing.T) {
|
||||
s, p := startServer(t)
|
||||
cl := attach(t, s)
|
||||
|
||||
// GET_DESCRIPTOR(DEVICE)
|
||||
cl.submit(dirIn, 0, 18, []byte{0x80, 6, 0, 1, 0, 0, 18, 0}, nil)
|
||||
_, st, d := cl.ret()
|
||||
if st != 0 || len(d) != 18 || d[1] != 1 {
|
||||
t.Fatalf("descriptor de dispositivo: status %d, % X", st, d)
|
||||
}
|
||||
if vid := binary.LittleEndian.Uint16(d[8:]); vid != 0x1209 {
|
||||
t.Fatalf("VID = %04X", vid)
|
||||
}
|
||||
|
||||
// GET_DESCRIPTOR(CONFIGURATION) con wLength 9: solo la cabecera
|
||||
cl.submit(dirIn, 0, 9, []byte{0x80, 6, 0, 2, 0, 0, 9, 0}, nil)
|
||||
_, _, d = cl.ret()
|
||||
if len(d) != 9 || d[2] != 32 {
|
||||
t.Fatalf("configuración: % X", d)
|
||||
}
|
||||
|
||||
// GET_DEVICE_ID de la clase impresora
|
||||
cl.submit(dirIn, 0, 1024, []byte{0xA1, 0, 0, 0, 0, 0, 0, 4}, nil)
|
||||
_, _, d = cl.ret()
|
||||
if !bytes.Contains(d, []byte("CMD:ESC/POS")) {
|
||||
t.Fatalf("GET_DEVICE_ID = %q", d)
|
||||
}
|
||||
|
||||
// DEVICE_QUALIFIER debe dar STALL
|
||||
cl.submit(dirIn, 0, 10, []byte{0x80, 6, 0, 6, 0, 0, 10, 0}, nil)
|
||||
if _, st, _ = cl.ret(); st != errPipe {
|
||||
t.Fatalf("DEVICE_QUALIFIER status = %d, want %d", st, errPipe)
|
||||
}
|
||||
|
||||
// Lectura bulk IN pendiente antes de que haya datos
|
||||
inSeq := cl.submit(dirIn, epBulkIn, 64, nil, nil)
|
||||
|
||||
// Bulk OUT: texto + DLE EOT 1
|
||||
payload := []byte("\x1b@Hola USB\n\x10\x04\x01")
|
||||
outSeq := cl.submit(dirOut, epBulkOut, len(payload), nil, payload)
|
||||
|
||||
got := map[uint32][]byte{}
|
||||
for i := 0; i < 2; i++ {
|
||||
seq, st, data := cl.ret()
|
||||
if st != 0 {
|
||||
t.Fatalf("seq %d status %d", seq, st)
|
||||
}
|
||||
got[seq] = data
|
||||
}
|
||||
if !bytes.Equal(got[inSeq], []byte{0x12}) {
|
||||
t.Fatalf("bulk IN = % X, want 12", got[inSeq])
|
||||
}
|
||||
if _, ok := got[outSeq]; !ok {
|
||||
t.Fatal("falta la respuesta del bulk OUT")
|
||||
}
|
||||
|
||||
// UNLINK de una lectura pendiente
|
||||
pending := cl.submit(dirIn, epBulkIn, 64, nil, nil)
|
||||
cl.seq++
|
||||
u := make([]byte, 48)
|
||||
binary.BigEndian.PutUint32(u[0:], cmdUnlink)
|
||||
binary.BigEndian.PutUint32(u[4:], cl.seq)
|
||||
binary.BigEndian.PutUint32(u[20:], pending)
|
||||
cl.c.Write(u)
|
||||
h := readN(t, cl.c, 48)
|
||||
if binary.BigEndian.Uint32(h[0:]) != retUnlink || int32(binary.BigEndian.Uint32(h[20:])) != errConnReset {
|
||||
t.Fatalf("RET_UNLINK inesperado: % X", h)
|
||||
}
|
||||
|
||||
if len(p.Snapshot(0).Connections) != 1 {
|
||||
t.Fatal("deberÃa haber una conexión USB activa")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user