ruby-nfc 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []