ruby-nfc 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/lib/ruby-nfc.rb +4 -0
- data/lib/ruby-nfc/libnfc.rb +292 -0
- data/lib/ruby-nfc/nfc.rb +27 -0
- data/lib/ruby-nfc/reader.rb +85 -0
- data/lib/ruby-nfc/tags/isodep.rb +71 -0
- data/lib/ruby-nfc/tags/mifare/classic.rb +170 -0
- data/lib/ruby-nfc/tags/mifare/tag.rb +68 -0
- data/lib/ruby-nfc/tags/mifare/ultralight.rb +63 -0
- data/lib/ruby-nfc/tags/tag.rb +46 -0
- metadata +72 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YmQ5NDY5MzE2MTUwMDJlMjEwYjNkMjU0ZDkxZTU1YmRjYTAyODUyOA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
M2U3Mjc5ZTY3YWI0MTg3ZTYwZTZmYzExOThhYjNkM2Y5NzVhMWQzZQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NzMwMTFlZWVlZmUwNDIzOWEzYWQ5NzIwMDY5N2UzNTViNTYwYTQwYzI1OTQ1
|
10
|
+
ZmFkNzE4NjAzZDk4M2UxMzFjMTFlODliZmVkNjdhN2Y2NDk2ZWJhOWY4ZGE4
|
11
|
+
NDhlYTI2NjY2NGE1NzVhZDRmMTg4NTk2ZDNkMzQwY2UxOWYzZjE=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ZWNjMzUxZGQ2OGEyNDc5OGNlNzgwNzBhMTI5MWQ3MmU1NWRmZTc4ZjA2NWRl
|
14
|
+
M2YzMTc0NGZkMTM1ZDliNDVlNmRlZmU4NjA5NzY1ZmZkYTEzMjRjYmY4NGIy
|
15
|
+
ZWY5ZTc4NjJjYTZlN2QyYzJjMGRmODM2MWFlZDJmNjFhZmMyY2Q=
|
data/lib/ruby-nfc.rb
ADDED
@@ -0,0 +1,292 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
module LibNFC
|
4
|
+
extend FFI::Library
|
5
|
+
ffi_lib ['libnfc', 'libnfc.so', 'libnfc.so.5', '/usr/local/lib/libnfc.so']
|
6
|
+
|
7
|
+
PROPERTIES = [ # please refer to nfc-types.h for description
|
8
|
+
:NP_TIMEOUT_COMMAND,
|
9
|
+
:NP_TIMEOUT_ATR,
|
10
|
+
:NP_TIMEOUT_COM,
|
11
|
+
:NP_HANDLE_CRC,
|
12
|
+
:NP_HANDLE_PARITY,
|
13
|
+
:NP_ACTIVATE_FIELD,
|
14
|
+
:NP_ACTIVATE_CRYPTO1,
|
15
|
+
:NP_INFINITE_SELECT,
|
16
|
+
:NP_ACCEPT_INVALID_FRAMES,
|
17
|
+
:NP_ACCEPT_MULTIPLE_FRAMES,
|
18
|
+
:NP_AUTO_ISO14443_4,
|
19
|
+
:NP_EASY_FRAMING,
|
20
|
+
:NP_FORCE_ISO14443_A,
|
21
|
+
:NP_FORCE_ISO14443_B,
|
22
|
+
:NP_FORCE_SPEED_106
|
23
|
+
]
|
24
|
+
|
25
|
+
DEP_MODE = [
|
26
|
+
:NDM_UNDEFINED,
|
27
|
+
:NDM_PASSIVE,
|
28
|
+
:NDM_ACTIVE
|
29
|
+
]
|
30
|
+
|
31
|
+
BAUD_RATE = [
|
32
|
+
:NBR_UNDEFINED,
|
33
|
+
:NBR_106,
|
34
|
+
:NBR_212,
|
35
|
+
:NBR_424,
|
36
|
+
:NBR_847
|
37
|
+
]
|
38
|
+
|
39
|
+
MODULATION_TYPE = [
|
40
|
+
:NMT_ISO14443A, 1,
|
41
|
+
:NMT_JEWEL,
|
42
|
+
:NMT_ISO14443B,
|
43
|
+
:NMT_ISO14443BI, # pre-ISO14443B aka ISO/IEC 14443 B' or Type B'
|
44
|
+
:NMT_ISO14443B2SR, # ISO14443-2B ST SRx
|
45
|
+
:NMT_ISO14443B2CT, # ISO14443-2B ASK CTx
|
46
|
+
:NMT_FELICA,
|
47
|
+
:NMT_DEP
|
48
|
+
]
|
49
|
+
|
50
|
+
class ISO14443a < FFI::Struct
|
51
|
+
pack 1
|
52
|
+
layout(
|
53
|
+
:abtAtqa, [:uint8, 2],
|
54
|
+
:btSak, :uint8,
|
55
|
+
:szUidLen, :size_t,
|
56
|
+
:abtUid, [:uint8, 10],
|
57
|
+
:szAtsLen, :size_t,
|
58
|
+
:abtAts, [:uint8, 254]
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
class Felica < FFI::Struct
|
63
|
+
pack 1
|
64
|
+
layout(
|
65
|
+
:szLen, :size_t,
|
66
|
+
:btResCode, :uint8,
|
67
|
+
:abtId, [:uint8, 8],
|
68
|
+
:abtPad, [:uint8, 8],
|
69
|
+
:abtSysCode, [:uint8, 2]
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
class ISO14443b < FFI::Struct
|
74
|
+
pack 1
|
75
|
+
layout(
|
76
|
+
:abtPupi, [:uint8, 4],
|
77
|
+
:abtApplicationData, [:uint8, 4],
|
78
|
+
:abtProtocolInfo, [:uint8, 3],
|
79
|
+
:ui8CardIdentifier, :uint8
|
80
|
+
)
|
81
|
+
end
|
82
|
+
|
83
|
+
class ISO14443bi < FFI::Struct
|
84
|
+
pack 1
|
85
|
+
layout(
|
86
|
+
:abtDIV, [:uint8, 4],
|
87
|
+
:btVerLog, :uint8,
|
88
|
+
:btConfig, :uint8,
|
89
|
+
:szAtrLen, :size_t,
|
90
|
+
:abtAtr, [:uint8, 33]
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
94
|
+
class ISO14443b2sr < FFI::Struct
|
95
|
+
pack 1
|
96
|
+
layout(
|
97
|
+
:abtUID, [:uint8, 8]
|
98
|
+
)
|
99
|
+
end
|
100
|
+
|
101
|
+
class ISO14443b2ct < FFI::Struct
|
102
|
+
pack 1
|
103
|
+
layout(
|
104
|
+
:abtUID, [:uint8, 4],
|
105
|
+
:btProdCode, :uint8,
|
106
|
+
:btFabCode, :uint8
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
class Jewel < FFI::Struct
|
111
|
+
pack 1
|
112
|
+
layout(
|
113
|
+
:btSensRes, [:uint8, 2],
|
114
|
+
:btId, [:uint8, 4]
|
115
|
+
)
|
116
|
+
end
|
117
|
+
|
118
|
+
DepEnum = enum(DEP_MODE)
|
119
|
+
|
120
|
+
class DepInfo < FFI::Struct
|
121
|
+
pack 1
|
122
|
+
layout(
|
123
|
+
:abtNFCID3, [:uint8, 10],# uint8_t abtNFCID3[10];
|
124
|
+
:btDID, :uint8,# uint8_t btDID;
|
125
|
+
:btBS, :uint8,# uint8_t btBS;
|
126
|
+
:btBR, :uint8,# uint8_t btBR;
|
127
|
+
:btTO, :uint8,# uint8_t btTO;
|
128
|
+
:btPP, :uint8,# uint8_t btPP;
|
129
|
+
:abtGB, [:uint8, 48],# uint8_t abtGB[48];
|
130
|
+
:szGB, :size_t,# size_t szGB;
|
131
|
+
:ndm, DepEnum
|
132
|
+
)
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
class TargetInfo < FFI::Union
|
137
|
+
pack 1
|
138
|
+
layout(
|
139
|
+
:nai, ISO14443a,
|
140
|
+
:nfi, Felica,
|
141
|
+
:nbi, ISO14443b,
|
142
|
+
:nii, ISO14443bi,
|
143
|
+
:nsi, ISO14443b2sr,
|
144
|
+
:nci, ISO14443b2ct,
|
145
|
+
:nji, Jewel,
|
146
|
+
:ndi, DepInfo
|
147
|
+
)
|
148
|
+
end
|
149
|
+
|
150
|
+
ModulationType = enum(MODULATION_TYPE)
|
151
|
+
BaudRate = enum(BAUD_RATE)
|
152
|
+
|
153
|
+
class Modulation < FFI::Struct
|
154
|
+
pack 1
|
155
|
+
layout(
|
156
|
+
:nmt, ModulationType,
|
157
|
+
:nbr, BaudRate
|
158
|
+
)
|
159
|
+
end
|
160
|
+
|
161
|
+
class Target < FFI::Struct
|
162
|
+
pack 1
|
163
|
+
layout(
|
164
|
+
:nti, TargetInfo,
|
165
|
+
:nm, Modulation
|
166
|
+
)
|
167
|
+
|
168
|
+
def processed!
|
169
|
+
@processed = true
|
170
|
+
end
|
171
|
+
|
172
|
+
def processed?
|
173
|
+
defined?(@processed) && @processed
|
174
|
+
end
|
175
|
+
|
176
|
+
def sak
|
177
|
+
self[:nti][:nai][:btSak]
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
###
|
182
|
+
#Version checking before wrapping the rest of the library to prevent
|
183
|
+
#name method matching errors.
|
184
|
+
attach_function :nfc_version, [], :string
|
185
|
+
|
186
|
+
attach_function :nfc_perror, [ :pointer, :string], :void
|
187
|
+
attach_function :nfc_list_devices, [ :pointer, :pointer, :size_t], :size_t
|
188
|
+
|
189
|
+
attach_function :nfc_initiator_init, [:pointer], :int
|
190
|
+
|
191
|
+
attach_function :nfc_device_set_property_bool, [
|
192
|
+
:pointer, #nfc_device *pnd
|
193
|
+
enum(PROPERTIES), #property constant
|
194
|
+
:bool # value
|
195
|
+
], :int
|
196
|
+
|
197
|
+
attach_function :nfc_open, [ :pointer, :string ], :pointer
|
198
|
+
attach_function :nfc_close, [ :pointer ], :void
|
199
|
+
|
200
|
+
attach_function :iso14443a_crc, [
|
201
|
+
:pointer, #uint8_t *pbtData
|
202
|
+
:size_t, #size_t szLen
|
203
|
+
:pointer #uint8_t *pbtCrc
|
204
|
+
], :void
|
205
|
+
|
206
|
+
attach_function :nfc_initiator_poll_dep_target, [
|
207
|
+
:pointer, #nfc_device *pnd
|
208
|
+
enum(DEP_MODE), #nfc_dep_mode
|
209
|
+
enum(BAUD_RATE), #nfc_baud_rate
|
210
|
+
:pointer, #nfc_dep_info *pndiInitiator
|
211
|
+
:pointer, #nfc_target *pnt
|
212
|
+
:int #timout
|
213
|
+
], :int
|
214
|
+
|
215
|
+
attach_function :nfc_initiator_poll_target, [
|
216
|
+
:pointer, #device
|
217
|
+
:pointer, #modulations
|
218
|
+
:size_t, #modulations size
|
219
|
+
:uint8, #targets count
|
220
|
+
:uint8, #period
|
221
|
+
:pointer #targets
|
222
|
+
], :int
|
223
|
+
|
224
|
+
attach_function :nfc_initiator_list_passive_targets, [
|
225
|
+
:pointer, # device
|
226
|
+
Modulation.by_value, # modulation
|
227
|
+
:pointer, # array of targets
|
228
|
+
:size_t # maximum amount of targets
|
229
|
+
], :int
|
230
|
+
|
231
|
+
attach_function :nfc_initiator_select_passive_target, [
|
232
|
+
:pointer, #device
|
233
|
+
Modulation.by_value, # modulation
|
234
|
+
:pointer, # pbInitData (uid)
|
235
|
+
:size_t, # uid size
|
236
|
+
:pointer, #target
|
237
|
+
], :int
|
238
|
+
|
239
|
+
attach_function :nfc_initiator_transceive_bytes, [
|
240
|
+
:pointer, #device
|
241
|
+
:pointer, #byte array to transmit
|
242
|
+
:size_t, #number of bytes to transmit
|
243
|
+
:pointer, #response buffer
|
244
|
+
:size_t, #response buffer size
|
245
|
+
:int, #timeout
|
246
|
+
], :int
|
247
|
+
|
248
|
+
attach_function :nfc_initiator_poll_target, [
|
249
|
+
:pointer, # device
|
250
|
+
:pointer, # array of modulations
|
251
|
+
:size_t, #number of modulations
|
252
|
+
:uint8, #number of tags for each modulation,
|
253
|
+
:uint8, #period
|
254
|
+
:pointer #target
|
255
|
+
], :int
|
256
|
+
|
257
|
+
attach_function :str_nfc_target, [:pointer, :pointer, :bool], :int
|
258
|
+
|
259
|
+
attach_function :nfc_initiator_select_dep_target, [
|
260
|
+
:pointer,
|
261
|
+
enum(DEP_MODE),
|
262
|
+
enum(BAUD_RATE),
|
263
|
+
:pointer,
|
264
|
+
:pointer,
|
265
|
+
:int
|
266
|
+
], :int
|
267
|
+
|
268
|
+
attach_function :nfc_initiator_deselect_target, [:pointer], :int
|
269
|
+
attach_function :nfc_init, [ :pointer ], :pointer
|
270
|
+
attach_function :nfc_exit, [ :pointer ], :void
|
271
|
+
|
272
|
+
def self.crc(data)
|
273
|
+
data_ptr = FFI::MemoryPointer.new(:uint8, data.length)
|
274
|
+
data_ptr.put_bytes(0, data)
|
275
|
+
|
276
|
+
crc_ptr = FFI::MemoryPointer.new(:uint8, 2)
|
277
|
+
crc_ptr.put_bytes(0, "\x0\x0")
|
278
|
+
|
279
|
+
iso14443a_crc(data_ptr, data.length, crc_ptr)
|
280
|
+
crc_ptr.get_bytes(0, 2).to_s
|
281
|
+
end
|
282
|
+
|
283
|
+
def self.crc_hex(data)
|
284
|
+
crc(data).unpack('H*').pop
|
285
|
+
end
|
286
|
+
|
287
|
+
def self.debug_target(target)
|
288
|
+
str_pointer = FFI::MemoryPointer.new(:pointer)
|
289
|
+
str_nfc_target(str_pointer, target, true)
|
290
|
+
puts str_pointer.get_pointer(0).get_string(0)
|
291
|
+
end
|
292
|
+
end
|
data/lib/ruby-nfc/nfc.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
require 'logger'
|
3
|
+
require_relative './libnfc'
|
4
|
+
|
5
|
+
module NFC
|
6
|
+
class Error < ::Exception;end
|
7
|
+
|
8
|
+
@@context = nil
|
9
|
+
# TODO
|
10
|
+
@@logger = Logger.new(STDERR)
|
11
|
+
|
12
|
+
def self.version
|
13
|
+
LibNFC.nfc_version
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.context
|
17
|
+
unless @@context
|
18
|
+
ptr = FFI::MemoryPointer.new(:pointer, 1)
|
19
|
+
LibNFC.nfc_init(ptr)
|
20
|
+
@@context = ptr.read_pointer
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.logger
|
25
|
+
@@logger
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module NFC
|
2
|
+
class Reader
|
3
|
+
attr_reader :ptr
|
4
|
+
|
5
|
+
def initialize(device_name)
|
6
|
+
@name = device_name
|
7
|
+
@ptr = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def set_flag(name, value)
|
11
|
+
LibNFC.nfc_device_set_property_bool(@ptr, name, value)
|
12
|
+
end
|
13
|
+
|
14
|
+
def connect
|
15
|
+
@ptr ||= LibNFC.nfc_open(NFC.context, @name)
|
16
|
+
raise NFC::Error.new('Cant connect to ' << @name) if @ptr.null?
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns list of tags applied to reader
|
21
|
+
def discover(*card_types)
|
22
|
+
# TODO: по правильному здесь надо делать низкоуровневый
|
23
|
+
card_types.inject([]) do |tags, card_type|
|
24
|
+
raise NFC::Error.new('Wrong card type') unless card_type.respond_to? :discover
|
25
|
+
tags += card_type.discover(connect)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def poll(*card_types)
|
30
|
+
connect
|
31
|
+
|
32
|
+
LibNFC.nfc_initiator_init(@ptr) # we'll be initiator not a target
|
33
|
+
|
34
|
+
set_flag(:NP_ACTIVATE_FIELD, false)
|
35
|
+
set_flag(:NP_HANDLE_CRC, true)
|
36
|
+
set_flag(:NP_HANDLE_PARITY, true)
|
37
|
+
set_flag(:NP_AUTO_ISO14443_4, true)
|
38
|
+
set_flag(:NP_ACTIVATE_FIELD, true)
|
39
|
+
|
40
|
+
modulation = LibNFC::Modulation.new
|
41
|
+
modulation[:nmt] = :NMT_ISO14443A
|
42
|
+
modulation[:nbr] = :NBR_106
|
43
|
+
|
44
|
+
targets = FFI::MemoryPointer.new(:uchar, LibNFC::Target.size * 10)
|
45
|
+
|
46
|
+
loop do
|
47
|
+
res = LibNFC.nfc_initiator_list_passive_targets(@ptr, modulation,
|
48
|
+
targets, 10)
|
49
|
+
|
50
|
+
# iterate over all applied targets and iterate
|
51
|
+
0.upto(res - 1) do |i|
|
52
|
+
target = LibNFC::Target.new(targets + i * LibNFC::Target.size)
|
53
|
+
# iterate over requested card types for each target
|
54
|
+
# notice that some targets can match several types i.e.
|
55
|
+
# contactless credit cards (PayPass/payWave) with mifare chip
|
56
|
+
# on board
|
57
|
+
card_types.each do |card_type|
|
58
|
+
if card_type.match?(target)
|
59
|
+
tag = card_type.new(target, self)
|
60
|
+
yield tag
|
61
|
+
# if this tag was marked as processed - continue with next tag
|
62
|
+
break if target.processed?
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end # upto
|
66
|
+
end # loop
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_s
|
70
|
+
@name
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.all
|
74
|
+
ptr = FFI::MemoryPointer.new(:char, 1024 * 10)
|
75
|
+
len = LibNFC.nfc_list_devices(NFC.context, ptr, 10)
|
76
|
+
|
77
|
+
if len <= 0
|
78
|
+
raise NFC::Error, "No compatible NFC readers found"
|
79
|
+
else
|
80
|
+
names = ptr.get_bytes(0, 1024 * len).split("\x00").reject {|e| e.empty?}
|
81
|
+
names.map {|name| Reader.new(name)}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require_relative '../nfc'
|
2
|
+
require_relative './tag'
|
3
|
+
|
4
|
+
module IsoDep
|
5
|
+
ISO_14443_4_COMPATIBLE = 0x20
|
6
|
+
|
7
|
+
class Error < ::Exception; end
|
8
|
+
|
9
|
+
class Tag < NFC::Tag
|
10
|
+
|
11
|
+
def self.match?(target)
|
12
|
+
target[:nti][:nai][:btSak] & IsoDep::ISO_14443_4_COMPATIBLE > 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def select(aid = nil, &block)
|
16
|
+
@reader.set_flag(:NP_AUTO_ISO14443_4, true)
|
17
|
+
|
18
|
+
modulation = LibNFC::Modulation.new
|
19
|
+
modulation[:nmt] = :NMT_ISO14443A
|
20
|
+
modulation[:nbr] = :NBR_106
|
21
|
+
|
22
|
+
nai_ptr = @target[:nti][:nai].pointer
|
23
|
+
|
24
|
+
# abt + sak + szUidLen offset
|
25
|
+
uid_ptr = nai_ptr + FFI.type_size(:uint8) * 3 + FFI.type_size(:size_t)
|
26
|
+
|
27
|
+
res = LibNFC.nfc_initiator_select_passive_target(
|
28
|
+
@reader.ptr,
|
29
|
+
modulation,
|
30
|
+
uid_ptr,
|
31
|
+
uid.length,
|
32
|
+
@target.pointer
|
33
|
+
)
|
34
|
+
|
35
|
+
if res > 0
|
36
|
+
# trying to select applet if applet identifier was given
|
37
|
+
if aid
|
38
|
+
sw = send_apdu("\x00\xA4\x04\x00#{aid.size.chr}#{aid}")
|
39
|
+
raise IsoDep::Error, "Application not found: #{aid.unpack('H*').pop}" unless "\x90\x00" == sw
|
40
|
+
end
|
41
|
+
|
42
|
+
super(&block)
|
43
|
+
else
|
44
|
+
raise IsoDep::Error, "Can't select tag: #{res}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def deselect
|
49
|
+
0 == LibNFC.nfc_initiator_deselect_target(@reader.ptr)
|
50
|
+
end
|
51
|
+
|
52
|
+
def send_apdu(apdu)
|
53
|
+
cmd = apdu
|
54
|
+
cmd.force_encoding('ASCII-8BIT')
|
55
|
+
command_buffer = FFI::MemoryPointer.new(:uint8, cmd.length)
|
56
|
+
command_buffer.write_string_length(cmd, cmd.length)
|
57
|
+
|
58
|
+
response_buffer = FFI::MemoryPointer.new(:uint8, 254)
|
59
|
+
|
60
|
+
res_len = LibNFC.nfc_initiator_transceive_bytes(@reader.ptr,
|
61
|
+
command_buffer, cmd.length, response_buffer, 254, 0)
|
62
|
+
|
63
|
+
raise IsoDep::Error, "APDU sending failed: #{res_len}" if res_len < 0
|
64
|
+
|
65
|
+
response_buffer.get_bytes(0, res_len).to_s
|
66
|
+
end
|
67
|
+
|
68
|
+
alias :'<<' :send_apdu
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require_relative './tag'
|
2
|
+
|
3
|
+
module Mifare
|
4
|
+
# Adding some Classic related stuff to Mifare module
|
5
|
+
|
6
|
+
# tag
|
7
|
+
attach_function :mifare_classic_connect, [:pointer], :int
|
8
|
+
# tag
|
9
|
+
attach_function :mifare_classic_disconnect, [:pointer], :int
|
10
|
+
# tag, blocknumber, key
|
11
|
+
attach_function :mifare_classic_authenticate, [:pointer, :uchar, :pointer, enum(:key_a, :key_b)], :int
|
12
|
+
|
13
|
+
# tag, blocknumber, block data
|
14
|
+
attach_function :mifare_classic_read, [:pointer, :uchar, :pointer], :int
|
15
|
+
|
16
|
+
# tag, blocknumber, block data
|
17
|
+
attach_function :mifare_classic_write, [:pointer, :uchar, :pointer], :int
|
18
|
+
|
19
|
+
#mifare_classic_init_value (MifareTag tag, const MifareClassicBlockNumber block, const int32_t value, const MifareClassicBlockNumber adr)
|
20
|
+
# tag, blocknumber, value, addr
|
21
|
+
attach_function :mifare_classic_init_value, [:pointer, :uchar, :int32, :uchar], :int
|
22
|
+
|
23
|
+
#mifare_classic_increment (MifareTag tag, const MifareClassicBlockNumber block, const uint32_t amount)
|
24
|
+
attach_function :mifare_classic_increment, [:pointer, :uchar, :int32], :int
|
25
|
+
|
26
|
+
#mifare_classic_decrement (MifareTag tag, const MifareClassicBlockNumber block, const uint32_t amount)
|
27
|
+
attach_function :mifare_classic_decrement, [:pointer, :uchar, :int32], :int
|
28
|
+
|
29
|
+
#mifare_classic_read_value (MifareTag tag, const MifareClassicBlockNumber block, int32_t *value, MifareClassicBlockNumber *adr)
|
30
|
+
attach_function :mifare_classic_read_value, [:pointer, :uchar, :pointer, :pointer], :int
|
31
|
+
|
32
|
+
#mifare_classic_transfer (MifareTag tag, const MifareClassicBlockNumber block)
|
33
|
+
attach_function :mifare_classic_transfer, [:pointer, :uchar], :int
|
34
|
+
|
35
|
+
module Classic
|
36
|
+
class Tag < Mifare::Tag
|
37
|
+
def initialize(target, reader)
|
38
|
+
super(target, reader)
|
39
|
+
|
40
|
+
@auth_block = nil #last authenticated block
|
41
|
+
end
|
42
|
+
|
43
|
+
def select(&block)
|
44
|
+
@reader.set_flag(:NP_AUTO_ISO14443_4, false)
|
45
|
+
|
46
|
+
res = Mifare.mifare_classic_connect(@pointer)
|
47
|
+
if 0 == res
|
48
|
+
super
|
49
|
+
else
|
50
|
+
raise Mifare::Error, "Can't select tag: #{res}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def deselect
|
55
|
+
Mifare.mifare_classic_disconnect(@pointer)
|
56
|
+
super
|
57
|
+
end
|
58
|
+
|
59
|
+
# keytype can be :key_a or :key_b
|
60
|
+
# key - hexadecimal string key representation like "FFFFFFFFFFFF"
|
61
|
+
def auth(block_num, key_type, key)
|
62
|
+
raise Mifare::Error.new('Wrong key type') unless [:key_a, :key_b].include? key_type
|
63
|
+
raise Mifare::Error.new('Wrong key length') unless [6, 12].include? key.size
|
64
|
+
|
65
|
+
key_ptr = FFI::MemoryPointer.new(:uchar, 6)
|
66
|
+
key_ptr.put_bytes(0, 6 == key.size ? key : [key].pack('H*'))
|
67
|
+
|
68
|
+
res = Mifare.mifare_classic_authenticate(@pointer, block_num, key_ptr,
|
69
|
+
key_type)
|
70
|
+
if 0 == res
|
71
|
+
@auth_block = block_num
|
72
|
+
else
|
73
|
+
raise Mifare::Error.new("Can't autenticate to block 0x%02x" % block_num)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# block number to read
|
78
|
+
def read(block_num = nil)
|
79
|
+
block_num ||= @auth_block
|
80
|
+
raise Mifare::Error.new('Not authenticated') unless block_num
|
81
|
+
|
82
|
+
data_ptr = FFI::MemoryPointer.new(:uchar, 16)
|
83
|
+
res = Mifare.mifare_classic_read(@pointer, block_num, data_ptr)
|
84
|
+
|
85
|
+
raise Mifare::Error.new("Can't read block 0x%02x" % block_num) unless 0 == res
|
86
|
+
|
87
|
+
data_ptr.get_bytes(0, 16).force_encoding('ASCII-8BIT')
|
88
|
+
end
|
89
|
+
|
90
|
+
# @data - 16 bytes represented by hexadecimal string
|
91
|
+
# @block_num - number of block to write to
|
92
|
+
def write(data, block_num = nil)
|
93
|
+
raise Mifare::Error.new('Wrong data given') if data !~ /^[\da-f]{32}$/i
|
94
|
+
|
95
|
+
block_num ||= @auth_block
|
96
|
+
raise Mifare::Error.new('Not authenticated') unless block_num
|
97
|
+
|
98
|
+
data_ptr = FFI::MemoryPointer.new(:uchar, 16)
|
99
|
+
data_ptr.put_bytes(0, [data].pack('H*'))
|
100
|
+
|
101
|
+
res = Mifare.mifare_classic_write(@pointer, block_num, data_ptr)
|
102
|
+
(0 == res) || raise(Mifare::Error.new("Can't write block 0x%02x" % block_num))
|
103
|
+
end
|
104
|
+
|
105
|
+
# Create value block structure and write it to block
|
106
|
+
def init_value(value, addr = nil, block_num = nil)
|
107
|
+
block_num ||= @auth_block
|
108
|
+
raise Mifare::Error.new('Not authenticated') unless block_num
|
109
|
+
|
110
|
+
addr ||= 0
|
111
|
+
|
112
|
+
res = Mifare.mifare_classic_init_value(@pointer, block_num, value, addr)
|
113
|
+
(0 == res) || raise(Mifare::Error.new("Can't init value block 0x%02x" % block_num))
|
114
|
+
end
|
115
|
+
|
116
|
+
# returns only value part of value block
|
117
|
+
def value(block_num = nil)
|
118
|
+
v, _ = value_with_addr(block_num)
|
119
|
+
v
|
120
|
+
end
|
121
|
+
|
122
|
+
# returns value and addr
|
123
|
+
def value_with_addr(block_num = nil)
|
124
|
+
block_num ||= @auth_block
|
125
|
+
raise Mifare::Error.new('Not authenticated') unless block_num
|
126
|
+
|
127
|
+
value_ptr = FFI::MemoryPointer.new(:int32, 1)
|
128
|
+
addr_ptr = FFI::MemoryPointer.new(:uchar, 1)
|
129
|
+
res = Mifare.mifare_classic_read_value(@pointer, block_num, value_ptr, addr_ptr)
|
130
|
+
raise Mifare::Error.new("Can't read value block 0x%02x" % block_num) unless 0 == res
|
131
|
+
|
132
|
+
[value_ptr.get_int32(0), addr_ptr.get_uchar(0)]
|
133
|
+
end
|
134
|
+
|
135
|
+
# Mifare classic increment
|
136
|
+
def inc(amount = 1, block_num = nil)
|
137
|
+
block_num ||= @auth_block
|
138
|
+
raise Mifare::Error.new('Not authenticated') unless block_num
|
139
|
+
|
140
|
+
res = Mifare.mifare_classic_increment(@pointer, block_num, amount)
|
141
|
+
(0 == res) || raise(Mifare::Error.new("Can't increment block 0x%02x" % block_num))
|
142
|
+
end
|
143
|
+
|
144
|
+
# Mifare classic decrement
|
145
|
+
def dec(amount = 1, block_num = nil)
|
146
|
+
block_num ||= @auth_block
|
147
|
+
raise Mifare::Error.new('Not authenticated') unless block_num
|
148
|
+
|
149
|
+
res = Mifare.mifare_classic_decrement(@pointer, block_num, amount)
|
150
|
+
(0 == res) || raise(Mifare::Error.new("Can't decrement block 0x%02x" % block_num))
|
151
|
+
end
|
152
|
+
|
153
|
+
def transfer(block_num = nil)
|
154
|
+
block_num ||= @auth_block
|
155
|
+
raise Mifare::Error.new('Not authenticated') unless block_num
|
156
|
+
|
157
|
+
res = Mifare.mifare_classic_transfer(@pointer, block_num)
|
158
|
+
(0 == res) || raise(Mifare::Error.new("Can't transfer to block 0x%02x" % block_num))
|
159
|
+
end
|
160
|
+
|
161
|
+
# Check's if our tag class is able to handle this LibNFC::Target
|
162
|
+
def self.match?(target)
|
163
|
+
keys = [:CLASSIC_1K, :CLASSIC_1K_EMULATED, :CLASSIC_1K_ANDROID,
|
164
|
+
:INFINEON_CLASSIC_1k, :CLASSIC_4K, :CLASSIC_4K_EMULATED]
|
165
|
+
|
166
|
+
Mifare::SAKS.values_at(*keys).include? target.sak
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
require_relative '../../nfc'
|
3
|
+
require_relative '../tag'
|
4
|
+
|
5
|
+
module Mifare
|
6
|
+
class Error < Exception; end
|
7
|
+
|
8
|
+
extend FFI::Library
|
9
|
+
|
10
|
+
ffi_lib ['libfreefare', 'libfreefare.so', '/usr/local/lib/libfreefare.so']
|
11
|
+
|
12
|
+
# List of known mifare card's SAKs
|
13
|
+
SAKS = {
|
14
|
+
CLASSIC_1K: 0x08,
|
15
|
+
CLASSIC_1K_EMULATED: 0x28,
|
16
|
+
CLASSIC_1K_ANDROID: 0x68, # Android with mifare emulated by USIM or eSE
|
17
|
+
INFINEON_CLASSIC_1k: 0x88,
|
18
|
+
CLASSIC_4K: 0x18,
|
19
|
+
CLASSIC_4K_EMULATED: 0x38,
|
20
|
+
ULTRALIGHT: 0x00
|
21
|
+
}
|
22
|
+
|
23
|
+
# common freefare functions prototypes
|
24
|
+
attach_function :freefare_tag_new, [:pointer, LibNFC::ISO14443a.by_value], :pointer
|
25
|
+
attach_function :freefare_free_tag, [:pointer], :void
|
26
|
+
|
27
|
+
# tag
|
28
|
+
attach_function :freefare_get_tag_uid, [:pointer], :string
|
29
|
+
# tag
|
30
|
+
attach_function :freefare_get_tag_friendly_name, [:pointer], :string
|
31
|
+
|
32
|
+
# device, pointer to array of pointers to tags
|
33
|
+
# and we need to go deeper :)
|
34
|
+
attach_function :freefare_get_tags, [:pointer], :pointer
|
35
|
+
|
36
|
+
class Tag < NFC::Tag
|
37
|
+
def initialize(target, reader)
|
38
|
+
super(target, reader)
|
39
|
+
|
40
|
+
@pointer = Mifare.freefare_tag_new(reader.ptr, target[:nti][:nai])
|
41
|
+
|
42
|
+
raise Mifare::Error, "Unknown mifare tag" if @pointer.null?
|
43
|
+
end
|
44
|
+
|
45
|
+
def name
|
46
|
+
Mifare.freefare_get_tag_friendly_name(@pointer)
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_s
|
50
|
+
"#{uid_hex} #{name} SAK: 0x#{@target.sak.to_s(16)}"
|
51
|
+
end
|
52
|
+
|
53
|
+
# frees memory allocated for mifare tag
|
54
|
+
def deselect
|
55
|
+
Mifare.freefare_free_tag(@pointer)
|
56
|
+
end
|
57
|
+
|
58
|
+
def sak
|
59
|
+
target.sak
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.match?(target)
|
63
|
+
SAKS.values.include?(target[:nti][:nai][:btSak])
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require_relative './tag'
|
2
|
+
|
3
|
+
module Mifare
|
4
|
+
# tag
|
5
|
+
attach_function :mifare_ultralight_connect, [:pointer], :int
|
6
|
+
# tag
|
7
|
+
attach_function :mifare_ultralight_disconnect, [:pointer], :int
|
8
|
+
|
9
|
+
# tag, page number, page data
|
10
|
+
attach_function :mifare_ultralight_read, [:pointer, :uchar, :pointer], :int
|
11
|
+
|
12
|
+
# tag, blocknumber, block data
|
13
|
+
attach_function :mifare_ultralight_write, [:pointer, :uchar, :pointer], :int
|
14
|
+
|
15
|
+
module Ultralight
|
16
|
+
class Tag < Mifare::Tag
|
17
|
+
def select(&block)
|
18
|
+
@reader.set_flag(:NP_AUTO_ISO14443_4, false)
|
19
|
+
|
20
|
+
res = Mifare.mifare_ultralight_connect(@pointer)
|
21
|
+
if 0 == res
|
22
|
+
super
|
23
|
+
else
|
24
|
+
raise Mifare::Error, "Can't select tag: #{res}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def deselect
|
29
|
+
Mifare.mifare_ultralight_disconnect(@pointer)
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
33
|
+
# block number to read
|
34
|
+
def read(page_num = nil)
|
35
|
+
data_ptr = FFI::MemoryPointer.new(:uchar, 4)
|
36
|
+
res = Mifare.mifare_ultralight_read(@pointer, page_num, data_ptr)
|
37
|
+
|
38
|
+
raise Mifare::Error, ("Can't read page 0x%02x" % page_num) unless 0 == res
|
39
|
+
|
40
|
+
data_ptr.get_bytes(0, 4).force_encoding('ASCII-8BIT')
|
41
|
+
end
|
42
|
+
|
43
|
+
# @data - 16 bytes represented by hexadecimal string
|
44
|
+
# @block_num - number of block to write to
|
45
|
+
def write(data, page_num = nil)
|
46
|
+
raise Mifare::Error, "Wrong data given" if data !~ /^[\da-f]{8}$/i
|
47
|
+
|
48
|
+
data_ptr = FFI::MemoryPointer.new(:uchar, 4)
|
49
|
+
data_ptr.put_bytes(0, [data].pack('H*'))
|
50
|
+
|
51
|
+
res = Mifare.mifare_classic_write(@pointer, block_num, data_ptr)
|
52
|
+
raise Mifare::Error, ("Can't write page 0x%02x" % page_num) unless 0 == res
|
53
|
+
|
54
|
+
res
|
55
|
+
end
|
56
|
+
|
57
|
+
# Check's if our tag class is able to handle this LibNFC::Target
|
58
|
+
def self.match?(target)
|
59
|
+
target.sak == Mifare::SAKS[:ULTRALIGHT]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module NFC
|
2
|
+
class Tag
|
3
|
+
def initialize(target, reader)
|
4
|
+
@target = target
|
5
|
+
@reader = reader
|
6
|
+
@processed = false
|
7
|
+
end
|
8
|
+
|
9
|
+
def select(&block)
|
10
|
+
if block_given?
|
11
|
+
begin
|
12
|
+
self.instance_eval(&block)
|
13
|
+
ensure
|
14
|
+
deselect
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def processed!
|
20
|
+
@target.processed!
|
21
|
+
end
|
22
|
+
|
23
|
+
def processed?
|
24
|
+
@target.processed?
|
25
|
+
end
|
26
|
+
|
27
|
+
def uid
|
28
|
+
uid_size = @target[:nti][:nai][:szUidLen]
|
29
|
+
@target[:nti][:nai][:abtUid].to_s[0...uid_size]
|
30
|
+
end
|
31
|
+
|
32
|
+
def uid_hex
|
33
|
+
uid.unpack('H*').pop
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
uid_hex
|
38
|
+
end
|
39
|
+
|
40
|
+
# Matches any NFC tag
|
41
|
+
def self.match?(target)
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-nfc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Maxim Chechel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-10-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ffi
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: ! " \tThis gem is built on top of libnfc and libfreefare using ffi and
|
28
|
+
supports:\n\t\t * Reading and writing Mifare Classic and Ultralight tags\n\t\t *
|
29
|
+
Android HCE / Blackberry VTE emulated tags\n\t\t * Dual-interface smart cards like
|
30
|
+
MasterCard PayPass or Visa payWave\n"
|
31
|
+
email: maximchick@gmail.com
|
32
|
+
executables: []
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
files:
|
36
|
+
- ./lib/ruby-nfc.rb
|
37
|
+
- ./lib/ruby-nfc/libnfc.rb
|
38
|
+
- ./lib/ruby-nfc/nfc.rb
|
39
|
+
- ./lib/ruby-nfc/reader.rb
|
40
|
+
- ./lib/ruby-nfc/tags/isodep.rb
|
41
|
+
- ./lib/ruby-nfc/tags/mifare/classic.rb
|
42
|
+
- ./lib/ruby-nfc/tags/mifare/tag.rb
|
43
|
+
- ./lib/ruby-nfc/tags/mifare/ultralight.rb
|
44
|
+
- ./lib/ruby-nfc/tags/tag.rb
|
45
|
+
homepage: https://github.com/maximchick/ruby-nfc
|
46
|
+
licenses:
|
47
|
+
- MIT
|
48
|
+
metadata: {}
|
49
|
+
post_install_message: ! "Don't forget to install libnfc and libfreefare\nsee installation
|
50
|
+
instructions here: \n\t https://github.com/maximchick/ruby-nfc"
|
51
|
+
rdoc_options: []
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ! '>='
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
requirements:
|
65
|
+
- libnfc, v1.7.x
|
66
|
+
- libfreefare
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 2.2.2
|
69
|
+
signing_key:
|
70
|
+
specification_version: 4
|
71
|
+
summary: Provides NFC functionality for Ruby
|
72
|
+
test_files: []
|