package cvend import ( "bytes" "encoding/binary" "encoding/hex" "errors" "log" "os" "slices" "time" "pm3.dev/ipp" ) const Path = "/dev/ttymxc3" func OpenIPP(handler ipp.Handler) (ipp.Session, error) { return ipp.Open(Path, handler) } func LogIPP(msgType byte, msgData []byte) { switch msgType { case 0x07: log.Printf("Heartbeat(%s)", hex.EncodeToString(msgData)) case 0x0f: log.Printf("Startup(%s)", hex.EncodeToString(msgData)) case 0xed: if len(msgData) == 0 { log.Printf("Log()") } else { log.Printf("Log(%d) %s", msgData[0], string(bytes.TrimSuffix(msgData[1:], []byte{0}))) } case 0xb1: if len(msgData) < 9 { log.Printf("ISORead(short)\n%s", hex.Dump(msgData)) } else { uid := msgData[2:9] log.Printf("ISORead(uid %s)\n%s", hex.EncodeToString(uid), hex.Dump(msgData)) } case 0xb3: log.Printf("ISOCardReleased\n%s", hex.Dump(msgData)) case 0xb5: log.Printf("APDUProxReply\n%s", hex.Dump(msgData)) case 0xb9: if len(msgData) < 11 { log.Printf("DESFireRead(short)\n%s", hex.Dump(msgData)) } else { uid := msgData[4:11] log.Printf("DESFireRead(uid %s)\n%s", hex.EncodeToString(uid), hex.Dump(msgData)) } case 0xbb: log.Printf("DESFireCardRemoved\n%s", hex.Dump(msgData)) case 0xbe: if len(msgData) < 22 || len(msgData) < 22+int(msgData[21]) { log.Printf("UnhandledCard(short)\n%s", hex.Dump(msgData)) } else { _ = msgData[:11] // unknown uid := msgData[11:18] atqa := binary.LittleEndian.Uint16(msgData[18:20]) sak := msgData[20] atrLen := msgData[21] atr := msgData[22 : 22+atrLen] _ = msgData[22+atrLen:] // unknown log.Printf("UnhandledCard(uid %s atqa 0x%04x sak %02x atr %s)\n%s", hex.EncodeToString(uid), atqa, sak, hex.EncodeToString(atr), hex.Dump(msgData)) } case 0xd1: log.Printf("EMVStatus\n%s", hex.Dump(msgData)) default: log.Printf("ipp %02x\n%s", msgType, hex.Dump(msgData)) } } var ( ErrCardRemoved = errors.New("card removed") ) type Device struct { ipp.Session closed chan struct{} statusReply chan []byte // 0x05 heartbeat chan []byte // 0x07 startup chan []byte // 0x0f desFireRead chan []byte // 0xb9 desFireCardRemoved chan []byte // 0xbb desFireCommandReply chan []byte // 0xbd unhandledCard chan []byte // 0xbe proxCardFunctionReply chan []byte // 0xe5 } func Open() (*Device, error) { d := &Device{ statusReply: make(chan []byte), heartbeat: make(chan []byte), startup: make(chan []byte), desFireRead: make(chan []byte), desFireCardRemoved: make(chan []byte), desFireCommandReply: make(chan []byte), unhandledCard: make(chan []byte), proxCardFunctionReply: make(chan []byte), } var err error d.Session, err = ipp.Open(Path, func(seq, replyTo byte, msgType byte, msgData []byte) { // if channel receiver waiting, deliver data to it, else log var ch chan []byte switch msgType { case 0x05: ch = d.statusReply case 0x07: ch = d.heartbeat case 0x0f: ch = d.startup case 0xb9: ch = d.desFireRead case 0xbb: ch = d.desFireCardRemoved case 0xbd: ch = d.desFireCommandReply case 0xbe: ch = d.unhandledCard case 0xe5: ch = d.proxCardFunctionReply } select { case ch <- slices.Clone(msgData): return default: } switch msgType { case 0x07: log.Printf("Heartbeat(%s)", hex.EncodeToString(msgData)) case 0xed: log.Printf("Log(%d) %s", msgData[0], string(bytes.TrimSuffix(msgData[1:], []byte{0}))) default: log.Printf("ipp %02x\n%s", msgType, hex.Dump(msgData)) } }) if err != nil { return nil, err } return d, err } func (d *Device) Close() error { close(d.closed) return d.Session.Close() } func (d *Device) AwaitStatus() ([]byte, error) { for { if err := d.SendIPP(0x04, 0, nil); err != nil { // Status return nil, err } select { case <-d.closed: return nil, os.ErrClosed case status := <-d.statusReply: return status, nil case <-d.startup: // retry case <-time.After(1 * time.Second): // retry } } } func (d *Device) ProxCardFunction(cardType uint16, enable bool) ([]byte, error) { var payload [4]byte binary.BigEndian.PutUint16(payload[0:2], cardType) payload[2] = 1 if enable { payload[3] = 1 } if err := d.SendIPP(0xe4, 0, payload[:]); err != nil { return nil, err } select { case resp := <-d.proxCardFunctionReply: return resp, nil case <-d.closed: return nil, os.ErrClosed } } func (d *Device) AwaitCard() (Card, error) { select { case data := <-d.desFireRead: if len(data) < 11 { log.Printf("short desfire read: %s", hex.EncodeToString(data)) return nil, errors.New("invalid desfire read") } c := &DESFireCard{ UID: data[4:], d: d, removed: make(chan struct{}), } go func() { <-d.desFireCardRemoved close(c.removed) }() return c, nil case <-d.closed: return nil, os.ErrClosed } } type Card interface { Release() error } type DESFireCard struct { UID []byte d *Device removed chan struct{} } func (c *DESFireCard) AwaitRemoved() { <-c.removed } func (c *DESFireCard) Release() error { select { case <-c.removed: return ErrCardRemoved default: } return c.d.SendIPP(0x32, 0, nil) } func (c *DESFireCard) Command(cmd byte, data []byte) ([]byte, error) { select { case <-c.removed: return nil, ErrCardRemoved default: } if err := c.d.SendIPP(0xbc, 0, append([]byte{cmd}, data...)); err != nil { return nil, err } select { case <-c.removed: return nil, ErrCardRemoved case <-c.d.closed: return nil, os.ErrClosed case data := <-c.d.desFireCommandReply: return data, nil } }