package access // built for PN53x import ( "context" "encoding/hex" "fmt" "net" "time" "github.com/clausecker/nfc/v2" "github.com/rs/zerolog" "git.tcp.direct/kayos/door5/pkg/logger" ) type ConnectionMode uint8 const ( I2C ConnectionMode = iota SPI ) type Keys struct { *nfc.Device AccessLog *CardTimeSeries incoming chan nfc.Target connectivity ConnectionMode infoString string readDeadline time.Time writeDeadline time.Time resetPin int } type localAddr struct { *Keys } func (la *localAddr) String() string { return la.Keys.infoString } func (la *localAddr) Network() string { switch la.Keys.connectivity { case I2C: return "i2c" case SPI: return "spi" } return "unknown" } func (rfid *Keys) Write(b []byte) (n int, err error) { logger.Get().Logger.Panic().Msg("not implemented") return 0, nil } func (rfid *Keys) Network() string { switch rfid.connectivity { case I2C: return "i2c" case SPI: return "spi" } return "unknown" } func (rfid *Keys) LocalAddr() net.Addr { return &localAddr{rfid} } func (rfid *Keys) RemoteAddr() net.Addr { return rfid } func (rfid *Keys) SetDeadline(t time.Time) error { rfid.readDeadline = t rfid.writeDeadline = t return nil } func (rfid *Keys) SetReadDeadline(t time.Time) error { rfid.readDeadline = t return nil } func (rfid *Keys) SetWriteDeadline(t time.Time) error { rfid.writeDeadline = t return nil } func (rfid *Keys) HasIRQ() bool { return rfid.connectivity == SPI && rfid.resetPin != 0 } //nolint:typecheck func NewRFID(connString string, mode ConnectionMode, ResetPin ...int) (*Keys, error) { log := logger.Get().Logger lg := log.With().Str("conn_string", connString).Logger() log = &lg reader := &Keys{ AccessLog: NewCardTimeSeries(), } reader.incoming = make(chan nfc.Target, 1) reader.connectivity = mode if len(ResetPin) > 0 { lg = log.With().Int("reset_pin", ResetPin[0]).Logger() log = &lg reader.resetPin = ResetPin[0] } tries := 0 lg = log.With().Int("tries", tries).Bool("hasIRQ", reader.HasIRQ()).Logger() log = &lg try: log.Trace().Msg("opening connection to NFC reader") dev, err := nfc.Open(connString) switch { case err != nil && reader.HasIRQ(): if tries > 3 { return nil, fmt.Errorf("failed to open device: %w", err) } log.Debug().Msg("errored during open, have IRQ, resetting and trying again...") reader.Reset() tries++ goto try case err != nil: log.Debug().Err(err).Msg("FUBAR") return nil, fmt.Errorf("failed to open device: %w", err) default: } reader.Device = &dev if reader.infoString, err = reader.Information(); err != nil { log.Warn().Err(err).Msg("failed to get device info, continuing anyway...") } if reader.infoString != "" { log.Debug().Str("info", reader.infoString).Msg("got device info") } log.Trace().Msg("initializing NFC reader") if err = reader.InitiatorInit(); err != nil { log.Debug().Err(err).Msg("FUBAR") return nil, fmt.Errorf("failed to initialize reader: %w", err) } return reader, nil } func (rfid *Keys) Reset() { log := logger.Get().Logger log.Debug().Msg("Resetting the RFID chip...") log.Panic().Msg("haha jk not implemented yet") } func (rfid *Keys) Close() error { if rfid.HasIRQ() { rfid.Reset() } return rfid.Device.Close() } type tagReadStatus struct { err error tagCount int currentTarget nfc.Target found []*Card } func (trs *tagReadStatus) Run(e *zerolog.Event, level zerolog.Level, message string) { stringers := make([]fmt.Stringer, len(trs.found)) for i, v := range trs.found { stringers[i] = v } e.Int("tagCount", trs.tagCount).Stringers("found", stringers) if trs.err != nil { e.Err(trs.err) } } func (trs *tagReadStatus) Error() string { return trs.err.Error() } type Card struct { nfc.Target Type string `json:"type"` Details map[string]interface{} `json:"details"` UID string `json:"uid"` } func (c *Card) String() string { return fmt.Sprintf("%s (%s)", c.UID, c.Type) } func (rfid *Keys) ListenForTags(ctx context.Context, want int) ([]*Card, error) { var status = &tagReadStatus{ err: nil, tagCount: 0, found: make([]*Card, 0), } log := logger.Get().Logger.Hook(status) var modulations = []nfc.Modulation{ {Type: nfc.ISO14443a, BaudRate: nfc.Nbr106}, {Type: nfc.ISO14443b, BaudRate: nfc.Nbr106}, {Type: nfc.Felica, BaudRate: nfc.Nbr212}, {Type: nfc.Felica, BaudRate: nfc.Nbr424}, {Type: nfc.Jewel, BaudRate: nfc.Nbr106}, {Type: nfc.ISO14443biClass, BaudRate: nfc.Nbr106}, } var ( errChan = make(chan error, want) ) var cancel context.CancelFunc ctx, cancel = context.WithCancel(ctx) defer cancel() go func() { for { select { case <-ctx.Done(): var err = ctx.Err() if rfid.LastError() != nil { err = fmt.Errorf("error from NFC reader: %w + context canceled: %s", rfid.LastError(), ctx.Err()) } errChan <- err return default: } var found int if found, status.currentTarget, status.err = rfid.InitiatorPollTarget(modulations, 1, 300*time.Millisecond); status.err != nil || found == 0 { log.Warn().Err(status.err).Int("found", found).Msg("error polling for target") continue } status.tagCount++ log.Debug().Msgf("got tag: %s", status.currentTarget.String()) var ( card *Card ) // Transform the target to a specific tag Type and send the UID to the channel switch status.currentTarget.Modulation() { case nfc.Modulation{Type: nfc.ISO14443a, BaudRate: nfc.Nbr106}: cardCasted := status.currentTarget.(*nfc.ISO14443aTarget) card = &Card{ UID: hex.EncodeToString(cardCasted.UID[:cardCasted.UIDLen]), Type: "ISO14443a", Target: cardCasted, } case nfc.Modulation{Type: nfc.ISO14443b, BaudRate: nfc.Nbr106}: cardCasted := status.currentTarget.(*nfc.ISO14443bTarget) card = &Card{ UID: hex.EncodeToString(cardCasted.ApplicationData[:len(cardCasted.ApplicationData)]), Type: "ISO14443b", Target: cardCasted, Details: map[string]interface{}{ "ProtocolInfo": cardCasted.ProtocolInfo, "BaudRate": cardCasted.Modulation().BaudRate, "ApplicationData": cardCasted.ApplicationData, "PUPI": cardCasted.Pupi, "CardIdentifier": cardCasted.CardIdentifier, }, } case nfc.Modulation{Type: nfc.Felica, BaudRate: nfc.Nbr212}, nfc.Modulation{Type: nfc.Felica, BaudRate: nfc.Nbr424}: cardCasted := status.currentTarget.(*nfc.FelicaTarget) card = &Card{ UID: hex.EncodeToString(cardCasted.ID[:cardCasted.Len]), Type: "Felica", Target: cardCasted, Details: map[string]interface{}{ "Len": cardCasted.Len, "Pad": cardCasted.Pad, "SysCode": cardCasted.SysCode, "ResCode": cardCasted.ResCode, }, } switch cardCasted.Modulation().BaudRate { case nfc.Nbr212: card.Details["kbps"] = 212 case nfc.Nbr424: card.Details["kbps"] = 424 default: } case nfc.Modulation{Type: nfc.Jewel, BaudRate: nfc.Nbr106}: cardCasted := status.currentTarget.(*nfc.JewelTarget) card = &Card{ UID: hex.EncodeToString(cardCasted.ID[:len(cardCasted.ID)]), Type: "Jewel", Target: cardCasted, Details: map[string]interface{}{ "SensRes": cardCasted.SensRes, }, } case nfc.Modulation{Type: nfc.ISO14443biClass, BaudRate: nfc.Nbr106}: cardCasted := status.currentTarget.(*nfc.ISO14443biClassTarget) card = &Card{ UID: hex.EncodeToString(cardCasted.UID[:len(cardCasted.UID)]), Type: "ISO14443biClass", Target: cardCasted, Details: map[string]interface{}{ "Baud": cardCasted.Baud, }, } } cardLog := log.With().Fields(card.Details).Str("uid", card.UID).Str("type", card.Type).Logger() cardLog.Info().Msg("+1 card") cardLog.Trace().Msg("checking card into timeseries log") rfid.AccessLog.CheckIn(card) cardLog.Trace().Msg("checked card into timeseries log") status.found = append(status.found, card) if status.tagCount >= want { cancel() return } time.Sleep(300 * time.Millisecond) } }() select { case e := <-errChan: return status.found, e case <-ctx.Done(): switch status.tagCount { case 0: return nil, fmt.Errorf("failed to read any tags") case want: return status.found, nil default: return status.found, fmt.Errorf("failed to read %d tags, only read %d", want, status.tagCount) } } } func (rfid *Keys) ListenForOneTag(ctx context.Context) (*Card, error) { if cards, err := rfid.ListenForTags(ctx, 1); err != nil { return nil, err } else { return cards[0], nil } } func (rfid *Keys) Read(buf []byte) (int, error) { found, err := rfid.ListenForOneTag(context.Background()) if err != nil { return 0, err } return copy(buf, []byte(found.UID)), nil }