Skip to content

Commit

Permalink
apdu refactoring plus tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Maxim Chechel committed Oct 8, 2014
1 parent 7edc3ad commit 6532e8b
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 54 deletions.
46 changes: 20 additions & 26 deletions examples/listen.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,34 @@
puts "Available readers: %s" % readers.to_s

# The order of tag types in poll arguments defines priority of tag types
readers[0].poll(IsoDep::Tag, Mifare::Classic::Tag, Mifare::Ultralight::Tag, NFC::Tag) do |tag|
readers[0].poll(IsoDep::Tag, Mifare::Classic::Tag, Mifare::Ultralight::Tag) do |tag|
begin
puts "Applied #{tag.class.name}: #{tag}"

case tag
when Mifare::Classic::Tag
tag.select do
# Perform authentication to block 0x04 with the Key A that equals
# to "\xFF\xFF\xFF\xFF\xFF\xFF" you can also use "FFFFFFFFFFFF"
# representation. In this case it will be automatically packed to 6 bytes
if auth(4, :key_a, "FFFFFFFFFFFF")
puts "authenticated!"
processed! # mark tag as processed so even if it supports different
# protocol poll method will continue with another physical
# tag
end
end
# Perform authentication to block 0x04 with the Key A that equals
# to "\xFF\xFF\xFF\xFF\xFF\xFF" you can also use "FFFFFFFFFFFF"
# representation. In this case it will be automatically packed to 6 bytes
if auth(4, :key_a, "FFFFFFFFFFFF")
puts "authenticated!"
processed! # mark tag as processed so even if it supports different
# protocol poll method will continue with another physical
# tag
end
when Mifare::Ultralight::Tag
tag.select do
puts "Page 1: %s" % read(1).unpack('H*').pop
processed!
end
puts "Page 1: %s" % read(1).unpack('H*').pop
processed!
when IsoDep::Tag
aid = ["F75246544101"].pack('H*')
tag.select(aid) do
# sending APDU command to tag using send_apdu method
apdu = ['A00D010018B455CAF0F331AF703EFA2E2D744EC7E22AA64076CD19F6D0'].pack('H*')
puts send_apdu(apdu).unpack('H*').pop
select ["F75246544101"].pack('H*')
# sending APDU command to tag using send_apdu method
apdu = ['A00D010018B455CAF0F331AF703EFA2E2D744EC7E22AA64076CD19F6D0'].pack('H*')
puts send_apdu(apdu).unpack('H*').pop

# sending APDU command with "<<" operator which is alias to send_apdu
# response = tag << apdu
# puts response.unpack('H*').pop
processed!
end
# sending APDU command with "<<" operator which is alias to send_apdu
# response = tag << apdu
# puts response.unpack('H*').pop
processed!
end
rescue Exception => e
puts e
Expand Down
24 changes: 12 additions & 12 deletions lib/ruby-nfc/apdu/response.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
require_relative './apdu'

module APDU
class Response
def initialize(response)
@response = response
end
resp_8bit = response.dup
resp_8bit.force_encoding('ASCII-8BIT')

# Public: Parse response without checking SW
def parse
raise APDU::Error, "Response must be at least 2-bytes long" if resp_8bit.size < 2

@response = resp_8bit
end

# Public: Parse APDU response and check SW after.
#
# Returns instance of APDU::Response class
# Raises APDU::Errno if SW != 9000
def parse!

# Public: Raises APDU::Errno if self.sw is not equal 0x9000
def raise_errno!
raise APDU::Errno.new(sw) if sw != 0x9000
self
end

# Public: Return Status Word of an APDU response. Status Word is a two-byte
Expand All @@ -23,12 +23,12 @@ def sw
@response[-2, 2].unpack('n').pop
end

# Public: Return high byte of Status Word aka SW1
# Public: Return high byte of Status Word
def sw1
@response[-2, 1].unpack('C').pop
end

# Public: Return low byte of Status Word aka SW2
# Public: Return low byte of Status Word
def sw2
@response[-1,1].unpack('C').pop
end
Expand Down
4 changes: 2 additions & 2 deletions lib/ruby-nfc/reader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def discover(*card_types)
end
end

def poll(*card_types)
def poll(*card_types, &block)
connect

LibNFC.nfc_initiator_init(@ptr) # we'll be initiator not a target
Expand Down Expand Up @@ -57,7 +57,7 @@ def poll(*card_types)
card_types.each do |card_type|
if card_type.match?(target)
tag = card_type.new(target, self)
yield tag
tag.connect(&block)
# if this tag was marked as processed - continue with next tag
break if target.processed?
end
Expand Down
37 changes: 34 additions & 3 deletions lib/ruby-nfc/tags/isodep.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
require_relative '../nfc'
require_relative './tag'
require_relative '../apdu/request'
require_relative '../apdu/response'

module IsoDep
ISO_14443_4_COMPATIBLE = 0x20
Expand All @@ -12,7 +14,7 @@ def self.match?(target)
target[:nti][:nai][:btSak] & IsoDep::ISO_14443_4_COMPATIBLE > 0
end

def select(aid = nil, &block)
def connect(&block)
@reader.set_flag(:NP_AUTO_ISO14443_4, true)

modulation = LibNFC::Modulation.new
Expand Down Expand Up @@ -45,10 +47,28 @@ def select(aid = nil, &block)
end
end

def deselect
def disconnect
0 == LibNFC.nfc_initiator_deselect_target(@reader.ptr)
end

# Public: Select application with given AID (Application Identifier)
#
# aid - Application Identifier of an applet located on a card
#
# Returns nothing.
# Raises APDU::Errno if application with such AID doesn't exists on a card
def select(aid)
send_apdu!("\x00\xA4\x04\x00#{aid.size.chr}#{aid}")
end

# Public: Send APDU command to tag
#
# apdu - APDU command to send. see ISO/IEC 7816-4 or wiki for details.
# APDU is a binary string that should
#
#
# Returns APDU::Response object
# Raises IsoDep::Error if card didn't respond
def send_apdu(apdu)
cmd = apdu
cmd.force_encoding('ASCII-8BIT')
Expand All @@ -62,7 +82,18 @@ def send_apdu(apdu)

raise IsoDep::Error, "APDU sending failed: #{res_len}" if res_len < 0

response_buffer.get_bytes(0, res_len).to_s
APDU::Response.from_string(response_buffer.get_bytes(0, res_len).to_s)
end

# Public: Send APDU command to tag and raises APDU::Errno exception
# if SW not equal to 0x9000
#
# apdu - APDU command to transmit to the tag
#
# Returns APDU::Response object
# Raises APDU::Errno if SW is not equal to 0x9000
def send_apdu!(apdu)
send_apdu(apdu).raise_errno!
end

alias :'<<' :send_apdu
Expand Down
6 changes: 3 additions & 3 deletions lib/ruby-nfc/tags/mifare/classic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,18 @@ def initialize(target, reader)
@auth_block = nil #last authenticated block
end

def select(&block)
def connect(&block)
@reader.set_flag(:NP_AUTO_ISO14443_4, false)

res = Mifare.mifare_classic_connect(@pointer)
if 0 == res
super
else
raise Mifare::Error, "Can't select tag: #{res}"
raise Mifare::Error, "Can't connect tag: #{res}"
end
end

def deselect
def disconnect
Mifare.mifare_classic_disconnect(@pointer)
super
end
Expand Down
2 changes: 1 addition & 1 deletion lib/ruby-nfc/tags/mifare/tag.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def to_s
end

# frees memory allocated for mifare tag
def deselect
def disconnect
Mifare.freefare_free_tag(@pointer)
end

Expand Down
6 changes: 3 additions & 3 deletions lib/ruby-nfc/tags/mifare/ultralight.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,18 @@ module Mifare

module Ultralight
class Tag < Mifare::Tag
def select(&block)
def connect(&block)
@reader.set_flag(:NP_AUTO_ISO14443_4, false)

res = Mifare.mifare_ultralight_connect(@pointer)
if 0 == res
super
else
raise Mifare::Error, "Can't select tag: #{res}"
raise Mifare::Error, "Can't connect to tag: #{res}"
end
end

def deselect
def disconnect
Mifare.mifare_ultralight_disconnect(@pointer)
super
end
Expand Down
6 changes: 4 additions & 2 deletions lib/ruby-nfc/tags/tag.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ def initialize(target, reader)
@processed = false
end

def select(&block)
def connect(&block)
if block_given?
begin
self.instance_eval(&block)
ensure
deselect
disconnect
end
end
end
Expand All @@ -24,6 +24,8 @@ def processed?
@target.processed?
end

def disconnect; end

def uid
uid_size = @target[:nti][:nai][:szUidLen]
@target[:nti][:nai][:abtUid].to_s[0...uid_size]
Expand Down
5 changes: 4 additions & 1 deletion ruby-nfc.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ Gem::Specification.new do |s|
"./lib/ruby-nfc/tags/isodep.rb",
"./lib/ruby-nfc/tags/tag.rb",
"./lib/ruby-nfc/reader.rb",
"./lib/ruby-nfc/libnfc.rb"
"./lib/ruby-nfc/libnfc.rb",
"./lib/ruby-nfc/apdu/apdu.rb",
"./lib/ruby-nfc/apdu/request.rb",
"./lib/ruby-nfc/apdu/response.rb"
]

s.homepage = 'https://github.com/maximchick/ruby-nfc'
Expand Down
2 changes: 1 addition & 1 deletion test/apdu_request_test.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
require 'test_helper'
require 'ruby-nfc/apdu/request'

class PostTest < MiniTest::Unit::TestCase
class APDURequestTest < MiniTest::Unit::TestCase
def test_raise_apdu_error_if_length_too_small
e = assert_raises(APDU::Error) {APDU::Request.from_string("123")}
assert_match e.message, /too short/
Expand Down
34 changes: 34 additions & 0 deletions test/apdu_response_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
require 'test_helper'
require 'ruby-nfc/apdu/response'

class APDUResponseTest < MiniTest::Unit::TestCase
def setup
@response = APDU::Response.new("\x01\x02\x03\x04\x69\x85")
end

def test_raise_apdu_error_if_length_too_small
assert_raises(APDU::Error) { APDU::Response.new("") }
end

def test_data
assert_equal "\x01\x02\x03\x04", @response.data
end

def test_sw
assert_equal 0x6985, @response.sw
end

def test_sw1
assert_equal 0x69, @response.sw1
end

def test_sw2
assert_equal 0x85, @response.sw2
end

def test_raise_errno
e = assert_raises(APDU::Errno) { @response.raise_errno! }
assert_equal 0x6985, e.code
end

end

0 comments on commit 6532e8b

Please sign in to comment.