ctapi 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +4 -0
- data/GPL +340 -0
- data/README.en +31 -0
- data/Rakefile +100 -0
- data/VERSION +1 -0
- data/examples/cardinfo.rb +26 -0
- data/examples/ctsh.rb +10 -0
- data/ext/ctapicore.c +443 -0
- data/ext/extconf.rb +8 -0
- data/install.rb +21 -0
- data/lib/ctapi.rb +563 -0
- data/make_doc.rb +6 -0
- data/tests/test.rb +55 -0
- metadata +53 -0
data/ext/extconf.rb
ADDED
data/install.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rbconfig'
|
4
|
+
require 'fileutils'
|
5
|
+
include FileUtils::Verbose
|
6
|
+
|
7
|
+
include Config
|
8
|
+
|
9
|
+
libraryname = ARGV.shift or fail "A ctapi library name is required!"
|
10
|
+
Dir.chdir('ext') do
|
11
|
+
File.exist?('Makefile') and system("make clean")
|
12
|
+
system("ruby extconf.rb #{libraryname}") or
|
13
|
+
fail "Could not make makefile for library!"
|
14
|
+
system("make") or fail "Could not make library"
|
15
|
+
end
|
16
|
+
|
17
|
+
libdir = CONFIG["sitelibdir"]
|
18
|
+
for file in %w[ext/ctapicore.so lib/ctapi.rb]
|
19
|
+
install(file, libdir)
|
20
|
+
end
|
21
|
+
# vim: set et sw=2 ts=2:
|
data/lib/ctapi.rb
ADDED
@@ -0,0 +1,563 @@
|
|
1
|
+
# There are two possible ways to use this library:
|
2
|
+
#
|
3
|
+
# 1. You can require 'ctapicore' and directly use the ct_init, ct_data and
|
4
|
+
# ct_close methods . That will give you access to the very simple
|
5
|
+
# C-Interface in Ruby, but you have to send APDU commands and receive and
|
6
|
+
# interpret APDU Responses yourself:
|
7
|
+
#
|
8
|
+
# require 'ctapicore'
|
9
|
+
# include CTAPICore
|
10
|
+
# ct_init(PORT_COM1, 0)
|
11
|
+
# response = ct_data(0, CT, HOST, "\x12\x34\x56")
|
12
|
+
# p response
|
13
|
+
# ct_close(0)
|
14
|
+
#
|
15
|
+
# 2. You can require 'ctapi' and the module CTAPI has some nice abstractions to
|
16
|
+
# deal with cardterminals, cards and APDUs, which are much more the Ruby
|
17
|
+
# Way:
|
18
|
+
#
|
19
|
+
# require 'ctapi'
|
20
|
+
# include CTAPI
|
21
|
+
# Cardterminal.open(PORT_COM1) do |ct|
|
22
|
+
# puts "Cardterminal Manufacturer: " + ct.manufacturer.to_s
|
23
|
+
# if ct.card_inserted?
|
24
|
+
# card = ct.card
|
25
|
+
# puts "My Cardterminal object: #{ct.inspect}"
|
26
|
+
# puts "Current card status is: #{ct.card_status}"
|
27
|
+
# puts "Answer to Reset is #{card.atr}."
|
28
|
+
# puts "ATR ok? #{card.atr_ok?}."
|
29
|
+
# puts "Memory size of this card is #{card.memory_size} bytes" +
|
30
|
+
# (#{card.memory_blocks} blocks x #{card.memory_bits} bit)."
|
31
|
+
# puts "Structure of this card is #{card.structure}."
|
32
|
+
# puts "Supported protocol type of this card is #{card.protocol}."
|
33
|
+
# puts "Trying to read(0, 16):"
|
34
|
+
# data = ct.read(0, 16)
|
35
|
+
# puts "Have read #{data.size} bytes:"
|
36
|
+
# p data
|
37
|
+
# else
|
38
|
+
# puts "Please insert a card into your cardterminal!"
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
module CTAPI
|
43
|
+
|
44
|
+
require 'ctapicore'
|
45
|
+
include CTAPICore
|
46
|
+
|
47
|
+
# Represents a cardterminal. Mixes in CTAPI methods and constants.
|
48
|
+
class Cardterminal
|
49
|
+
|
50
|
+
include CTAPI
|
51
|
+
@@cardterminals = {}
|
52
|
+
|
53
|
+
# Creates a new Cardterminal object for this interface and initializes
|
54
|
+
# the card terminal that is connected. If no cardterminal number is
|
55
|
+
# given, a number (that is not currently in use) is assigned to this
|
56
|
+
# object. Otherwise this object is forced to use the given card
|
57
|
+
# terminal number. BTW: number has to be >= 0.
|
58
|
+
def initialize(interface, number = nil)
|
59
|
+
Thread.critical = true
|
60
|
+
@interface = interface
|
61
|
+
@slot = ICC1
|
62
|
+
@chunk_size = 255
|
63
|
+
if number
|
64
|
+
@number = number
|
65
|
+
else
|
66
|
+
@number = 0
|
67
|
+
while @@cardterminals.key?(@number)
|
68
|
+
@number += 1
|
69
|
+
end
|
70
|
+
end
|
71
|
+
ct_init(@number, interface)
|
72
|
+
@@cardterminals[@number] = true
|
73
|
+
@manufacturer = get_manufacturer
|
74
|
+
reset
|
75
|
+
select_file # master file
|
76
|
+
ensure
|
77
|
+
Thread.critical = false
|
78
|
+
end
|
79
|
+
|
80
|
+
class << self
|
81
|
+
alias init new
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns a Manufacturer object with information about the manufacturer
|
85
|
+
# of this cardterminal. Could return nil if the response was not
|
86
|
+
# successful.
|
87
|
+
def get_manufacturer
|
88
|
+
get_manufacturer_status = [ CTBCS_CLA, CTBCS_INS_STATUS,
|
89
|
+
CTBCS_P1_CT_KERNEL, CTBCS_P2_STATUS_MANUFACTURER, 0 ]
|
90
|
+
response = data(CT, HOST, get_manufacturer_status)
|
91
|
+
return nil unless response.successful?
|
92
|
+
Manufacturer.new(response)
|
93
|
+
end
|
94
|
+
private :get_manufacturer
|
95
|
+
|
96
|
+
# The cardterminal number assigned to this object.
|
97
|
+
attr_reader :number
|
98
|
+
|
99
|
+
# The interface this cardterminal is connected to. This is
|
100
|
+
# a value of PORT_COM1, PORT_COM2, PORT_COM3, PORT_COM4, PORT_LPT1,
|
101
|
+
# PORT_LPT2, PORT_Modem or PORT_Printer.
|
102
|
+
attr_reader :interface
|
103
|
+
|
104
|
+
# The slot of the card terminal where the card is inserted. This
|
105
|
+
# is a value in the range of ICC1, ICC2,..., ICC14. Default is
|
106
|
+
# ICC1.
|
107
|
+
attr_accessor :slot
|
108
|
+
|
109
|
+
# The maximum size of the chunks of data that are read from/written to
|
110
|
+
# the card. Can be set by calling the chunk_size= method.
|
111
|
+
attr_reader :chunk_size
|
112
|
+
|
113
|
+
# Sets the size of the chunks of data that are read from/written to
|
114
|
+
# the card to size: (0 < size <= 255).
|
115
|
+
def chunk_size=(size)
|
116
|
+
raise ArgumentError.new("size must be > 0") unless size > 0
|
117
|
+
raise ArgumentError.new("size must be <= 255") unless size <= 255
|
118
|
+
@chunk_size = size
|
119
|
+
end
|
120
|
+
|
121
|
+
# The Manufacturer object for this cardterminal.
|
122
|
+
attr_reader :manufacturer
|
123
|
+
|
124
|
+
# Card object of the inserted chipcard.
|
125
|
+
attr_reader :card
|
126
|
+
|
127
|
+
# Takes a block and yields to an initialized Cardterminal object.
|
128
|
+
# After the block has been called, the cardterminal is closed
|
129
|
+
# again.
|
130
|
+
def self.open(interface, number = nil)
|
131
|
+
cardterminal = self.new(interface, cardterminal)
|
132
|
+
yield cardterminal
|
133
|
+
ensure
|
134
|
+
cardterminal.close if cardterminal
|
135
|
+
end
|
136
|
+
|
137
|
+
# Sends a sequence of commands to the card or the cardterminal
|
138
|
+
# (depending on destination/dad and source address/sad) and returns the
|
139
|
+
# response (or responses) to the calling program. A command can be
|
140
|
+
# given as an array of bytes [ 0x12, 0x23 ] or as a string of the form
|
141
|
+
# '12:23:a4' or '12 23 a4' or as a Command object.
|
142
|
+
def data(dad, sad, *commands)
|
143
|
+
responses = []
|
144
|
+
commands.each do |command|
|
145
|
+
command =
|
146
|
+
case command
|
147
|
+
when String then Command.from_string(command)
|
148
|
+
when Array then Command.from_array(command)
|
149
|
+
else command
|
150
|
+
end
|
151
|
+
$DEBUG and debug(2, command)
|
152
|
+
data = ct_data(@number, dad, sad, command.data)
|
153
|
+
response = Response.new(data)
|
154
|
+
$DEBUG and debug(1, response)
|
155
|
+
responses << response
|
156
|
+
end
|
157
|
+
return *responses
|
158
|
+
end
|
159
|
+
|
160
|
+
# Terminates the communication with the cardterminal associated with
|
161
|
+
# this object and releases the assigned cardterminal number.
|
162
|
+
def close
|
163
|
+
Thread.critical = true
|
164
|
+
ct_close(@number)
|
165
|
+
@@cardterminals.delete @number
|
166
|
+
ensure
|
167
|
+
Thread.critical = false
|
168
|
+
end
|
169
|
+
|
170
|
+
# Sends the select file byte sequence to the card with a default value
|
171
|
+
# for the master file. Returns true if the response was successful,
|
172
|
+
# false otherwise.
|
173
|
+
def select_file(fid = [ 0x3f, 0 ])
|
174
|
+
select_file = [ 0, 0xa4, 0, 0, 0x02, *fid ]
|
175
|
+
response = data(@slot, HOST, select_file)
|
176
|
+
response.successful?
|
177
|
+
end
|
178
|
+
|
179
|
+
def read_chunk(address, size)
|
180
|
+
unless address <= 65535
|
181
|
+
raise ArgumentError.new("address must be <= 65535")
|
182
|
+
end
|
183
|
+
unless size <= 255
|
184
|
+
raise ArgumentError.new("size must be <= 255")
|
185
|
+
end
|
186
|
+
read_file = [ 0, 0xb0, address >> 8, address & 0xff, size ]
|
187
|
+
response = data(@slot, HOST, read_file)
|
188
|
+
response.successful? or return
|
189
|
+
data = response.data
|
190
|
+
data.slice!(-2, 2)
|
191
|
+
data
|
192
|
+
end
|
193
|
+
private :read_chunk
|
194
|
+
|
195
|
+
# Attempts to read data of length size starting at address. If
|
196
|
+
# size is nil, an attempt is made to read the whole card memory.
|
197
|
+
def read(address = 0, size = nil)
|
198
|
+
if size == nil
|
199
|
+
if @card
|
200
|
+
size = @card.memory_size - address
|
201
|
+
else
|
202
|
+
size = chunk_size
|
203
|
+
end
|
204
|
+
elsif @card and address + size > @card.memory_size
|
205
|
+
size = @card.memory_size - address
|
206
|
+
end
|
207
|
+
return if size <= 0
|
208
|
+
data = ''
|
209
|
+
caught = catch(:break) do
|
210
|
+
while size >= chunk_size
|
211
|
+
d = read_chunk(address, chunk_size)
|
212
|
+
if d
|
213
|
+
data << d
|
214
|
+
address += chunk_size
|
215
|
+
size -= chunk_size
|
216
|
+
else
|
217
|
+
break :break
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
if caught != :break and size > 0
|
222
|
+
d = read_chunk(address, size) and data << d
|
223
|
+
end
|
224
|
+
data
|
225
|
+
end
|
226
|
+
|
227
|
+
def write_chunk(address, data)
|
228
|
+
unless address <= 65535
|
229
|
+
raise ArgumentError.new("address must be <= 65535")
|
230
|
+
end
|
231
|
+
unless data.size <= 255
|
232
|
+
raise ArgumentError.new("size of data must be <= 255")
|
233
|
+
end
|
234
|
+
data = data.unpack("C*") if data.is_a? String
|
235
|
+
write_file = [ 0, 0xd6, address >> 8, address & 0xff, data.size ] +
|
236
|
+
data
|
237
|
+
response = data(@slot, HOST, write_file)
|
238
|
+
response.successful? or return
|
239
|
+
true
|
240
|
+
end
|
241
|
+
private :write_chunk
|
242
|
+
|
243
|
+
# Attempts to write the string data to the card starting at address.
|
244
|
+
# On success returns a true value.
|
245
|
+
def write(data, address = 0)
|
246
|
+
size = data.size
|
247
|
+
if @card and address + size > @card.memory_size
|
248
|
+
size = @card.memory_size - address
|
249
|
+
end
|
250
|
+
return if size <= 0
|
251
|
+
offset = 0
|
252
|
+
caught = catch(:break) do
|
253
|
+
while size >= chunk_size
|
254
|
+
write_chunk(address, data[offset, chunk_size]) or break :break
|
255
|
+
address += chunk_size
|
256
|
+
offset += chunk_size
|
257
|
+
size -= chunk_size
|
258
|
+
end
|
259
|
+
end
|
260
|
+
if caught == :break
|
261
|
+
return false
|
262
|
+
elsif size > 0
|
263
|
+
write_chunk(address, data[offset, size])
|
264
|
+
end
|
265
|
+
true
|
266
|
+
end
|
267
|
+
|
268
|
+
# The pin is sent to the card. It can be given as a string or an array
|
269
|
+
# of characters. A true result is returned if the sending was
|
270
|
+
# successful.
|
271
|
+
def enter_pin(pin)
|
272
|
+
unless pin.size <= 255
|
273
|
+
raise ArgumentError.new("size of pin must be <= 255")
|
274
|
+
end
|
275
|
+
pin = pin.unpack("C*") if pin.is_a? String
|
276
|
+
enter_pin = [ 0, 0x20, 0, 0, pin.size ] + pin
|
277
|
+
response = data(@slot, HOST, enter_pin)
|
278
|
+
response.successful? or return
|
279
|
+
true
|
280
|
+
end
|
281
|
+
|
282
|
+
# The pin of this card is changed from old_pin to new_pin. They can be
|
283
|
+
# given as strings or arrays of characters. A true result is returned
|
284
|
+
# if the sending was successful.
|
285
|
+
def change_pin(old_pin, new_pin)
|
286
|
+
old_pin = old_pin.unpack("C*") if old_pin.is_a? String
|
287
|
+
new_pin = new_pin.unpack("C*") if new_pin.is_a? String
|
288
|
+
data = old_pin + new_pin
|
289
|
+
unless data.size <= 255
|
290
|
+
raise ArgumentError.new("size of old and new pin must be <= 255")
|
291
|
+
end
|
292
|
+
change_pin = [ 0, 0x24, 0, 0, data.size ] + data
|
293
|
+
response = data(@slot, HOST, change_pin)
|
294
|
+
response.successful? or return
|
295
|
+
true
|
296
|
+
end
|
297
|
+
|
298
|
+
# Requests the card status from the cardterminal. Returns the
|
299
|
+
# Response object or nil if the response wasn't successful.
|
300
|
+
# This method is called by the card_inserted? method to find
|
301
|
+
# out if a card is inserted into the terminal.
|
302
|
+
def request_card_status
|
303
|
+
get_card_status = [ CTBCS_CLA, CTBCS_INS_STATUS,
|
304
|
+
CTBCS_P1_CT_KERNEL, CTBCS_P2_STATUS_ICC, 0 ]
|
305
|
+
response = data(CT, HOST, get_card_status)
|
306
|
+
response.successful? ? response : nil
|
307
|
+
end
|
308
|
+
|
309
|
+
# Sends a byte sequence to the card to get the answer to reset
|
310
|
+
# Response object. This method is called by the Cardterminal#reset
|
311
|
+
# method.
|
312
|
+
def request_icc
|
313
|
+
get_atr = [ CTBCS_CLA, CTBCS_INS_REQUEST, CTBCS_P1_INTERFACE1,
|
314
|
+
CTBCS_P2_REQUEST_GET_ATR, 0 ]
|
315
|
+
@card_old = @card if @card
|
316
|
+
@card, atr = nil, nil
|
317
|
+
begin
|
318
|
+
if card_inserted?
|
319
|
+
atr = data(CT, HOST, get_atr)
|
320
|
+
if atr
|
321
|
+
if atr.not_changed?
|
322
|
+
@card = @card_old
|
323
|
+
return @card.atr
|
324
|
+
end
|
325
|
+
@card = Card.new(atr)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
rescue CTAPIError => e
|
329
|
+
STDERR.puts "Caught: #{e}."
|
330
|
+
end
|
331
|
+
@card.atr
|
332
|
+
end
|
333
|
+
|
334
|
+
# Sends the eject card byte sequence to the card terminal and
|
335
|
+
# returns the Response object. This method is called by the
|
336
|
+
# Cardterminal#reset method.
|
337
|
+
def eject_icc
|
338
|
+
eject = [ 0x20, 0x15, 0x01, 0x00, 0x00 ]
|
339
|
+
data(CT, HOST, eject)
|
340
|
+
end
|
341
|
+
|
342
|
+
# The cardterminal is reset by first calling eject_icc and
|
343
|
+
# then request_icc. If the reset was successful the Response
|
344
|
+
# object of request_icc is returned, otherwise a nontrue value is
|
345
|
+
# returned.
|
346
|
+
def reset
|
347
|
+
eject_icc
|
348
|
+
response = request_icc or return
|
349
|
+
response.successful? or return
|
350
|
+
response
|
351
|
+
end
|
352
|
+
|
353
|
+
# Returns the card status as symbol: :no_card, :card, :card_connect.
|
354
|
+
def card_status
|
355
|
+
response = request_card_status
|
356
|
+
case response[0]
|
357
|
+
when CTBCS_DATA_STATUS_NOCARD
|
358
|
+
:no_card
|
359
|
+
when CTBCS_DATA_STATUS_CARD
|
360
|
+
:card
|
361
|
+
when CTBCS_DATA_STATUS_CARD_CONNECT
|
362
|
+
:card_connect
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
# Returns true if a card is inserted in the cardterminal at the moment,
|
367
|
+
# false if the cardterminal is empty.
|
368
|
+
def card_inserted?
|
369
|
+
cs = card_status
|
370
|
+
cs == :card || cs == :card_connect ? true : false
|
371
|
+
end
|
372
|
+
|
373
|
+
# Returns true if the card has been changed in the cardterminal,
|
374
|
+
# false it is still the same card. If an error occured nil
|
375
|
+
# is returned.
|
376
|
+
def card_changed?
|
377
|
+
response = reset or return
|
378
|
+
response.changed? and return true
|
379
|
+
response.not_changed? and return false
|
380
|
+
return
|
381
|
+
end
|
382
|
+
private :card_changed?
|
383
|
+
|
384
|
+
# A little structure for manufacturer information.
|
385
|
+
class Manufacturer
|
386
|
+
|
387
|
+
# Creates a Manufacturer object for this cardterminal from
|
388
|
+
# a manufacturer status response.
|
389
|
+
def initialize(response)
|
390
|
+
@manufacturer, @model, @revision =
|
391
|
+
response[0, 5], response[5, 3], response[10, 5]
|
392
|
+
end
|
393
|
+
|
394
|
+
# The manufacturer of this cardterminal.
|
395
|
+
attr_reader :manufacturer
|
396
|
+
|
397
|
+
# The model name of this cardterminal.
|
398
|
+
attr_reader :model
|
399
|
+
|
400
|
+
# The revision number of this cardterminal.
|
401
|
+
attr_reader :revision
|
402
|
+
|
403
|
+
# A string of the form 'manufacturer model revision' is returned.
|
404
|
+
def to_s
|
405
|
+
[manufacturer, model, revision].join(" ")
|
406
|
+
end
|
407
|
+
|
408
|
+
end
|
409
|
+
|
410
|
+
# Prints data with indication of direction to STDERR.
|
411
|
+
def debug(direction, data)
|
412
|
+
direction =
|
413
|
+
case direction
|
414
|
+
when 1 then '>>> '
|
415
|
+
when 2 then '<<< '
|
416
|
+
else ''
|
417
|
+
end
|
418
|
+
STDERR.puts direction + data.to_s
|
419
|
+
end
|
420
|
+
private :debug
|
421
|
+
|
422
|
+
end
|
423
|
+
|
424
|
+
# APDU (Application Protocol Data Unit) that is sent to/received from the
|
425
|
+
# cardterminal. This is the parent class of Command and Response.
|
426
|
+
class APDU
|
427
|
+
|
428
|
+
# An APDU object is generated from the String data.
|
429
|
+
def initialize(data)
|
430
|
+
@data = data
|
431
|
+
end
|
432
|
+
|
433
|
+
# This is the data string.
|
434
|
+
attr_reader :data
|
435
|
+
|
436
|
+
# Access data string by indices.
|
437
|
+
def [](*args) @data[*args] end
|
438
|
+
|
439
|
+
# We use Ruby's inspect representation to inspect the data.
|
440
|
+
def inspect
|
441
|
+
@data.inspect
|
442
|
+
end
|
443
|
+
|
444
|
+
# To display the data, a string of the form '12:23:a4' is built.
|
445
|
+
def to_s
|
446
|
+
@data.unpack('C*').map { |x| sprintf("%02x", x) }.join(':')
|
447
|
+
end
|
448
|
+
|
449
|
+
end
|
450
|
+
|
451
|
+
# A command sent to the cardterminal.
|
452
|
+
class Command < APDU
|
453
|
+
|
454
|
+
# A Command is generated from a string of the form '12:23:a4' or
|
455
|
+
# '12 23 a4'.
|
456
|
+
def self.from_string(command)
|
457
|
+
command = command.split(/[ :]/).map { |x| Integer '0x' + x }
|
458
|
+
from_array(command)
|
459
|
+
end
|
460
|
+
|
461
|
+
# A Command is generated from an array of bytes of the form
|
462
|
+
# [ 0x12, 0x23, 0xa4].
|
463
|
+
def self.from_array(command)
|
464
|
+
new(command.pack('C*'))
|
465
|
+
end
|
466
|
+
|
467
|
+
end
|
468
|
+
|
469
|
+
# A response received from the cardterminal.
|
470
|
+
class Response < APDU
|
471
|
+
|
472
|
+
# If this response was successful true is returned, false otherwise.
|
473
|
+
# A response is successful if the last two bytes are '90:00'.
|
474
|
+
def successful?
|
475
|
+
return false if @data.size < 2
|
476
|
+
return true if @data[-2, 2] == "\x90\x00"
|
477
|
+
false
|
478
|
+
end
|
479
|
+
|
480
|
+
def changed?
|
481
|
+
@data[-2, 2] == "\x62\x00" ? true : false
|
482
|
+
end
|
483
|
+
|
484
|
+
def not_changed?
|
485
|
+
@data[-2, 2] == "\x62\x01" ? true : false
|
486
|
+
end
|
487
|
+
|
488
|
+
end
|
489
|
+
|
490
|
+
class Card
|
491
|
+
|
492
|
+
def initialize(atr)
|
493
|
+
@atr = atr
|
494
|
+
end
|
495
|
+
|
496
|
+
# The answer to reset (ATR) of the inserted chipcard.
|
497
|
+
attr_accessor :atr
|
498
|
+
|
499
|
+
# Check if the ATR is correct. If false, the data of this Card
|
500
|
+
# object could be wrong and the calculations based on this data wrong.
|
501
|
+
def atr_ok?
|
502
|
+
@atr[4, 2] == "\x90\x00" && @atr[2] == 0x10
|
503
|
+
end
|
504
|
+
|
505
|
+
# The bit width of this chipcard.
|
506
|
+
def memory_bits
|
507
|
+
1 << (@atr[1] & 0x07)
|
508
|
+
end
|
509
|
+
|
510
|
+
# Number of memory blocks on this chipcard.
|
511
|
+
def memory_blocks
|
512
|
+
1 << (((@atr[1] & 0x78) >> 3) + 6)
|
513
|
+
end
|
514
|
+
|
515
|
+
# Computes the memory size of the inserted chipcard in bytes.
|
516
|
+
def memory_size
|
517
|
+
(memory_blocks * memory_bits >> 3)
|
518
|
+
end
|
519
|
+
|
520
|
+
# The structure of this chipcard.
|
521
|
+
def structure
|
522
|
+
if (@atr[0] & 0x3) == 0
|
523
|
+
'ISO'
|
524
|
+
elsif (@atr[0] & 0x7) == 2
|
525
|
+
'common use'
|
526
|
+
elsif (@atr[0] & 0x7) == 6
|
527
|
+
'proprietary use'
|
528
|
+
else
|
529
|
+
'special use'
|
530
|
+
end
|
531
|
+
end
|
532
|
+
|
533
|
+
# Supported protocol of this chipcard.
|
534
|
+
def protocol
|
535
|
+
if (@atr[0] & 0x80) == 0x00
|
536
|
+
'ISO'
|
537
|
+
elsif (@atr[0] & 0xf0) == 0x80
|
538
|
+
'I2C'
|
539
|
+
elsif (@atr[0] & 0xf0) == 0x90
|
540
|
+
'3W'
|
541
|
+
elsif (@atr[0] & 0xf0) == 0xa0
|
542
|
+
'2W'
|
543
|
+
else
|
544
|
+
'unknown'
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
548
|
+
# Returns a short description of this card as a string.
|
549
|
+
def to_s
|
550
|
+
"<#{self.class}:Structure #{structure} Protocol #{protocol} " +
|
551
|
+
"(#{memory_blocks}x#{memory_bits}=#{memory_size}B)" + '>'
|
552
|
+
end
|
553
|
+
|
554
|
+
# Returns adescription of this card including the ATR as a string.
|
555
|
+
def inspect
|
556
|
+
string = to_s
|
557
|
+
string[-1] = " #{atr}>"
|
558
|
+
string
|
559
|
+
end
|
560
|
+
|
561
|
+
end
|
562
|
+
|
563
|
+
end
|