ruby-nfc 1.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.
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,4 @@
1
+ require_relative './ruby-nfc/reader'
2
+ require_relative './ruby-nfc/tags/mifare/classic'
3
+ require_relative './ruby-nfc/tags/mifare/ultralight'
4
+ require_relative './ruby-nfc/tags/isodep'
@@ -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
@@ -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: []