initial commit
This commit is contained in:
@@ -0,0 +1,485 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user