Files
2026-09-30 10:22:42 +02:00

486 lines
13 KiB
Go

// 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 <host> -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
}