tem_ruby 0.9.2 → 0.10.0

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