diff options
| author | Kevin Wallace <kevin@pentabarf.net> | 2026-04-26 08:18:53 -0700 |
|---|---|---|
| committer | Kevin Wallace <kevin@pentabarf.net> | 2026-04-26 08:18:53 -0700 |
| commit | 1c8205cff6432248212a160a7a86b8c896dd2a8d (patch) | |
| tree | c2b33124837881e20becce13285c3b5a9a2a183b /cvend/cvend.go | |
| parent | wait for cvend at startup, only redraw on state changes (diff) | |
cvend wip
Diffstat (limited to 'cvend/cvend.go')
| -rw-r--r-- | cvend/cvend.go | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/cvend/cvend.go b/cvend/cvend.go index 263b1f7..b002629 100644 --- a/cvend/cvend.go +++ b/cvend/cvend.go @@ -4,7 +4,11 @@ import ( "bytes" "encoding/binary" "encoding/hex" + "errors" "log" + "os" + "slices" + "time" "pm3.dev/ipp" ) @@ -66,3 +70,180 @@ func LogIPP(msgType byte, msgData []byte) { 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(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, 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, 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, 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, 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 + } +} |