class PICC
Constants
- CMD_ADDITIONAL_FRAME
- CMD_DESELECT
- CMD_PPS
- CMD_RATS
- FSCI_to_FSC
Attributes
sak[R]
uid[R]
Public Class Methods
identify_model(sak)
click to toggle source
# File lib/picc.rb, line 154 def self.identify_model(sak) # SAK coding separation reference: # https://www.nxp.com/docs/en/application-note/AN10833.pdf # https://www.nxp.com/docs/en/application-note/AN10834.pdf if sak & 0x04 != 0 return :picc_uid_not_complete end if sak & 0x02 != 0 return :picc_reserved_future_use end if sak & 0x08 != 0 if sak & 0x10 != 0 return :picc_mifare_4k end if sak & 0x01 != 0 return :picc_mifare_mini end return :picc_mifare_1k end if sak & 0x10 != 0 if sak & 0x01 != 0 return :picc_mifare_plus_4k_sl2 end return :picc_mifare_plus_2k_sl2 end if sak == 0x00 return :picc_mifare_ultralight end if sak & 0x20 != 0 return :picc_iso_14443_4 end if sak & 0x40 != 0 return :picc_iso_18092 end return :picc_unknown end
new(pcd, uid, sak)
click to toggle source
# File lib/picc.rb, line 12 def initialize(pcd, uid, sak) @pcd = pcd @uid = uid @sak = sak @halted = false ## ISO mode @cid = 0x00 # We don't support CID, fix it to 0 @fsc = 16 # Assume PICC only supports 16 bytes frame @fwt = 256 # 77.33ms(256 ticks) default frame waiting time @picc_support_cid = false # PICC support for CID @picc_support_nad = false # PICC support for NAD @historical_byte = [] @block_number = 0 # ISO frame block number @iso_selected = false # If card is in iso mode end
Public Instance Methods
halt()
click to toggle source
# File lib/picc.rb, line 149 def halt iso_deselect if @iso_selected @halted = @pcd.picc_halt end
iso_deselect()
click to toggle source
Send S(DESELECT)
# File lib/picc.rb, line 130 def iso_deselect buffer = [CMD_DESELECT] received_data = picc_transceive(buffer) result = received_data[0] & 0xF7 == CMD_DESELECT @iso_selected = !result result end
iso_select()
click to toggle source
ISO/IEC 14443-4 select
# File lib/picc.rb, line 105 def iso_select # Send RATS (Request for Answer To Select) buffer = [CMD_RATS, 0x50 | @cid] received_data = picc_transceive(buffer) process_ats(received_data) # Send PPS (Protocol and Parameter Selection Request) buffer = [CMD_PPS | @cid, 0x11, (@dsi << 2) | @dri] received_data = picc_transceive(buffer) raise UnexpectedDataError, 'Incorrect response' if received_data[0] != (0xD0 | @cid) # Set PCD baud rate @pcd.transceiver_baud_rate(:tx, @dri) @pcd.transceiver_baud_rate(:rx, @dsi) @block_number = 0 @max_frame_size = [@pcd.buffer_size, @fsc].min @max_inf_size = @max_frame_size - 3 # PCB + CRC16 @max_inf_size -= 1 if @picc_support_cid @max_inf_size -= 1 if @picc_support_nad @iso_selected = true end
iso_transceive(send_data)
click to toggle source
Wrapper for handling ISO protocol
# File lib/picc.rb, line 39 def iso_transceive(send_data) # Split data according to max buffer size send_data = [send_data] unless send_data.is_a? Array chained_data = send_data.each_slice(@max_inf_size).to_a # Initialize I-block pcb = 0x02 # Send chained data until chained_data.empty? pcb &= 0xEF # Reset chaining indicator pcb |= 0x10 if chained_data.size > 1 # Set chaining pcb |= @block_number # Set block number data = chained_data.shift buffer = [pcb] + data finished = false until finished received_data = handle_wtx(buffer) # Retreive response pcb from data r_pcb = received_data[0] # Received ACK if r_pcb & 0xF6 == 0xA2 # If ACK matches current block number means success # Otherwise transmit it again if (pcb & 0x01) == (r_pcb & 0x01) finished = true end else finished = true end end @block_number ^= 1 # toggle block number for next frame end received_chained_data = [received_data] # Receive chained data while r_pcb & 0x10 != 0 ack = 0xA2 | @block_number # Set block number received_data = handle_wtx([ack]) # Send ACK to receive next frame r_pcb = received_data[0] received_chained_data << received_data @block_number ^= 1 # toggle block number for next frame end # Collect INF from chain inf = [] received_chained_data.each do |data| flag = data.shift data.shift if flag & 0x08 != 0 # CID present data.shift if flag & 0x04 != 0 # NAD present inf.concat(data) end inf end
picc_transceive(send_data, accept_timeout = false, need_bits = false)
click to toggle source
# File lib/picc.rb, line 29 def picc_transceive(send_data, accept_timeout = false, need_bits = false) received_data, valid_bits = @pcd.picc_transceive(send_data, accept_timeout) if need_bits return received_data, valid_bits else return received_data end end
restart_communication()
click to toggle source
# File lib/picc.rb, line 139 def restart_communication picc_was_in_iso_mode = @iso_selected iso_deselect if picc_was_in_iso_mode unless @pcd.reestablish_picc_communication(@uid) halt raise CommunicationError, 'Unable to resume communication or wrong card was presented. Halting cards in the field.' end iso_select if picc_was_in_iso_mode end
Protected Instance Methods
crc32(*datas)
click to toggle source
# File lib/picc.rb, line 203 def crc32(*datas) crc = 0xFFFFFFFF datas.each do |data| data = [data] unless data.is_a? Array data.each do |byte| crc ^= byte 8.times do flag = crc & 0x01 > 0 crc >>= 1 crc ^= 0xEDB88320 if flag end end end crc end
Private Instance Methods
choose_d(value)
click to toggle source
# File lib/picc.rb, line 222 def choose_d(value) # ISO DS/DR # 0b000: 106kBd, 0b001: 212kBd, 0b010: 424kBd, 0b100: 848kBd # MFRC522 register & ISO DSI/DRI # 0b000: 106kBd, 0b001: 212kBd, 0b010: 424kBd, 0b011: 848kBd # Find largest bit(fastest baud rate) x = (value >> 2) & 0x01 y = (value >> 1) & 0x01 z = value & 0x01 ((x | y) << 1) + (x | (~y & z)) end
handle_wtx(data)
click to toggle source
# File lib/picc.rb, line 297 def handle_wtx(data) 24.times do begin received_data = picc_transceive(data) rescue CommunicationError => e raise e unless e.is_a? PICCTimeoutError # Try sending NAK when timeout nak = 0xB2 | @block_number data = [nak] next end pcb = received_data[0] # WTX detected if pcb & 0xF7 == 0xF2 inf_position = (pcb & 0x08 != 0) ? 2 : 1 wtxm = received_data[inf_position] & 0x3F # Set temporary timer @pcd.internal_timer(@fwt * wtxm) # Set WTX response data = [0xF2, wtxm] else # Set timer back to FWT @pcd.internal_timer(@fwt) return received_data end end raise PICCTimeoutError, 'Timeout while handling WTX frame.' end
process_ats(ats)
click to toggle source
Gether information from ATS (Answer to Select)
# File lib/picc.rb, line 236 def process_ats(ats) position = 1 t0 = ats[position] # Format byte fsci = t0 & 0x0F # PICC buffer size integer y1 = (t0 >> 4) & 0x07 # Optional frame(TA, TB, TC) indicator @fsc = FSCI_to_FSC.fetch(fsci) # Convert buffer size integer to bytes # Frame: TA if y1 & 0x01 != 0 position += 1 ta = ats[position] dr = ta & 0x07 # PCD to PICC baud rate ds = (ta >> 4) & 0x07 # PICC to PCD baud rate same_d = (ta >> 7) & 0x01 if same_d != 0 dr &= ds ds &= dr end @dri = choose_d(dr) @dsi = choose_d(ds) end # Frame: TB if y1 & 0x02 != 0 position += 1 tb = ats[position] fwi = (tb >> 4) & 0x0F # Frame wating integer sgfi = tb & 0x0F # Start-up frame guard integer # Convert integers to real time @fwt = (1 << fwi) sgft = (1 << sgfi) # Set frame waiting time @pcd.internal_timer(@fwt) end # Get info about CID or NAD if y1 & 0x04 != 0 position += 1 tc = ats[position] @picc_support_cid = true if tc & 0x02 != 0 @picc_support_nad = true if tc & 0x01 != 0 end position += 1 if ats.size - position > 0 @historical_byte = ats[position..-1] end # Start-up guard time sleep 0.000302 * sgft end