hybridgroup.gobot/drivers/common/mfrc522/mfrc522_picc.go

404 lines
13 KiB
Go

package mfrc522
import (
"fmt"
)
const piccDebug = false
// Commands sent to the PICC, used by the PCD to for communication with several PICCs (ISO 14443-3, Type A, section 6.4)
const (
// Activation
piccCommandRequestA = 0x26 // REQuest command type A, 7 bit frame, invites PICCs in state IDLE to go to READY
piccCommandWakeUpA = 0x52 // Wake-UP command type A, 7 bit frame, invites PICCs in state IDLE and HALT to go to READY
// Anticollision and SAK
piccCommandCascadeLevel1 = 0x93 // Select cascade level 1
piccCommandCascadeLevel2 = 0x95 // Select cascade Level 2
piccCommandCascadeLevel3 = 0x97 // Select cascade Level 3
piccCascadeTag = 0x88 // Cascade tag is used during anti collision
piccUIDNotComplete = 0x04 // used on SAK call
// Halt
piccCommandHLTA = 0x50 // Halt command, Type A. Instructs an active PICC to go to state HALT.
piccCommandRATS = 0xE0 // Request command for Answer To Reset.
// The commands used for MIFARE Classic (from http://www.mouser.com/ds/2/302/MF1S503x-89574.pdf, Section 9)
// Use MFAuthent to authenticate access to a sector, then use these commands to read/write/modify the blocks on
// the sector. The read/write commands can also be used for MIFARE Ultralight.
piccCommandMFRegAUTHRegKEYRegA = 0x60 // Perform authentication with Key A
piccCommandMFRegAUTHRegKEYRegB = 0x61 // Perform authentication with Key B
// Reads one 16 byte block from the authenticated sector of the PICC. Also used for MIFARE Ultralight.
piccCommandMFRegREAD = 0x30
// Writes one 16 byte block to the authenticated sector of the PICC. Called "COMPATIBILITY WRITE" for MIFARE Ultralight.
piccCommandMFRegWRITE = 0xA0
piccWriteAck = 0x0A // MIFARE Classic: 4 bit ACK, we use any other value as NAK (data sheet: 0h to 9h, Bh to Fh)
piccCommandMFRegDECREMENT = 0xC0 // Decrements the contents of a block and stores the result in the internal data register.
piccCommandMFRegINCREMENT = 0xC1 // Increments the contents of a block and stores the result in the internal data register.
piccCommandMFRegRESTORE = 0xC2 // Reads the contents of a block into the internal data register.
piccCommandMFRegTRANSFER = 0xB0 // Writes the contents of the internal data register to a block.
// The commands used for MIFARE Ultralight (from http://www.nxp.com/documents/dataRegsheet/MF0ICU1.pdf, Section 8.6)
// The piccCommandMFRegREAD and piccCommandMFRegWRITE can also be used for MIFARE Ultralight.
//piccCommandULRegWRITE = 0xA2 // Writes one 4 byte page to the PICC.
)
const piccReadWriteAuthBlock = uint8(11)
var piccKey = []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
var piccUserBlockAddresses = []byte{8, 9, 10}
var piccCardFromSak = map[uint8]string{0x08: "Classic 1K, Plus 2K-SE-1K(SL1)", 0x18: "Classic 4K, Plus 4K(SL1)",
0x10: "Plus 2K(SL2)", 0x11: "Plus 4K(SL2)", 0x20: "Plus 2K-SE-1K(SL3), Plus 4K(SL3)"}
// IsCardPresent is used to poll for a card in range. After an successful request, the card is halted.
func (d *MFRC522Common) IsCardPresent() error {
d.firstCardAccess = true
if err := d.writeByteData(regTxMode, rxtxModeRegReset); err != nil {
return err
}
if err := d.writeByteData(regRxMode, rxtxModeRegReset); err != nil {
return err
}
if err := d.writeByteData(regModWidth, modWidthRegReset); err != nil {
return err
}
answer := []byte{0x00, 0x00} // also called ATQA
if err := d.piccRequest(piccCommandWakeUpA, answer); err != nil {
return err
}
if piccDebug {
fmt.Printf("Card found: %v\n\n", answer)
}
if err := d.piccHalt(); err != nil {
return err
}
return nil
}
// ReadText reads a card with the dedicated workflow: REQA, Activate, Perform Transaction, Halt/Deselect.
// see "Card Polling" in https://www.nxp.com/docs/en/application-note/AN10834.pdf.
// and return the result as text string.
// TODO: make this more usable, e.g. by given length of text
func (d *MFRC522Common) ReadText() (string, error) {
answer := []byte{0x00, 0x00}
if err := d.piccRequest(piccCommandWakeUpA, answer); err != nil {
return "", err
}
uid, err := d.piccActivate()
if err != nil {
return "", err
}
if piccDebug {
fmt.Printf("uid: %v\n", uid)
}
if err := d.piccAuthenticate(piccReadWriteAuthBlock, piccKey, uid); err != nil {
if piccDebug {
fmt.Println("authenticate failed for address", piccReadWriteAuthBlock)
}
return "", err
}
var content []byte
for _, block := range piccUserBlockAddresses {
blockData, err := d.piccRead(block)
if err != nil {
if piccDebug {
fmt.Println("read failed at block", block)
}
return "", err
}
content = append(content, blockData...)
}
if piccDebug {
fmt.Println("content:", string(content[:]), content)
}
if err := d.piccHalt(); err != nil {
return "", err
}
return string(content[:]), d.stopCrypto1()
}
// WriteText writes the given string to the card. All old values will be overwritten.
func (d *MFRC522Common) WriteText(text string) error {
answer := []byte{0x00, 0x00}
if err := d.piccRequest(piccCommandWakeUpA, answer); err != nil {
return err
}
uid, err := d.piccActivate()
if err != nil {
return err
}
if piccDebug {
fmt.Printf("uid: %v\n", uid)
}
if err := d.piccAuthenticate(piccReadWriteAuthBlock, piccKey, uid); err != nil {
if piccDebug {
fmt.Println("authenticate failed for address", piccReadWriteAuthBlock)
}
return err
}
// prepare data with text and trailing zero's
textData := append([]byte(text), make([]byte, len(piccUserBlockAddresses)*16)...)
for i, blockNum := range piccUserBlockAddresses {
blockData := textData[i*16 : (i+1)*16]
err := d.piccWrite(blockNum, blockData)
if err != nil {
if piccDebug {
fmt.Println("write failed at block", blockNum)
}
return err
}
}
if err := d.piccHalt(); err != nil {
return err
}
return d.stopCrypto1()
}
func (d *MFRC522Common) piccHalt() error {
if piccDebug {
fmt.Println("-halt-")
}
haltCommand := []byte{piccCommandHLTA, 0x00}
crcResult := []byte{0x00, 0x00}
if err := d.calculateCRC(haltCommand, crcResult); err != nil {
return err
}
haltCommand = append(haltCommand, crcResult...)
txLastBits := uint8(0x00) // we use all 8 bits
if err := d.communicateWithPICC(commandRegTransceive, haltCommand, []byte{}, txLastBits, false); err != nil {
// an error is the sign for successful halt
if piccDebug {
fmt.Println("this is not treated as error:", err)
}
return nil
}
return fmt.Errorf("something went wrong with halt")
}
func (d *MFRC522Common) piccWrite(block uint8, blockData []byte) error {
if piccDebug {
fmt.Println("-write-")
fmt.Println("blockData:", blockData, len(blockData))
}
if len(blockData) != 16 {
return fmt.Errorf("the block to write needs to be exactly 16 bytes long, but has %d bytes", len(blockData))
}
// MIFARE Classic protocol requires two steps to perform a write.
// Step 1: Tell the PICC we want to write to block blockAddr.
writeDataCommand := []byte{piccCommandMFRegWRITE, block}
crcResult := []byte{0x00, 0x00}
if err := d.calculateCRC(writeDataCommand, crcResult); err != nil {
return err
}
writeDataCommand = append(writeDataCommand, crcResult...)
txLastBits := uint8(0x00) // we use all 8 bits
backData := make([]byte, 1)
if err := d.communicateWithPICC(commandRegTransceive, writeDataCommand, backData, txLastBits, false); err != nil {
return err
}
if backData[0]&piccWriteAck != piccWriteAck {
return fmt.Errorf("preparation of write on MIFARE classic failed (%v)", backData)
}
if piccDebug {
fmt.Println("backData", backData)
}
// Step 2: Transfer the data
if err := d.calculateCRC(blockData, crcResult); err != nil {
return err
}
var writeData []byte
writeData = append(writeData, blockData...)
writeData = append(writeData, crcResult...)
if err := d.communicateWithPICC(commandRegTransceive, writeData, []byte{}, txLastBits, false); err != nil {
return err
}
return nil
}
func (d *MFRC522Common) piccRead(block uint8) ([]byte, error) {
if piccDebug {
fmt.Println("-read-")
}
readDataCommand := []byte{piccCommandMFRegREAD, block}
crcResult := []byte{0x00, 0x00}
if err := d.calculateCRC(readDataCommand, crcResult); err != nil {
return nil, err
}
readDataCommand = append(readDataCommand, crcResult...)
txLastBits := uint8(0x00) // we use all 8 bits
backData := make([]byte, 18) // 16 data byte and 2 byte CRC
if err := d.communicateWithPICC(commandRegTransceive, readDataCommand, backData, txLastBits, true); err != nil {
return nil, err
}
return backData[:16], nil
}
func (d *MFRC522Common) piccAuthenticate(address uint8, key []byte, uid []byte) error {
if piccDebug {
fmt.Println("-authenticate-")
}
buf := []byte{piccCommandMFRegAUTHRegKEYRegA, address}
buf = append(buf, key...)
buf = append(buf, uid...)
if err := d.communicateWithPICC(commandRegMFAuthent, buf, []byte{}, 0, false); err != nil {
return err
}
return nil
}
// activate a card with the dedicated workflow: Anticollision and optional Request for "Answer To Select" (RATS) and
// "Protocol Parameter Selection" (PPS).
// see "Card Activation" in https://www.nxp.com/docs/en/application-note/AN10834.pdf.
// note: the card needs to be in ready state, e.g. by a request or wake up is done before
func (d *MFRC522Common) piccActivate() ([]byte, error) {
if err := d.clearRegisterBitMask(regColl, collRegValuesAfterCollBit); err != nil {
return nil, err
}
if err := d.writeByteData(regBitFraming, bitFramingRegReset); err != nil {
return nil, err
}
// start cascade level 1 (0x93) for return:
// * one size UID (4 byte): UID0..3 and one byte BCC or
// * cascade tag (0x88) and UID0..2 and BCC
// in the latter case the UID is incomplete and the next cascade level needs to be started.
// cascade level 2 (0x95) return:
// * double size UID (7 byte): UID3..6 and one byte BCC or
// * cascade tag (0x88) and UID3..5 and BCC
// cascade level 3 (0x97) return:
// * triple size UID (10 byte): UID6..9
// after each anticollision check (request of next UID) the SAK needs to be done (same level command)
// BCC: Block Check Character
// SAK: Select Acknowledge
var uid []byte
var sak uint8
for cascadeLevel := 1; cascadeLevel < 3; cascadeLevel++ {
var piccCommand uint8
switch cascadeLevel {
case 1:
piccCommand = piccCommandCascadeLevel1
case 2:
piccCommand = piccCommandCascadeLevel2
case 3:
piccCommand = piccCommandCascadeLevel3
default:
return nil, fmt.Errorf("unknown cascade level %d", cascadeLevel)
}
if piccDebug {
fmt.Println("-anti collision-")
}
txLastBits := uint8(0x00) // we use all 8 bits
numValidBits := uint8(4 * 8)
sendForAnticol := []byte{piccCommand, numValidBits}
backData := []byte{0x00, 0x00, 0x00, 0x00, 0x00} // 4 bytes CT/UID and BCC
if err := d.communicateWithPICC(commandRegTransceive, sendForAnticol, backData, txLastBits, false); err != nil {
return nil, err
}
// TODO: no real anticollision check yet
// check BCC
bcc := byte(0)
for _, v := range backData[:4] {
bcc = bcc ^ v
}
if bcc != backData[4] {
return nil, fmt.Errorf(fmt.Sprintf("BCC mismatch, expected %02x actual %02x", bcc, backData[4]))
}
if backData[0] == piccCascadeTag {
uid = append(uid, backData[1:3]...)
if piccDebug {
fmt.Printf("next cascade is needed after SAK, uid: %v", uid)
}
} else {
uid = append(uid, backData[:4]...)
if piccDebug {
fmt.Printf("backData: %v, uid: %v\n", backData, uid)
}
}
if piccDebug {
fmt.Println("-select acknowledge-")
}
sendCommand := []byte{piccCommand}
sendCommand = append(sendCommand, 0x70) // 7 bytes
sendCommand = append(sendCommand, backData...) // uid including BCC
crcResult := []byte{0x00, 0x00}
if err := d.calculateCRC(sendCommand, crcResult); err != nil {
return uid, err
}
sendCommand = append(sendCommand, crcResult...)
sakData := []byte{0x00, 0x00, 0x00}
if err := d.communicateWithPICC(commandRegTransceive, sendCommand, sakData, txLastBits, false); err != nil {
return nil, err
}
bcc = byte(0)
for _, v := range sakData[:2] {
bcc = bcc ^ v
}
if piccDebug {
fmt.Printf("sak data: %v\n", sakData)
}
if sakData[0] != piccUIDNotComplete {
sak = sakData[0]
break
}
if piccDebug {
fmt.Printf("next cascade called, SAK: %v\n", sakData[0])
}
}
if piccDebug || d.firstCardAccess {
d.firstCardAccess = false
fmt.Printf("card '%s' selected\n", piccCardFromSak[sak])
}
return uid, nil
}
func (d *MFRC522Common) piccRequest(reqMode uint8, answer []byte) error {
if len(answer) < 2 {
return fmt.Errorf("at least 2 bytes room needed for the answer")
}
if err := d.clearRegisterBitMask(regColl, collRegValuesAfterCollBit); err != nil {
return err
}
// for request A and wake up the short frame format is used - transmit only 7 bits of the last (and only) byte.
txLastBits := uint8(0x07 & bitFramingRegTxLastBits)
if err := d.communicateWithPICC(commandRegTransceive, []byte{reqMode}, answer, txLastBits, false); err != nil {
return err
}
return nil
}