mfrc522 1.0.6 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,40 @@
1
+ module MIFARE
2
+ module UltralightEV1
3
+ def auth(passwd)
4
+ passwd_bytes = [passwd].pack('H*').bytes
5
+ if passwd_bytes.size != 4
6
+ raise UsageError, "Expect 4 bytes password in hex, got: #{passwd_bytes.size} byte"
7
+ end
8
+
9
+ transceive([CMD_PWD_AUTH, *passwd_bytes])
10
+ end
11
+
12
+ def authed?
13
+ @authed || false
14
+ end
15
+
16
+ def fast_read(from, to)
17
+ if (to - from + 1) > @max_range
18
+ raise UsageError, "Reading from #{from} to #{to} exceeds PCD receive buffer"
19
+ end
20
+
21
+ transceive([CMD_FAST_READ, from, to])
22
+ end
23
+
24
+ def read_counter(counter)
25
+ transceive([CMD_READ_CNT, counter])
26
+ end
27
+
28
+ def increment_counter(counter)
29
+ transceive([CMD_INCR_CNT, counter])
30
+ end
31
+
32
+ def counter_torn?(counter)
33
+ transceive([CMD_CHECK_TEARING_EVENT, counter]) != 0xBD
34
+ end
35
+
36
+ def read_signature
37
+ transceive([CMD_READ_SIG, 0x00])
38
+ end
39
+ end
40
+ end
@@ -1,4 +1,11 @@
1
1
  class PICC
2
+ FSCI_to_FSC = [16, 24, 32, 40, 48, 64, 96, 128, 256]
3
+
4
+ CMD_RATS = 0xE0
5
+ CMD_PPS = 0xD0
6
+ CMD_DESELECT = 0xC2
7
+ CMD_ADDITIONAL_FRAME = 0xAF
8
+
2
9
  attr_reader :uid
3
10
  attr_reader :sak
4
11
 
@@ -7,22 +14,319 @@ class PICC
7
14
  @uid = uid
8
15
  @sak = sak
9
16
  @halted = false
17
+
18
+ ## ISO mode
19
+ @cid = 0x00 # We don't support CID, fix it to 0
20
+ @fsc = 16 # Assume PICC only supports 16 bytes frame
21
+ @fwt = 256 # 77.33ms(256 ticks) default frame waiting time
22
+ @picc_support_cid = false # PICC support for CID
23
+ @picc_support_nad = false # PICC support for NAD
24
+ @historical_byte = []
25
+ @block_number = 0 # ISO frame block number
26
+ @iso_selected = false # If card is in iso mode
10
27
  end
11
28
 
12
- def resume_communication
13
- if @pcd.reestablish_picc_communication(@uid)
14
- @halted = false
15
- true
29
+ def picc_transceive(send_data, accept_timeout = false, need_bits = false)
30
+ received_data, valid_bits = @pcd.picc_transceive(send_data, accept_timeout)
31
+ if need_bits
32
+ return received_data, valid_bits
16
33
  else
17
- false
34
+ return received_data
35
+ end
36
+ end
37
+
38
+ # Wrapper for handling ISO protocol
39
+ def iso_transceive(send_data)
40
+ # Split data according to max buffer size
41
+ send_data = [send_data] unless send_data.is_a? Array
42
+ chained_data = send_data.each_slice(@max_inf_size).to_a
43
+
44
+ # Initialize I-block
45
+ pcb = 0x02
46
+
47
+ # Send chained data
48
+ until chained_data.empty?
49
+ pcb &= 0xEF # Reset chaining indicator
50
+ pcb |= 0x10 if chained_data.size > 1 # Set chaining
51
+ pcb |= @block_number # Set block number
52
+ data = chained_data.shift
53
+
54
+ buffer = [pcb] + data
55
+
56
+ finished = false
57
+ until finished
58
+ received_data = handle_wtx(buffer)
59
+
60
+ # Retreive response pcb from data
61
+ r_pcb = received_data[0]
62
+
63
+ # Received ACK
64
+ if r_pcb & 0xF6 == 0xA2
65
+ # If ACK matches current block number means success
66
+ # Otherwise transmit it again
67
+ if (pcb & 0x01) == (r_pcb & 0x01)
68
+ finished = true
69
+ end
70
+ else
71
+ finished = true
72
+ end
73
+ end
74
+
75
+ @block_number ^= 1 # toggle block number for next frame
76
+ end
77
+
78
+ received_chained_data = [received_data]
79
+
80
+ # Receive chained data
81
+ while r_pcb & 0x10 != 0
82
+ ack = 0xA2 | @block_number # Set block number
83
+ received_data = handle_wtx([ack]) # Send ACK to receive next frame
84
+
85
+ r_pcb = received_data[0]
86
+
87
+ received_chained_data << received_data
88
+
89
+ @block_number ^= 1 # toggle block number for next frame
90
+ end
91
+
92
+ # Collect INF from chain
93
+ inf = []
94
+ received_chained_data.each do |data|
95
+ inf_position = 1
96
+ inf_position += 1 if data[0] & 0x08 != 0 # CID present
97
+ inf_position += 1 if data[0] & 0x04 != 0 # NAD present
98
+
99
+ inf.concat(data[inf_position..-1])
100
+ end
101
+ inf
102
+ end
103
+
104
+ # ISO/IEC 14443-4 select
105
+ def iso_select
106
+ # Send RATS (Request for Answer To Select)
107
+ buffer = [CMD_RATS, 0x50 | @cid]
108
+ received_data = picc_transceive(buffer)
109
+
110
+ process_ats(received_data)
111
+
112
+ # Send PPS (Protocol and Parameter Selection Request)
113
+ buffer = [CMD_PPS | @cid, 0x11, (@dsi << 2) | @dri]
114
+ received_data = picc_transceive(buffer)
115
+ raise UnexpectedDataError, 'Incorrect response' if received_data[0] != (0xD0 | @cid)
116
+
117
+ # Set PCD baud rate
118
+ @pcd.transceiver_baud_rate(:tx, @dri)
119
+ @pcd.transceiver_baud_rate(:rx, @dsi)
120
+
121
+ @block_number = 0
122
+ @max_frame_size = [@pcd.buffer_size, @fsc].min
123
+ @max_inf_size = @max_frame_size - 3 # PCB + CRC16
124
+ @max_inf_size -= 1 if @picc_support_cid
125
+ @max_inf_size -= 1 if @picc_support_nad
126
+ @iso_selected = true
127
+ end
128
+
129
+ # Send S(DESELECT)
130
+ def iso_deselect
131
+ buffer = [CMD_DESELECT]
132
+ received_data = picc_transceive(buffer)
133
+
134
+ result = received_data[0] & 0xF7 == CMD_DESELECT
135
+ @iso_selected = !result
136
+ result
137
+ end
138
+
139
+ def restart_communication
140
+ picc_was_in_iso_mode = @iso_selected
141
+ iso_deselect if picc_was_in_iso_mode
142
+ unless @pcd.reestablish_picc_communication(@uid)
143
+ halt
144
+ raise CommunicationError, 'Unable to resume communication or wrong card was presented. Halting cards in the field.'
18
145
  end
146
+ iso_select if picc_was_in_iso_mode
19
147
  end
20
148
 
21
149
  def halt
22
- if @pcd.picc_halt
23
- @halted = true
24
- else
25
- @halted = false
150
+ iso_deselect if @iso_selected
151
+ @halted = @pcd.picc_halt
152
+ end
153
+
154
+ def self.identify_model(sak)
155
+ # SAK coding separation reference:
156
+ # https://www.nxp.com/docs/en/application-note/AN10833.pdf
157
+ # https://www.nxp.com/docs/en/application-note/AN10834.pdf
158
+ if sak & 0x04 != 0
159
+ return :picc_uid_not_complete
160
+ end
161
+
162
+ if sak & 0x02 != 0
163
+ return :picc_reserved_future_use
164
+ end
165
+
166
+ if sak & 0x08 != 0
167
+ if sak & 0x10 != 0
168
+ return :picc_mifare_4k
169
+ end
170
+
171
+ if sak & 0x01 != 0
172
+ return :picc_mifare_mini
173
+ end
174
+
175
+ return :picc_mifare_1k
176
+ end
177
+
178
+ if sak & 0x10 != 0
179
+ if sak & 0x01 != 0
180
+ return :picc_mifare_plus_4k_sl2
181
+ end
182
+
183
+ return :picc_mifare_plus_2k_sl2
26
184
  end
185
+
186
+ if sak == 0x00
187
+ return :picc_mifare_ultralight
188
+ end
189
+
190
+ if sak & 0x20 != 0
191
+ return :picc_iso_14443_4
192
+ end
193
+
194
+ if sak & 0x40 != 0
195
+ return :picc_iso_18092
196
+ end
197
+
198
+ return :picc_unknown
199
+ end
200
+
201
+ protected
202
+
203
+ def crc32(*datas)
204
+ crc = 0xFFFFFFFF
205
+
206
+ datas.each do |data|
207
+ data = [data] unless data.is_a? Array
208
+ data.each do |byte|
209
+ crc ^= byte
210
+ 8.times do
211
+ flag = crc & 0x01 > 0
212
+ crc >>= 1
213
+ crc ^= 0xEDB88320 if flag
214
+ end
215
+ end
216
+ end
217
+ crc
218
+ end
219
+
220
+ private
221
+
222
+ def choose_d(value)
223
+ # ISO DS/DR
224
+ # 0b000: 106kBd, 0b001: 212kBd, 0b010: 424kBd, 0b100: 848kBd
225
+ # MFRC522 register & ISO DSI/DRI
226
+ # 0b000: 106kBd, 0b001: 212kBd, 0b010: 424kBd, 0b011: 848kBd
227
+ # Find largest bit(fastest baud rate)
228
+ x = (value >> 2) & 0x01
229
+ y = (value >> 1) & 0x01
230
+ z = value & 0x01
231
+
232
+ ((x | y) << 1) + (x | (~y & z))
233
+ end
234
+
235
+ # Gether information from ATS (Answer to Select)
236
+ def process_ats(ats)
237
+ position = 1
238
+ t0 = ats[position] # Format byte
239
+
240
+ fsci = t0 & 0x0F # PICC buffer size integer
241
+ y1 = (t0 >> 4) & 0x07 # Optional frame(TA, TB, TC) indicator
242
+ @fsc = FSCI_to_FSC.fetch(fsci) # Convert buffer size integer to bytes
243
+
244
+ # Frame: TA
245
+ if y1 & 0x01 != 0
246
+ position += 1
247
+ ta = ats[position]
248
+
249
+ dr = ta & 0x07 # PCD to PICC baud rate
250
+ ds = (ta >> 4) & 0x07 # PICC to PCD baud rate
251
+ same_d = (ta >> 7) & 0x01
252
+
253
+ if same_d != 0
254
+ dr &= ds
255
+ ds &= dr
256
+ end
257
+
258
+ @dri = choose_d(dr)
259
+ @dsi = choose_d(ds)
260
+ end
261
+
262
+ # Frame: TB
263
+ if y1 & 0x02 != 0
264
+ position += 1
265
+ tb = ats[position]
266
+
267
+ fwi = (tb >> 4) & 0x0F # Frame wating integer
268
+ sgfi = tb & 0x0F # Start-up frame guard integer
269
+
270
+ # Convert integers to real time
271
+ @fwt = (1 << fwi)
272
+ sgft = (1 << sgfi)
273
+
274
+ # Set frame waiting time
275
+ @pcd.internal_timer(@fwt)
276
+ end
277
+
278
+ # Get info about CID or NAD
279
+ if y1 & 0x04 != 0
280
+ position += 1
281
+ tc = ats[position]
282
+
283
+ @picc_support_cid = true if tc & 0x02 != 0
284
+ @picc_support_nad = true if tc & 0x01 != 0
285
+ end
286
+
287
+ position += 1
288
+
289
+ if ats.size - position > 0
290
+ @historical_byte = ats[position..-1]
291
+ end
292
+
293
+ # Start-up guard time
294
+ sleep 0.000302 * sgft
295
+ end
296
+
297
+ def handle_wtx(data)
298
+ 24.times do
299
+ begin
300
+ received_data = picc_transceive(data)
301
+ rescue CommunicationError => e
302
+ raise e unless e.is_a? PICCTimeoutError
303
+
304
+ # Try sending NAK when timeout
305
+ nak = 0xB2 | @block_number
306
+ data = [nak]
307
+ next
308
+ end
309
+
310
+ pcb = received_data[0]
311
+
312
+ # WTX detected
313
+ if pcb & 0xF7 == 0xF2
314
+ inf_position = (pcb & 0x08 != 0) ? 2 : 1
315
+ wtxm = received_data[inf_position] & 0x3F
316
+
317
+ # Set temporary timer
318
+ @pcd.internal_timer(@fwt * wtxm)
319
+
320
+ # Set WTX response
321
+ data = [0xF2, wtxm]
322
+ else
323
+ # Set timer back to FWT
324
+ @pcd.internal_timer(@fwt)
325
+
326
+ return received_data
327
+ end
328
+ end
329
+
330
+ raise PICCTimeoutError, 'Timeout while handling WTX frame.'
27
331
  end
28
332
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mfrc522
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.6
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - atitan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-07-02 00:00:00.000000000 Z
11
+ date: 2018-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pi_piper
@@ -38,13 +38,17 @@ extra_rdoc_files: []
38
38
  files:
39
39
  - lib/core_ext.rb
40
40
  - lib/exceptions.rb
41
- - lib/iso144434.rb
42
41
  - lib/mfrc522.rb
43
42
  - lib/mifare/classic.rb
44
43
  - lib/mifare/des_fire.rb
45
44
  - lib/mifare/key.rb
45
+ - lib/mifare/plus.rb
46
+ - lib/mifare/plus_sl0.rb
47
+ - lib/mifare/plus_sl1.rb
48
+ - lib/mifare/plus_sl3.rb
46
49
  - lib/mifare/ultralight.rb
47
50
  - lib/mifare/ultralight_c.rb
51
+ - lib/mifare/ultralight_ev1.rb
48
52
  - lib/picc.rb
49
53
  homepage: https://github.com/atitan/MFRC522_Ruby
50
54
  licenses:
@@ -1,247 +0,0 @@
1
- class ISO144434 < PICC
2
- FSCI_to_FSC = [16, 24, 32, 40, 48, 64, 96, 128, 256]
3
-
4
- CMD_RATS = 0xE0
5
- CMD_PPS = 0xD0
6
- CMD_DESELECT = 0xC2
7
- CMD_SUCCESS = 0x00
8
- CMD_ADDITIONAL_FRAME = 0xAF
9
-
10
- def initialize(pcd, uid, sak)
11
- super
12
-
13
- @cid = 0x00 # We don't support CID
14
- @fsc = 16 # Assume PICC only supports 16 bytes frame
15
- @fwt = 256 # 77.33ms(256 ticks) default frame waiting time
16
-
17
- @support_cid = false
18
- @support_nad = false
19
- @block_number = 0
20
- @selected = false
21
- end
22
-
23
- # ISO/IEC 14443-4 select
24
- def select
25
- # Send RATS (Request for Answer To Select)
26
- buffer = [CMD_RATS, 0x50 | @cid]
27
- received_data = @pcd.picc_transceive(buffer)
28
-
29
- dr, ds = process_ats(received_data)
30
-
31
- # Send PPS (Protocol and Parameter Selection Request)
32
- buffer = [CMD_PPS | @cid, 0x11, (ds << 2) | dr]
33
- received_data = @pcd.picc_transceive(buffer)
34
- raise UnexpectedDataError, 'Incorrect response' if received_data[0] != (0xD0 | @cid)
35
-
36
- # Set PCD baud rate
37
- @pcd.transceiver_baud_rate(:tx, dr)
38
- @pcd.transceiver_baud_rate(:rx, ds)
39
-
40
- @block_number = 0
41
- @max_frame_size = [64, @fsc].min
42
- @max_inf_size = @max_frame_size - 3 # PCB + CRC16
43
- @max_inf_size -= 1 if @support_cid
44
- @max_inf_size -= 1 if @support_nad
45
- @selected = true
46
- end
47
-
48
- # Send S(DESELECT)
49
- def deselect
50
- buffer = [CMD_DESELECT]
51
- received_data = @pcd.picc_transceive(buffer)
52
-
53
- if received_data[0] & 0xF7 == CMD_DESELECT
54
- @selected = false
55
- true
56
- else
57
- false
58
- end
59
- end
60
-
61
- # Wrapper for handling ISO protocol
62
- def transceive(send_data)
63
- # Split data according to max buffer size
64
- send_data = [send_data] unless send_data.is_a? Array
65
- chained_data = send_data.each_slice(@max_inf_size).to_a
66
-
67
- # Initialize I-block
68
- pcb = 0x02
69
-
70
- # Send chained data
71
- until chained_data.empty?
72
- pcb &= 0xEF # Reset chaining indicator
73
- pcb |= 0x10 if chained_data.size > 1 # Set chaining
74
- pcb |= @block_number # Set block number
75
- data = chained_data.shift
76
-
77
- buffer = [pcb] + data
78
-
79
- finished = false
80
- until finished
81
- received_data = handle_wtx(buffer)
82
-
83
- # Retreive response pcb from data
84
- r_pcb = received_data[0]
85
-
86
- # Received ACK
87
- if r_pcb & 0xF6 == 0xA2
88
- # If ACK matches current block number means success
89
- # Otherwise transmit it again
90
- if (pcb & 0x01) == (r_pcb & 0x01)
91
- finished = true
92
- end
93
- else
94
- finished = true
95
- end
96
- end
97
-
98
- @block_number ^= 1 # toggle block number for next frame
99
- end
100
-
101
- received_chained_data = [received_data]
102
-
103
- # Receive chained data
104
- while r_pcb & 0x10 != 0
105
- ack = 0xA2 | @block_number # Set block number
106
- received_data = handle_wtx([ack]) # Send ACK to receive next frame
107
-
108
- r_pcb = received_data[0]
109
-
110
- received_chained_data << received_data
111
-
112
- @block_number ^= 1 # toggle block number for next frame
113
- end
114
-
115
- # Collect INF from chain
116
- inf = []
117
- received_chained_data.each do |data|
118
- inf_position = 1
119
- inf_position += 1 if data[0] & 0x08 != 0 # CID present
120
- inf_position += 1 if data[0] & 0x04 != 0 # NAD present
121
-
122
- inf.concat(data[inf_position..-1])
123
- end
124
-
125
- inf
126
- end
127
-
128
- def resume_communication
129
- deselect rescue nil
130
- super
131
- end
132
-
133
- def halt
134
- deselect rescue nil
135
- super
136
- end
137
-
138
- private
139
-
140
- def convert_iso_baud_rate_to_pcd_setting(value)
141
- # ISO
142
- # 0b000: 106kBd, 0b001: 212kBd, 0b010: 424kBd, 0b100: 848kBd
143
- # MFRC522 register
144
- # 0b000: 106kBd, 0b001: 212kBd, 0b010: 424kBd, 0b011: 848kBd
145
- x = (value >> 2) & 0x01
146
- y = (value >> 1) & 0x01
147
- z = value & 0x01
148
-
149
- ((x | y) << 1) + (x | (~y & z))
150
- end
151
-
152
- # Gether information from ATS (Answer to Select)
153
- def process_ats(ats)
154
- position = 1
155
- t0 = ats[position] # Format byte
156
-
157
- fsci = t0 & 0x0F # PICC buffer size integer
158
- y1 = (t0 >> 4) & 0x07 # Optional frame(TA, TB, TC) indicator
159
- @fsc = FSCI_to_FSC.fetch(fsci) # Convert buffer size integer to bytes
160
- dr = 0 # default baud rate 106kBd
161
- ds = 0
162
-
163
- # Frame: TA
164
- if y1 & 0x01 != 0
165
- position += 1
166
- ta = ats[position]
167
-
168
- dr = ta & 0x07 # PCD to PICC baud rate
169
- ds = (ta >> 4) & 0x07 # PICC to PCD baud rate
170
-
171
- # Convert fastest baud rate to PCD setting
172
- # dr = convert_iso_baud_rate_to_pcd_setting(dr)
173
- # ds = convert_iso_baud_rate_to_pcd_setting(ds)
174
-
175
- # FIXME: baud rate fixed to 106kBd
176
- # until author can confirm negotiation works
177
- dr = 0
178
- ds = 0
179
- end
180
-
181
- # Frame: TB
182
- if y1 & 0x02 != 0
183
- position += 1
184
- tb = ats[position]
185
-
186
- fwi = (tb >> 4) & 0x0F # Frame wating integer
187
- sgfi = tb & 0x0F # Start-up frame guard integer
188
-
189
- # Convert integers to real time
190
- @fwt = (1 << fwi)
191
- sgft = (1 << sgfi)
192
-
193
- # Set frame waiting time
194
- @pcd.internal_timer(@fwt)
195
- end
196
-
197
- # Get info about CID or NAD
198
- if y1 & 0x04 != 0
199
- position += 1
200
- tc = ats[position]
201
-
202
- @support_cid = true if tc & 0x02 != 0
203
- @support_nad = true if tc & 0x01 != 0
204
- end
205
-
206
- # Start-up guard time
207
- sleep 0.000302 * sgft
208
-
209
- return dr, ds
210
- end
211
-
212
- def handle_wtx(data)
213
- 24.times do
214
- begin
215
- received_data = @pcd.picc_transceive(data)
216
- rescue CommunicationError => e
217
- raise e unless e.is_a? PICCTimeoutError
218
-
219
- # Try sending NAK when timeout
220
- nak = 0xB2 | @block_number
221
- data = [nak]
222
- next
223
- end
224
-
225
- pcb = received_data[0]
226
-
227
- # WTX detected
228
- if pcb & 0xF7 == 0xF2
229
- inf_position = (pcb & 0x08 != 0) ? 2 : 1
230
- wtxm = received_data[inf_position] & 0x3F
231
-
232
- # Set temporary timer
233
- @pcd.internal_timer(@fwt * wtxm)
234
-
235
- # Set WTX response
236
- data = [0xF2, wtxm]
237
- else
238
- # Set timer back to FWT
239
- @pcd.internal_timer(@fwt)
240
-
241
- return received_data
242
- end
243
- end
244
-
245
- raise PICCTimeoutError
246
- end
247
- end