186 lines
4.8 KiB
Go
186 lines
4.8 KiB
Go
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")
|
|
}
|
|
}
|
|
|