Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions lib/smartcard/pcsc/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,37 @@ def readers(groups = [])
groups_ptr.free
end
end

def usb_port_for_reader(reader)
begin
card = self.card(reader, :shared, :any)
begin
attrib = card[FFILib::Consts::SCARD_ATTR_CHANNEL_ID]
if attrib
ddddcccc = attrib.unpack('I')[0]
dddd = ddddcccc >> 16
if dddd == 0x0020
bus = (ddddcccc & 0xFF00) >> 8
device_address = ddddcccc & 0xFF
# return bus and device_address
return {bus: bus, device_address: device_address}
else
puts "Not a USB reader"
end
else
puts "Unable to get SCARD_ATTR_CHANNEL_ID"
end
ensure
card.disconnect if card
end
rescue Smartcard::PCSC::Exception => e
if e.message.include?("no_smartcard")
nil
else
raise e
end
end
end

# Queries smart-card readers, blocking until a state change occurs.
#
Expand Down
3 changes: 2 additions & 1 deletion lib/smartcard/pcsc/ffi_structs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ class ReaderStateQuery < FFI::Struct
:current_state, Word,
:event_state, Word,
:atr_length, Word,
:atr, [:char, Consts::MAX_ATR_SIZE]
:atr, [:char, Consts::MAX_ATR_SIZE],
:usb_address, :pointer
end

# Low-level protocol information for APDU transmission and reception.
Expand Down
14 changes: 14 additions & 0 deletions lib/smartcard/pcsc/reader_state_queries.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# License:: MIT

require 'set'
require 'json'

# :nodoc: namespace
module Smartcard::PCSC
Expand Down Expand Up @@ -169,6 +170,19 @@ def reader_name=(new_name)
self[:reader_name] = FFI::MemoryPointer.from_string new_name
end

def usb_address
if self[:usb_address].null?
nil
else
JSON.parse(self[:usb_address].read_string, symbolize_names: true)
end
end

def usb_address=(new_usb_address)
self[:usb_address].free if self[:usb_address].kind_of? FFI::MemoryPointer
self[:usb_address] = FFI::MemoryPointer.from_string(new_usb_address.to_json)
end

# Packs an unpacked card state (symbol or set of symbols) into a number.
#
# This should not be used by client code.
Expand Down
17 changes: 17 additions & 0 deletions test/pcsc/context_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,23 @@ def _check_readers(readers)
'each reader name should be a string'
end
end

def test_usb_port_for_reader
readers = @context.readers
readers.each do |reader|
usb_port = @context.usb_port_for_reader(reader)
if usb_port
assert_operator usb_port, :kind_of?, Hash, 'usb_port should be a Hash'
assert_includes usb_port, :bus, 'usb_port should include :bus key'
assert_includes usb_port, :device_address, 'usb_port should include :device_address key'

assert_operator usb_port[:bus], :kind_of?, Integer, ':bus should be an Integer'
assert_operator usb_port[:device_address], :kind_of?, Integer, ':device_address should be an Integer'
else
assert_nil usb_port, 'usb_port should be nil for non-USB reader or no card present'
end
end
end

def test_wait_for_status_change
readers = @context.readers
Expand Down
8 changes: 8 additions & 0 deletions test/pcsc/reader_state_queries_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

require 'rubygems'
require 'smartcard'
require 'json'

require 'test/unit'

Expand All @@ -20,6 +21,8 @@ def setup
@queries[0].atr = 'grreat success'
@queries[0].reader_name = 'PC/SC Reader 0'
@queries[1].reader_name = 'CCID Reader 1'
@queries[0].usb_address = { :bus => 1, :device_address => 2 }
@queries[1].usb_address = { :bus => 1, :device_address => 4 }
end

def teardown
Expand Down Expand Up @@ -65,6 +68,11 @@ def test_reader_names
assert_equal 'PC/SC Reader 0', @queries[0].reader_name
assert_equal 'CCID Reader 1', @queries[1].reader_name
end

def test_usb_address
assert_equal({ :bus => 1, :device_address => 2 }, @queries[0].usb_address)
assert_equal({ :bus => 1, :device_address => 4 }, @queries[1].usb_address)
end

def test_ack_changes
@queries.ack_changes
Expand Down