summaryrefslogtreecommitdiff
path: root/cvend
diff options
context:
space:
mode:
Diffstat (limited to 'cvend')
-rw-r--r--cvend/cvend.go181
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
+ }
+}