// Package usbip exporta la impresora como un dispositivo USB real mediante // el protocolo USB/IP. Un cliente USB/IP (usbip-win2 en Windows, usbip en // Linux) conecta el dispositivo al sistema operativo, que lo ve como una // impresora USB más: clase 07 (impresora), subclase 01, protocolo 02 // (bidireccional), con un endpoint bulk OUT (EP 1) y uno bulk IN (EP 2). // // Protocolo: https://docs.kernel.org/usb/usbip_protocol.html package usbip import ( "bytes" "encoding/binary" "errors" "io" "net" "sync" "unicode/utf16" "escpos-emulator/internal/escpos" ) const ( protocolVersion = 0x0111 opReqDevlist = 0x8005 opRepDevlist = 0x0005 opReqImport = 0x8003 opRepImport = 0x0003 cmdSubmit = 1 cmdUnlink = 2 retSubmit = 3 retUnlink = 4 dirOut = 0 dirIn = 1 epBulkOut = 1 epBulkIn = 2 speedFull = 2 errPipe = -32 // -EPIPE: STALL errConnReset = -104 // -ECONNRESET: URB cancelada maxInBuffer = 4096 maxTransfer = 16 << 20 ) // BusID es el identificador con el que se conecta el dispositivo // (usbip attach -r -b 1-1). const BusID = "1-1" // Device describe la identidad USB del dispositivo. type Device struct { VendorID uint16 ProductID uint16 Manufacturer string Product string Serial string IEEE1284ID string // respuesta a GET_DEVICE_ID de la clase impresora } // DeviceFromProfile construye la identidad USB a partir del perfil. func DeviceFromProfile(p escpos.Profile) Device { return Device{ VendorID: p.USBVendorID, ProductID: p.USBProductID, Manufacturer: p.Maker, Product: p.Model, Serial: p.Serial, IEEE1284ID: "MFG:" + p.Maker + ";MDL:" + p.Model + ";CMD:ESC/POS;CLS:PRINTER;DES:" + p.Name + ";", } } // Server atiende clientes USB/IP. type Server struct { ln net.Listener dev Device printer *escpos.Printer mu sync.Mutex attached bool } // Listen abre el puerto (normalmente 3240) y atiende en segundo plano. func Listen(addr string, dev Device, p *escpos.Printer) (*Server, error) { ln, err := net.Listen("tcp", addr) if err != nil { return nil, err } s := &Server{ln: ln, dev: dev, printer: p} go s.serve() return s, nil } // Addr devuelve la dirección en la que escucha. func (s *Server) Addr() net.Addr { return s.ln.Addr() } // Close deja de aceptar conexiones. func (s *Server) Close() error { return s.ln.Close() } func (s *Server) serve() { for { conn, err := s.ln.Accept() if err != nil { if errors.Is(err, net.ErrClosed) { return } continue } go s.handle(conn) } } func (s *Server) handle(conn net.Conn) { defer conn.Close() for { var h [8]byte if _, err := io.ReadFull(conn, h[:]); err != nil { return } switch binary.BigEndian.Uint16(h[2:]) { case opReqDevlist: reply := opHeader(opRepDevlist, 0) reply = binary.BigEndian.AppendUint32(reply, 1) reply = append(reply, s.deviceRecord()...) reply = append(reply, 0x07, 0x01, 0x02, 0x00) // interfaz 0: clase, subclase, protocolo, relleno if _, err := conn.Write(reply); err != nil { return } case opReqImport: var bus [32]byte if _, err := io.ReadFull(conn, bus[:]); err != nil { return } id := string(bytes.TrimRight(bus[:], "\x00")) if id != BusID || !s.acquire() { s.printer.Note("USB/IP: rechazada la conexión a %q desde %s (no existe o ya está conectado)", id, conn.RemoteAddr()) _, _ = conn.Write(opHeader(opRepImport, 1)) return } _, err := conn.Write(append(opHeader(opRepImport, 0), s.deviceRecord()...)) if err == nil { s.runDevice(conn) } s.release() return default: return } } } func (s *Server) acquire() bool { s.mu.Lock() defer s.mu.Unlock() if s.attached { return false } s.attached = true return true } func (s *Server) release() { s.mu.Lock() s.attached = false s.mu.Unlock() } func opHeader(code uint16, status uint32) []byte { b := binary.BigEndian.AppendUint16(nil, protocolVersion) b = binary.BigEndian.AppendUint16(b, code) return binary.BigEndian.AppendUint32(b, status) } // deviceRecord es la estructura usbip_usb_device (312 bytes, big endian). func (s *Server) deviceRecord() []byte { b := make([]byte, 312) copy(b[0:256], "/sys/devices/platform/escpos-emulator/usb1/"+BusID) copy(b[256:288], BusID) binary.BigEndian.PutUint32(b[288:], 1) // busnum binary.BigEndian.PutUint32(b[292:], 2) // devnum binary.BigEndian.PutUint32(b[296:], speedFull) binary.BigEndian.PutUint16(b[300:], s.dev.VendorID) binary.BigEndian.PutUint16(b[302:], s.dev.ProductID) binary.BigEndian.PutUint16(b[304:], 0x0100) // bcdDevice b[306], b[307], b[308] = 0, 0, 0 // la clase se declara en la interfaz b[309] = 1 // bConfigurationValue b[310] = 1 // bNumConfigurations b[311] = 1 // bNumInterfaces return b } // --------------------------------------------------------------------------- // Dispositivo conectado: intercambio de URBs. type inURB struct { seq uint32 length int numPackets uint32 } type devConn struct { srv *Server conn net.Conn session *escpos.Session wmu sync.Mutex // serializa las escrituras en conn mu sync.Mutex pendingIn []inURB inBuf []byte config byte } func (s *Server) runDevice(conn net.Conn) { d := &devConn{srv: s, conn: conn} d.session = s.printer.NewSession("USB "+conn.RemoteAddr().String(), d.pushIn) defer d.session.Close() var h [48]byte for { if _, err := io.ReadFull(conn, h[:]); err != nil { return } var err error switch binary.BigEndian.Uint32(h[0:]) { case cmdSubmit: err = d.submit(h) case cmdUnlink: err = d.unlink(h) default: return } if err != nil { return } } } func (d *devConn) submit(h [48]byte) error { seq := binary.BigEndian.Uint32(h[4:]) dir := binary.BigEndian.Uint32(h[12:]) ep := binary.BigEndian.Uint32(h[16:]) length := binary.BigEndian.Uint32(h[24:]) numPackets := binary.BigEndian.Uint32(h[32:]) var setup [8]byte copy(setup[:], h[40:48]) if length > maxTransfer { return errors.New("usbip: transferencia demasiado grande") } var data []byte if dir == dirOut && length > 0 { data = make([]byte, length) if _, err := io.ReadFull(d.conn, data); err != nil { return err } } switch { case ep == 0: return d.control(seq, numPackets, setup, data) case ep == epBulkOut && dir == dirOut: d.session.Write(data) return d.reply(seq, numPackets, 0, len(data), nil) case ep == epBulkIn && dir == dirIn: // El host deja una lectura pendiente; se completa cuando la // impresora tenga algo que enviar (mientras, el USB responde NAK). d.mu.Lock() d.pendingIn = append(d.pendingIn, inURB{seq: seq, length: int(length), numPackets: numPackets}) d.mu.Unlock() return d.drainIn() default: return d.reply(seq, numPackets, errPipe, 0, nil) } } func (d *devConn) unlink(h [48]byte) error { seq := binary.BigEndian.Uint32(h[4:]) target := binary.BigEndian.Uint32(h[20:]) d.wmu.Lock() defer d.wmu.Unlock() d.mu.Lock() var status int32 for i, u := range d.pendingIn { if u.seq == target { d.pendingIn = append(d.pendingIn[:i], d.pendingIn[i+1:]...) status = errConnReset break } } d.mu.Unlock() b := make([]byte, 48) binary.BigEndian.PutUint32(b[0:], retUnlink) binary.BigEndian.PutUint32(b[4:], seq) binary.BigEndian.PutUint32(b[20:], uint32(status)) _, err := d.conn.Write(b) return err } // pushIn recibe las respuestas de la impresora (DLE EOT, GS I...). func (d *devConn) pushIn(b []byte) { d.mu.Lock() d.inBuf = append(d.inBuf, b...) if over := len(d.inBuf) - maxInBuffer; over > 0 { d.inBuf = d.inBuf[over:] } d.mu.Unlock() _ = d.drainIn() } // drainIn completa lecturas bulk IN pendientes con los datos disponibles. func (d *devConn) drainIn() error { d.wmu.Lock() defer d.wmu.Unlock() for { d.mu.Lock() if len(d.pendingIn) == 0 || len(d.inBuf) == 0 { d.mu.Unlock() return nil } u := d.pendingIn[0] d.pendingIn = d.pendingIn[1:] n := min(u.length, len(d.inBuf)) chunk := append([]byte(nil), d.inBuf[:n]...) d.inBuf = d.inBuf[n:] d.mu.Unlock() if _, err := d.conn.Write(retSubmitPacket(u.seq, u.numPackets, 0, n, chunk)); err != nil { return err } } } func (d *devConn) reply(seq, numPackets uint32, status int32, actual int, data []byte) error { d.wmu.Lock() defer d.wmu.Unlock() _, err := d.conn.Write(retSubmitPacket(seq, numPackets, status, actual, data)) return err } func retSubmitPacket(seq, numPackets uint32, status int32, actual int, data []byte) []byte { b := make([]byte, 48, 48+len(data)) binary.BigEndian.PutUint32(b[0:], retSubmit) binary.BigEndian.PutUint32(b[4:], seq) binary.BigEndian.PutUint32(b[20:], uint32(status)) binary.BigEndian.PutUint32(b[24:], uint32(actual)) // Igual que el servidor de Linux: se devuelve el mismo number_of_packets // que envió el cliente (0 o 0xFFFFFFFF en transferencias no isócronas). binary.BigEndian.PutUint32(b[32:], numPackets) return append(b, data...) } // --------------------------------------------------------------------------- // Endpoint 0: peticiones de control. func (d *devConn) control(seq, numPackets uint32, setup [8]byte, data []byte) error { reqType, req := setup[0], setup[1] value := binary.LittleEndian.Uint16(setup[2:]) wLength := int(binary.LittleEndian.Uint16(setup[6:])) var resp []byte ok := false switch reqType & 0x60 { case 0x00: resp, ok = d.standardRequest(reqType, req, value) case 0x20: resp, ok = d.classRequest(reqType, req) } if !ok { return d.reply(seq, numPackets, errPipe, 0, nil) } if reqType&0x80 == 0 { return d.reply(seq, numPackets, 0, len(data), nil) } if len(resp) > wLength { resp = resp[:wLength] } return d.reply(seq, numPackets, 0, len(resp), resp) } func (d *devConn) standardRequest(reqType, req byte, value uint16) ([]byte, bool) { switch req { case 0x00: // GET_STATUS if reqType&0x1F == 0 { return []byte{0x01, 0x00}, true // autoalimentado } return []byte{0x00, 0x00}, true case 0x01, 0x03, 0x05: // CLEAR_FEATURE, SET_FEATURE, SET_ADDRESS return nil, true case 0x06: // GET_DESCRIPTOR return d.srv.dev.descriptor(byte(value>>8), byte(value)) case 0x08: // GET_CONFIGURATION d.mu.Lock() defer d.mu.Unlock() return []byte{d.config}, true case 0x09: // SET_CONFIGURATION d.mu.Lock() d.config = byte(value) d.mu.Unlock() return nil, true case 0x0A: // GET_INTERFACE return []byte{0x00}, true case 0x0B: // SET_INTERFACE return nil, value == 0 } return nil, false } // classRequest atiende las peticiones de la clase impresora USB 1.1. func (d *devConn) classRequest(reqType, req byte) ([]byte, bool) { switch { case reqType == 0xA1 && req == 0: // GET_DEVICE_ID id := d.srv.dev.IEEE1284ID n := len(id) + 2 return append([]byte{byte(n >> 8), byte(n)}, id...), true case reqType == 0xA1 && req == 1: // GET_PORT_STATUS st := d.srv.printer.Status() v := byte(0x18) // bit 3: sin error, bit 4: seleccionada if st.Blocked() { v &^= 0x08 } if st.Offline { v &^= 0x10 } if st.PaperEnd { v |= 0x20 } return []byte{v}, true case reqType == 0x21 && req == 2: // SOFT_RESET return nil, true } return nil, false } // descriptor devuelve los descriptores USB estándar (little endian). func (dev Device) descriptor(typ, index byte) ([]byte, bool) { switch typ { case 1: // dispositivo return []byte{ 18, 1, 0x00, 0x02, // bLength, DEVICE, bcdUSB 2.00 0, 0, 0, 64, // clase/subclase/protocolo en la interfaz, bMaxPacketSize0 byte(dev.VendorID), byte(dev.VendorID >> 8), byte(dev.ProductID), byte(dev.ProductID >> 8), 0x00, 0x01, // bcdDevice 1.00 1, 2, 3, // iManufacturer, iProduct, iSerialNumber 1, // bNumConfigurations }, true case 2: // configuración + interfaz + endpoints if index != 0 { return nil, false } return []byte{ 9, 2, 32, 0, 1, 1, 0, 0xC0, 1, // CONFIGURATION: wTotalLength 32, 1 interfaz, autoalimentado 9, 4, 0, 0, 2, 0x07, 0x01, 0x02, 0, // INTERFACE: 2 endpoints, impresora bidireccional 7, 5, 0x01, 0x02, 64, 0, 0, // ENDPOINT 1 OUT, bulk, 64 bytes 7, 5, 0x82, 0x02, 64, 0, 0, // ENDPOINT 2 IN, bulk, 64 bytes }, true case 3: // cadenas switch index { case 0: return []byte{4, 3, 0x09, 0x04}, true // idioma: inglés EE.UU. case 1: return stringDescriptor(dev.Manufacturer), true case 2: return stringDescriptor(dev.Product), true case 3: return stringDescriptor(dev.Serial), true } } // DEVICE_QUALIFIER, BOS...: un dispositivo full speed USB 2.00 los rechaza con STALL. return nil, false } func stringDescriptor(s string) []byte { u := utf16.Encode([]rune(s)) b := []byte{byte(2 + 2*len(u)), 3} for _, c := range u { b = append(b, byte(c), byte(c>>8)) } return b }