tem_ruby 0.9.2 → 0.10.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.
data/lib/tem/lifecycle.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  module Tem::Lifecycle
2
2
  def activate
3
- issue_apdu([0x00, 0x10, 0x00, 0x00, 0x00])[0] == 0x90
3
+ @transport.applet_apdu(:ins => 0x10)[:status] == 0x9000
4
4
  end
5
5
  def kill
6
- issue_apdu([0x00, 0x11, 0x00, 0x00, 0x00])[0] == 0x90
6
+ @transport.applet_apdu(:ins => 0x11)[:status] == 0x9000
7
7
  end
8
8
  end
@@ -14,8 +14,7 @@ module Tem::SeClosures
14
14
 
15
15
  def sec_trace
16
16
  #begin
17
- response = issue_apdu [0x00, 0x54, 0x00, 0x00, 0x00].flatten
18
- trace = reply_data(response)
17
+ trace = @transport.applet_apdu! :ins => 0x54
19
18
  if trace.length > 2
20
19
  case read_tem_short(trace, 0) # trace version
21
20
  when 1
@@ -32,22 +31,20 @@ module Tem::SeClosures
32
31
  def solve_psfault
33
32
  # TODO: better strategy, lol
34
33
  next_cell = rand(16)
35
- response = issue_apdu [0x00, 0x53, to_tem_ushort(next_cell), 0x00].flatten
36
- tem_error(response) if failure_code(response)
34
+ @transport.applet_apdu! :ins => 0x53, :p12 => to_tem_ushort(next_cell)
37
35
  end
38
36
 
39
37
  def execute(secpack, key_id = 0)
40
38
  # load SECpack
41
39
  buffer_id = post_buffer(secpack.tem_formatted_body)
42
- response = issue_apdu [0x00, 0x50, to_tem_byte(buffer_id), to_tem_byte(key_id), 0x00].flatten
43
- tem_error(response) if failure_code(response)
40
+ response = @transport.applet_apdu! :ins => 0x50, :p1 => buffer_id,
41
+ :p2 => key_id
44
42
  tem_secpack_error(response) if read_tem_byte(response, 0) != 1
45
43
 
46
44
  # execute SEC
47
45
  sec_exception = nil
48
46
  loop do
49
- response = issue_apdu [0x00, 0x52, 0x00, 0x00, 0x00].flatten
50
- tem_error(response) if failure_code(response)
47
+ response = @transport.applet_apdu! :ins => 0x52
51
48
  sec_status = read_tem_byte(response, 0)
52
49
  case sec_status
53
50
  when 2 # success
@@ -67,15 +64,14 @@ module Tem::SeClosures
67
64
  end
68
65
  end
69
66
 
70
- # TODO: handle response to figure out if we need to do page faults or something
71
-
72
67
  # unbind SEC
73
- response = issue_apdu [0x00, 0x51, 0x00, 0x00, 0x00].flatten
68
+ response = @transport.applet_apdu! :ins => 0x51
74
69
  raise sec_exception if sec_exception
75
- buffer_id, buffer_length = read_tem_byte(response, 0), read_tem_short(response, 1)
70
+ buffer_id = read_tem_byte(response, 0)
71
+ buffer_length = read_tem_short(response, 1)
76
72
  data_buffer = read_buffer buffer_id
77
73
  release_buffer buffer_id
78
74
 
79
75
  return data_buffer[0...buffer_length]
80
76
  end
81
- end
77
+ end
data/lib/tem/tag.rb CHANGED
@@ -1,28 +1,33 @@
1
1
  module Tem::Tag
2
- def set_tag(tag_data)
3
- buffer_id = post_buffer(tag_data)
4
- response = issue_apdu [0x00, 0x30, to_tem_byte(buffer_id), 0x00, 0x00].flatten
5
- tem_error(response) if failure_code(response)
6
- release_buffer(buffer_id)
2
+ def set_tag(tag_data)
3
+ buffer_id = post_buffer tag_data
4
+ begin
5
+ @transport.applet_apdu! :ins => 0x30, :p1 => buffer_id
6
+ ensure
7
+ release_buffer buffer_id
8
+ end
7
9
  end
8
10
 
9
11
  def get_tag_length
10
- response = issue_apdu [0x00, 0x31, 0x00, 0x00, 0x00].flatten
11
- tem_error(response) if failure_code(response)
12
+ response = @transport.applet_apdu! :ins => 0x31
12
13
  return read_tem_short(response, 0)
13
14
  end
14
15
 
15
16
  def get_tag_data(offset, length)
16
- buffer_id = alloc_buffer(length)
17
- response = issue_apdu [0x00, 0x32, to_tem_byte(buffer_id), 0x00, 0x04, to_tem_short(offset), to_tem_short(length)].flatten
18
- tem_error(response) if failure_code(response)
19
- tag_data = read_buffer(buffer_id)
20
- release_buffer(buffer_id)
21
- return tag_data
17
+ buffer_id = alloc_buffer length
18
+ begin
19
+ @transport.applet_apdu! :ins => 0x32, :p1 => buffer_id,
20
+ :data => [to_tem_short(offset),
21
+ to_tem_short(length)].flatten
22
+ tag_data = read_buffer buffer_id
23
+ ensure
24
+ release_buffer buffer_id
25
+ end
26
+ tag_data
22
27
  end
23
28
 
24
29
  def get_tag
25
30
  tag_length = self.get_tag_length
26
- get_tag_data(0, tag_length)
31
+ get_tag_data 0, tag_length
27
32
  end
28
33
  end
data/lib/tem/tem.rb CHANGED
@@ -1,5 +1,3 @@
1
- require 'pp'
2
-
3
1
  class Tem::Session
4
2
  include Tem::Abi
5
3
  include Tem::Buffers
@@ -12,33 +10,19 @@ class Tem::Session
12
10
  include Tem::Tag
13
11
  include Tem::Toolkit
14
12
 
15
- @@aid = [0x19, 0x83, 0x12, 0x29, 0x10, 0xBA, 0xBE]
13
+ CAPPLET_AID = [0x19, 0x83, 0x12, 0x29, 0x10, 0xBA, 0xBE]
16
14
 
17
- def initialize(javacard)
18
- @card = javacard
19
- @card.select_applet(@@aid)
20
- end
15
+ attr_reader :transport
21
16
 
22
- def disconnect
23
- # TODO: deselect applet, reset card
24
- @card = nil
17
+ def initialize(transport)
18
+ @transport = transport
19
+ @transport.select_applet CAPPLET_AID
25
20
  end
26
21
 
27
- def issue_apdu(apdu)
28
- @card.issue_apdu apdu
29
- end
30
-
31
- def failure_code(reply_apdu)
32
- @card.failure_code reply_apdu
33
- end
34
-
35
- def reply_data(reply_apdu)
36
- @card.reply_data reply_apdu
37
- end
38
-
39
- def tem_error(response)
40
- fcode = failure_code response
41
- raise "TEM returned error 0x#{'%04x' % fcode} while processing the request"
22
+ def disconnect
23
+ return unless @transport
24
+ @transport.disconnect
25
+ @transport = nil
42
26
  end
43
27
 
44
28
  def tem_secpack_error(response)
@@ -0,0 +1,87 @@
1
+ # :nodoc: namespace
2
+ module Tem::Transport
3
+
4
+ # Automatic configuration code.
5
+ module AutoConfigurator
6
+ # The name of the environment variable that might supply the transport
7
+ # configuration.
8
+ ENVIRONMENT_VARIABLE_NAME = 'TEM_PORT'
9
+
10
+ # The default configurations to be tried if no configuration is specified.
11
+ DEFAULT_CONFIGURATIONS = [
12
+ { :class => JcopRemoteTransport,
13
+ :opts => { :host => '127.0.0.1', :port => 8050} },
14
+ { :class => PcscTransport, :opts => { :reader_index => 0 }}
15
+ ]
16
+
17
+ # Creates a transport based on available configuration information.
18
+ def self.auto_transport
19
+ configuration = env_configuration
20
+ return try_transport(configuration) if configuration
21
+
22
+ DEFAULT_CONFIGURATIONS.each do |config|
23
+ transport = try_transport(config)
24
+ return transport if transport
25
+ end
26
+ return nil
27
+ end
28
+
29
+ # Retrieves transport configuration information from an environment variable.
30
+ #
31
+ # :call-seq:
32
+ # AutoConfigurator.env_configuration -> hash
33
+ #
34
+ # The returned configuration has the keys required by
35
+ # AutoConfigurator#try_transport
36
+ def self.env_configuration
37
+ return nil unless conf = ENV[ENVIRONMENT_VARIABLE_NAME]
38
+
39
+ case conf[0]
40
+ when ?:
41
+ # :8050 -- JCOP emulator at port 8050
42
+ transport_class = JcopRemoteTransport
43
+ transport_opts = { :host => '127.0.0.1' }
44
+ transport_opts[:port] = conf[1..-1].to_i
45
+ when ?@
46
+ # @127.0.0.1:8050 -- JCOP emulator at host 127.0.0.1 port 8050
47
+ transport_class = JcopRemoteTransport
48
+ port_index = conf.rindex(?:) || conf.length
49
+ transport_opts = { :host => conf[1...port_index] }
50
+ transport_opts[:port] = conf[(port_index + 1)..-1].to_i
51
+ when ?#
52
+ # #2 -- 2nd PC/SC reader in the system
53
+ transport_class = PcscTransport
54
+ transport_opts = { :reader_index => conf[1..-1].to_i - 1 }
55
+ else
56
+ # Reader Name -- the PC/SC reader with the given name
57
+ transport_class = PcscTransport
58
+ transport_opts = { :reader_name => conf }
59
+ end
60
+
61
+ transport_opts[:port] = 8050 if transport_opts[:port] == 0
62
+ if transport_opts[:reader_index] and transport_opts[:reader_index] < 0
63
+ transport_opts[:reader_index] = 0
64
+ end
65
+ { :class => transport_class, :opts => transport_opts }
66
+ end
67
+
68
+ # Attempts to create a new TEM transport with the given configuration.
69
+ # :call-seq:
70
+ # AutoConfigurator.try_transport(configuration) -> Transport or nil
71
+ #
72
+ # The configuration should have the following keys:
73
+ # class:: the Ruby class implementing the transport
74
+ # opts:: the options to be passed to the implementation's constructor
75
+ def self.try_transport(configuration)
76
+ raise 'No transport class specified' unless configuration[:class]
77
+ begin
78
+ transport = configuration[:class].new(configuration[:opts] || {})
79
+ transport.connect
80
+ return transport
81
+ rescue Exception
82
+ return nil
83
+ end
84
+ end
85
+ end # module AutoConfigurator
86
+
87
+ end # module Tem::Transport
@@ -0,0 +1,99 @@
1
+ # :nodoc: namespace
2
+ module Tem::Transport
3
+
4
+ # Module intended to be mixed into transport implementations to mediate between
5
+ # a high level format for Javacard-specific APDUs and the wire-level APDU
6
+ # request and response formats.
7
+ #
8
+ # The mix-in calls exchange_apdu in the transport implementation. It supplies
9
+ # the APDU data as an array of integers between 0 and 255, and expects a
10
+ # response in the same format.
11
+ module JavaCardMixin
12
+ # Selects a Javacard applet.
13
+ def select_applet(applet_id)
14
+ applet_apdu! :ins => 0xA4, :p1 => 0x04, :p2 => 0x00, :data => applet_id
15
+ end
16
+
17
+ # APDU exchange with the JavaCard applet, raising an exception if the return
18
+ # code is not success (0x9000).
19
+ #
20
+ # :call_seq:
21
+ # transport.applet_apdu!(apdu_data) -> array
22
+ #
23
+ # The apdu_data should be in the format expected by
24
+ # JavaCardMixin#serialize_apdu. Returns the response data, if the response
25
+ # status indicates success (0x9000). Otherwise, raises an exeception.
26
+ def applet_apdu!(apdu_data)
27
+ response = self.applet_apdu apdu_data
28
+ return response[:data] if response[:status] == 0x9000
29
+ raise "JavaCard response has error status 0x#{'%04x' % response[:status]}"
30
+ end
31
+
32
+ # Performs an APDU exchange with the JavaCard applet.
33
+ #
34
+ # :call-seq:
35
+ # transport.applet_apdu(apdu_data) -> hash
36
+ #
37
+ # The apdu_data should be in the format expected by
38
+ # JavaCardMixin#serialize_apdu. The response will be as specified in
39
+ # JavaCardMixin#deserialize_response.
40
+ def applet_apdu(apdu_data)
41
+ apdu = Tem::Transport::JavaCardMixin.serialize_apdu apdu_data
42
+ response = self.exchange_apdu apdu
43
+ JavaCardMixin.deserialize_response response
44
+ end
45
+
46
+ # Serializes an APDU for wire transmission.
47
+ #
48
+ # :call-seq:
49
+ # transport.wire_apdu(apdu_data) -> array
50
+ #
51
+ # The following keys are recognized in the APDU hash:
52
+ # cla:: the CLA byte in the APDU (optional, defaults to 0)
53
+ # ins:: the INS byte in the APDU -- the first byte seen by a JavaCard applet
54
+ # p::
55
+ # p1, p2:: the P1 and P2 bytes in the APDU (optional, both default to 0)
56
+ # data:: the extra data in the APDU (optional, defaults to nothing)
57
+ def self.serialize_apdu(apdu_data)
58
+ raise 'Unspecified INS in apdu_data' unless apdu_data[:ins]
59
+ apdu = [ apdu_data[:cla] || 0, apdu_data[:ins] ]
60
+ if apdu_data[:p12]
61
+ unless apdu_data[:p12].length == 2
62
+ raise "Malformed P1,P2 - #{apdu_data[:p12]}"
63
+ end
64
+ apdu += apdu_data[:p12]
65
+ else
66
+ apdu << (apdu_data[:p1] || 0)
67
+ apdu << (apdu_data[:p2] || 0)
68
+ end
69
+ if apdu_data[:data]
70
+ apdu << apdu_data[:data].length
71
+ apdu += apdu_data[:data]
72
+ else
73
+ apdu << 0
74
+ end
75
+ apdu
76
+ end
77
+
78
+ # De-serializes a JavaCard response APDU.
79
+ #
80
+ # :call-seq:
81
+ # transport.deserialize_response(response) -> hash
82
+ #
83
+ # The response contains the following keys:
84
+ # status:: the 2-byte status code (e.g. 0x9000 is OK)
85
+ # data:: the additional data in the response
86
+ def self.deserialize_response(response)
87
+ { :status => response[-2] * 256 + response[-1], :data => response[0...-2] }
88
+ end
89
+
90
+ # Installs a JavaCard applet on the JavaCard.
91
+ #
92
+ # This would be really, really nice to have. Sadly, it's a far away TBD right
93
+ # now.
94
+ def install_applet(cap_contents)
95
+ raise "Not implemeted; it'd be nice though, right?"
96
+ end
97
+ end # module Tem
98
+
99
+ end # module Tem::Transport
@@ -0,0 +1,51 @@
1
+ # :nodoc: namespace
2
+ module Tem::Transport
3
+
4
+ # Mixin implementing the JCOP simulator protocol.
5
+ #
6
+ # The (pretty informal) protocol specification is contained in the JavaDocs for
7
+ # the class com.ibm.jc.terminal.RemoteJCTerminal and should be easy to find by
8
+ # http://www.google.com/search?q=%22com.ibm.jc.terminal.RemoteJCTerminal%22
9
+ module JcopRemoteProtocol
10
+ # Encodes and sends a JCOP simulator message to a TCP socket.
11
+ #
12
+ # The message must contain the following keys:
13
+ # type:: Integer expressing the message type (e.g. 1 = APDU exchange)
14
+ # node:: Integer expressing the node address (e.g. 0 for most purposes)
15
+ # data:: message payload, as an array of Integers ranging from 0 to 255
16
+ def send_message(socket, message)
17
+ raw_message = [message[:type], message[:node], message[:data].length].
18
+ pack('CCn') + message[:data].pack('C*')
19
+ socket.send raw_message, 0
20
+ end
21
+
22
+ # Reads and decodes a JCOP simulator message from a TCP socket.
23
+ #
24
+ # :call_seq:
25
+ # client.read_message(socket) -> Hash or nil
26
+ #
27
+ # If the other side of the TCP socket closes the connection, this method
28
+ # returns nil. Otherwise, a Hash is returned, with the format required by the
29
+ # JcopRemoteProtocol#send_message.
30
+ def recv_message(socket)
31
+ header = ''
32
+ while header.length < 4
33
+ partial = socket.recv 4 - header.length
34
+ return false if partial.length == 0
35
+ header += partial
36
+ end
37
+ message_type, node_address, data_length = *header.unpack('CCn')
38
+ raw_data = ''
39
+ while raw_data.length < data_length
40
+ partial = socket.recv data_length - raw_data.length
41
+ return false if partial.length == 0
42
+ raw_data += partial
43
+ end
44
+
45
+ return false unless raw_data.length == data_length
46
+ data = raw_data.unpack('C*')
47
+ return { :type => message_type, :node => node_address, :data => data }
48
+ end
49
+ end # module JcopRemoteProtocol
50
+
51
+ end # namespace Tem::Transport
@@ -0,0 +1,171 @@
1
+ require 'socket'
2
+
3
+ # :nodoc: namespace
4
+ module Tem::Transport
5
+
6
+ # Stubs out the methods that can be implemented by the serving logic in a
7
+ # JCOP remote server. Serving logic classes should mix in this module, to
8
+ # avoid having unimplemented methods.
9
+ module JcopRemoteServingStubs
10
+ # Called when a client connection accepted.
11
+ #
12
+ # This method serves as a notification to the serving logic implementation.
13
+ # Its return value is discarded.
14
+ def connection_start
15
+ nil
16
+ end
17
+
18
+ # Called when a client connection is closed.
19
+ #
20
+ # This method serves as a notification to the serving logic implementation.
21
+ # Its return value is discarded.
22
+ def connection_end
23
+ nil
24
+ end
25
+
26
+ # Serving logic handling an APDU exchange.
27
+ #
28
+ # :call-seq:
29
+ # logic.exchange_apdu(apdu) -> array
30
+ #
31
+ # The |apdu| parameter is the request APDU, formatted as an array of
32
+ # integers between 0 and 255. The method should return the response APDU,
33
+ # formatted in a similar manner.
34
+ def exchange_apdu(apdu)
35
+ # Dumb implementation that always returns OK.
36
+ [0x90, 0x00]
37
+ end
38
+ end # module JcopRemoteServingStubs
39
+
40
+
41
+ # A server for the JCOP simulator protocol.
42
+ #
43
+ # The JCOP simulator protocol is generally useful when talking to a real JCOP
44
+ # simulator. This server is only handy for testing, and for forwarding
45
+ # connections (JCOP's Eclipse plug-in makes the simulator listen to 127.0.0.1,
46
+ # and sometimes you want to use it from another box).
47
+ class JcopRemoteServer
48
+ include JcopRemoteProtocol
49
+
50
+ # Creates a new JCOP server.
51
+ #
52
+ # The options hash supports the following keys:
53
+ # port:: the port to serve on
54
+ # ip:: the IP of the interface to serve on (defaults to all interfaces)
55
+ # reusable:: if set, the serving port can be shared with another
56
+ # application (REUSEADDR flag will be set on the socket)
57
+ #
58
+ # If the |serving_logic| parameter is nil, a serving logic implementation
59
+ # must be provided when calling JcopRemoteServer#run. The server will crash
60
+ # otherwise.
61
+ def initialize(options, serving_logic = nil)
62
+ @logic = serving_logic
63
+ @running = false
64
+ @options = options
65
+ @mutex = Mutex.new
66
+ end
67
+
68
+ # Runs the serving loop indefinitely.
69
+ #
70
+ # This method serves incoming conenctions until #stop is called.
71
+ #
72
+ # If |serving_logic| contains a non-nil value, it overrides any previously
73
+ # specified serving logic implementation. If no implementation is specified
74
+ # when the server is instantiated via JcopRemoteServer#new, one must be
75
+ # passed into |serving_logic|.
76
+ def run(serving_logic = nil)
77
+ @mutex.synchronize do
78
+ @logic ||= serving_logic
79
+ @serving_socket = serving_socket @options
80
+ @running = true
81
+ end
82
+ loop do
83
+ break unless @mutex.synchronize { @running }
84
+ begin
85
+ client_socket, client_address = @serving_socket.accept
86
+ rescue
87
+ # An exception will occur if the socket is closed
88
+ break
89
+ end
90
+ @logic.connection_start
91
+ loop do
92
+ break unless @mutex.synchronize { @running }
93
+ break unless process_request client_socket
94
+ end
95
+ client_socket.close rescue nil
96
+ @logic.connection_end # implemented by subclass
97
+ end
98
+ @mutex.synchronize do
99
+ @serving_socket.close if @serving_socket
100
+ @serving_socket = nil
101
+ end
102
+ end
103
+
104
+ # Stops the serving loop.
105
+ def stop
106
+ @mutex.synchronize do
107
+ if @running
108
+ @serving_socket.close rescue nil
109
+ @serving_socket = nil
110
+ @running = false
111
+ end
112
+ end
113
+
114
+ # TODO(costan): figure out a way to let serving logic reach this directly.
115
+ end
116
+
117
+
118
+ # Creates a socket listening to incoming connections to this server.
119
+ #
120
+ # :call-seq:
121
+ # server.establish_socket(options) -> Socket
122
+ #
123
+ # The |options| parameter supports the same keys as the options parameter
124
+ # of JcopRemoteServer#new.
125
+ #
126
+ # Returns a Socket configured to accept incoming connections.
127
+ def serving_socket(options)
128
+ port = options[:port] || 0
129
+ interface_ip = options[:ip] || '0.0.0.0'
130
+ serving_address = Socket.pack_sockaddr_in port, interface_ip
131
+
132
+ socket = Socket.new Socket::AF_INET, Socket::SOCK_STREAM,
133
+ Socket::PF_UNSPEC
134
+
135
+ if options[:reusable]
136
+ socket.setsockopt Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true
137
+ end
138
+ socket.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true
139
+ socket.bind serving_address
140
+ socket.listen 5
141
+ socket
142
+ end
143
+ private :serving_socket
144
+
145
+ # Performs a request/response cycle.
146
+ #
147
+ # :call-seq:
148
+ # server.process_request(socket) -> Boolean
149
+ #
150
+ # Returns true if the server should do another request/response cycle, or
151
+ # false if this client indicated it's done talking to the server.
152
+ def process_request(socket)
153
+ return false unless request = recv_message(socket)
154
+
155
+ case request[:type]
156
+ when 0
157
+ # Wait for card; no-op, because that should have happen when the client
158
+ # connected.
159
+ send_message socket, :type => 0, :node => 0, :data => [3, 1, 4, 1, 5, 9]
160
+ when 1
161
+ # ATR exchange; the class' bread and butter
162
+ response = @logic.exchange_apdu request[:data]
163
+ send_message socket, :type => 1, :node => 0, :data => response
164
+ else
165
+ send_message socket, :type => request[:type], :node => 0, :data => []
166
+ end
167
+ end
168
+ private :process_request
169
+ end # module JcopRemoteServer
170
+
171
+ end # namespace Tem::Transport
@@ -0,0 +1,65 @@
1
+ require 'socket'
2
+
3
+ # :nodoc: namespace
4
+ module Tem::Transport
5
+
6
+ # Implements the transport layer for a JCOP simulator instance.
7
+ class JcopRemoteTransport
8
+ include JavaCardMixin
9
+ include JcopRemoteProtocol
10
+
11
+ # Creates a new unconnected transport for a JCOP simulator serving TCP/IP.
12
+ #
13
+ # The options parameter must have the following keys:
14
+ # host:: the DNS name or IP of the host running the JCOP simulator
15
+ # port:: the TCP/IP port of the JCOP simulator server
16
+ def initialize(options)
17
+ @host, @port = options[:host], options[:port]
18
+ @socket = nil
19
+ end
20
+
21
+ #
22
+ def exchange_apdu(apdu)
23
+ send_message @socket, :type => 1, :node => 0, :data => apdu
24
+ recv_message(@socket)[:data]
25
+ end
26
+
27
+ # Makes a transport-level connection to the TEM.
28
+ def connect
29
+ begin
30
+ Socket.getaddrinfo(@host, @port, Socket::AF_INET,
31
+ Socket::SOCK_STREAM).each do |addr_info|
32
+ begin
33
+ @socket = Socket.new(addr_info[4], addr_info[5], addr_info[6])
34
+ @socket.connect Socket.pack_sockaddr_in(addr_info[1], addr_info[3])
35
+ break
36
+ rescue
37
+ @socket = nil
38
+ end
39
+ end
40
+ raise 'Connection refused' unless @socket
41
+
42
+ # Wait for the card to be inserted.
43
+ send_message @socket, :type => 0, :node => 0, :data => [0, 1, 0, 0]
44
+ recv_message @socket # ATR should come here, but who cares
45
+ rescue Exception
46
+ @socket = nil
47
+ raise
48
+ end
49
+ end
50
+
51
+ # Breaks down the transport-level connection to the TEM.
52
+ def disconnect
53
+ if @socket
54
+ @socket.close
55
+ @socket = nil
56
+ end
57
+ end
58
+
59
+ def to_s
60
+ "#<JCOP Remote Terminal: disconnected>" if @socket.nil?
61
+ "#<JCOP Remote Terminal: #{@host}:#{@port}>"
62
+ end
63
+ end # class JcopRemoteTransport
64
+
65
+ end # module Tem::Transport