smartware 0.4.3 → 0.4.4

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.
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rubygems"
4
+ require "bundler/setup"
5
+ require "smartware"
6
+
7
+ def receive_event(event, *args)
8
+ device, event = event.split "."
9
+ return if device != "pin_pad"
10
+
11
+ case event
12
+ when "error"
13
+ type, = args
14
+ if type.nil? && !@reading
15
+ @reading = true
16
+ print "> "
17
+ Smartware.pin_pad.start_input(Smartware::Interface::PinPad::INPUT_PLAINTEXT)
18
+ elsif !type.nil? && @reading
19
+ exit_with_error "pinpad error #{type}"
20
+ end
21
+
22
+ when "input"
23
+ input_event *args
24
+ end
25
+ end
26
+
27
+ def input_event(type, data)
28
+ case type
29
+ when "cancel"
30
+ exit_with_error "cancelled"
31
+
32
+ when "accept"
33
+ print "\n"
34
+ STDERR.puts @linebuf
35
+ exit 0
36
+
37
+ when "input"
38
+ print data
39
+ @linebuf << data
40
+
41
+ when "backspace"
42
+ unless @linebuf.length == 0
43
+ print "\b \b"
44
+ @linebuf.slice! -1, 1
45
+ end
46
+ end
47
+ end
48
+
49
+ def exit_with_error(error)
50
+ print "\b \b" * (@linebuf.length * 2)
51
+ puts "smartware-readepp: #{error}"
52
+
53
+ exit 1
54
+ end
55
+
56
+ @reading = false
57
+ @linebuf = ""
58
+
59
+ EventMachine.run do
60
+ Smartware.subscribe &method(:receive_event)
61
+ end
data/lib/smartware.rb CHANGED
@@ -7,6 +7,7 @@ require 'stringio'
7
7
  require 'eventmachine'
8
8
  require 'serialport'
9
9
  require 'json'
10
+ require 'set'
10
11
 
11
12
  require 'smartkiosk/common'
12
13
 
@@ -14,17 +15,14 @@ require 'smartware/version'
14
15
  require 'smartware/logging'
15
16
  require 'smartware/service'
16
17
  require 'smartware/process_manager'
17
- require 'smartware/clients/cash_acceptor'
18
- require 'smartware/clients/printer'
19
- require 'smartware/clients/modem'
20
- require 'smartware/clients/watchdog'
21
- require 'smartware/clients/card_reader'
18
+ require 'smartware/client'
22
19
  require 'smartware/interfaces/interface'
23
20
  require 'smartware/interfaces/cash_acceptor'
24
21
  require 'smartware/interfaces/modem'
25
22
  require 'smartware/interfaces/printer'
26
23
  require 'smartware/interfaces/watchdog'
27
24
  require 'smartware/interfaces/card_reader'
25
+ require 'smartware/interfaces/pin_pad'
28
26
  require 'smartware/connection_monitor'
29
27
  require 'smartware/pub_sub_server'
30
28
  require 'smartware/pub_sub_client'
@@ -35,24 +33,28 @@ module Smartware
35
33
  yield self
36
34
  end
37
35
 
38
- def self.card_reader
39
- Smartware::Client::CardReader
36
+ def self.cash_acceptor
37
+ Smartware::Client.instance('druby://localhost:6001')
40
38
  end
41
39
 
42
- def self.cash_acceptor
43
- Smartware::Client::CashAcceptor
40
+ def self.modem
41
+ Smartware::Client.instance('druby://localhost:6002')
44
42
  end
45
43
 
46
- def self.printer
47
- Smartware::Client::Printer
44
+ def self.watchdog
45
+ Smartware::Client.instance('druby://localhost:6003')
48
46
  end
49
47
 
50
- def self.modem
51
- Smartware::Client::Modem
48
+ def self.card_reader
49
+ Smartware::Client.instance('druby://localhost:6004')
52
50
  end
53
51
 
54
- def self.watchdog
55
- Smartware::Client::Watchdog
52
+ def self.printer
53
+ Smartware::Client.instance('druby://localhost:6005')
54
+ end
55
+
56
+ def self.pin_pad
57
+ Smartware::Client.instance('druby://localhost:6006')
56
58
  end
57
59
 
58
60
  def self.subscribe(&block)
@@ -0,0 +1,14 @@
1
+ module Smartware
2
+ module Client
3
+ @@instances = {}
4
+
5
+ def self.instance(url)
6
+ if @@instances.include? url
7
+ @@instances[url]
8
+ else
9
+ instance = ::DRbObject.new_with_uri(url)
10
+ @@instances[url] = instance
11
+ end
12
+ end
13
+ end
14
+ end
@@ -39,6 +39,8 @@ module Smartware
39
39
  EventMachine.next_tick { check_dsr! }
40
40
  end
41
41
 
42
+ protected
43
+
42
44
  def check_dsr!
43
45
  if @io.dsr == 0
44
46
  @state = :drop
@@ -52,8 +54,6 @@ module Smartware
52
54
  end
53
55
  end
54
56
 
55
- protected
56
-
57
57
  def remove_timeouts
58
58
  unless @command_timer.nil?
59
59
  EventMachine.cancel_timer @command_timer
@@ -0,0 +1,184 @@
1
+ require "smartware/drivers/common/command_based_device"
2
+
3
+ module Smartware
4
+ class SZZTConnection < CommandBasedDevice
5
+ DESIRED_BAUD = 7
6
+ BAUD_TABLE = [ 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 ]
7
+
8
+ attr_accessor :baud_test_command, :baud_switch_command, :initialize_device
9
+ attr_accessor :handle_keypad, :dsr_fall
10
+
11
+ def initialize
12
+ super
13
+
14
+ @ready_for_commands = false
15
+
16
+ @command_timer = nil
17
+ @probing_baud = false
18
+ @baud_index = nil
19
+ @baud_test_command = nil
20
+ @baud_switch_command = nil
21
+ @initialize_device = nil
22
+ @handle_keypad = nil
23
+ @dsr_fall = nil
24
+
25
+ EventMachine.add_periodic_timer(1) { check_dsr! }
26
+ EventMachine.next_tick { check_dsr! }
27
+ end
28
+
29
+ protected
30
+
31
+ def check_dsr!
32
+ if @io.dsr == 0
33
+ if @ready_for_commands
34
+ Logging.logger.debug "SZZT: DSR low detected"
35
+
36
+ @state = :drop
37
+ kill_queue
38
+
39
+ @dsr_fall.call
40
+ end
41
+ else
42
+ was_ready, @ready_for_commands = @ready_for_commands, true
43
+
44
+ if !was_ready
45
+ @probing_baud = true
46
+ @baud_index = -1
47
+ Logging.logger.debug "SZZT: starting baud probe"
48
+ try_next_baud
49
+ end
50
+ end
51
+ end
52
+
53
+ def try_next_baud
54
+ @baud_index = BAUD_TABLE.length - 1 if @baud_index == -1
55
+ @io.baud = BAUD_TABLE[@baud_index]
56
+
57
+ Logging.logger.debug "SZZT: trying baud #{@io.baud}"
58
+ command(@baud_test_command) do |resp|
59
+ if resp.nil?
60
+ @baud_index -= 1
61
+ try_next_baud
62
+ else
63
+ Logging.logger.debug "SZZT: found baud rate: #{BAUD_TABLE[@baud_index]}"
64
+
65
+ if @baud_index != DESIRED_BAUD
66
+ Logging.logger.debug "SZZT: changing baud rate: #{BAUD_TABLE[DESIRED_BAUD]}"
67
+
68
+ cmd = @baud_switch_command.call(DESIRED_BAUD)
69
+ command(cmd) do |resp|
70
+ EventMachine.add_timer(0.25) do
71
+ Logging.logger.debug "SZZT: checking baud"
72
+ @baud_index = DESIRED_BAUD
73
+ try_next_baud
74
+ end
75
+ end
76
+ else
77
+ Logging.logger.debug "SZZT: probe completed"
78
+ @probing_baud = false
79
+
80
+ EventMachine.defer @initialize_device
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ def remove_timeouts
87
+ unless @command_timer.nil?
88
+ EventMachine.cancel_timer @command_timer
89
+ @command_timer = nil
90
+ end
91
+ end
92
+
93
+
94
+ def install_timeouts
95
+ if @probing_baud
96
+ interval = 0.25
97
+ else
98
+ interval = 1
99
+ end
100
+
101
+ @command_timer = EventMachine.add_timer(interval) do
102
+ @command_timer = nil
103
+ retry_or_fail
104
+ post_command unless @executing_command
105
+ end
106
+ end
107
+
108
+ def max_retries
109
+ if @probing_baud
110
+ 0
111
+ else
112
+ 4
113
+ end
114
+ end
115
+
116
+ def calculate_crc(string)
117
+ string.bytes.reduce 0, :^
118
+ end
119
+
120
+ def submit_command(text)
121
+ command = sprintf "%03u%s\x03", text.length, text
122
+ checksum = calculate_crc command
123
+
124
+ send_data sprintf("\x02%s%02X", command, checksum)
125
+ end
126
+
127
+ def keypad(char)
128
+ begin
129
+ EventMachine.defer ->() do
130
+ @handle_keypad.call char
131
+ end
132
+ rescue => e
133
+ Logging.logger.error "handle_keypad failed: #{e}"
134
+ e.backtrace.each { |line| Logging.logger.error line }
135
+ end
136
+ end
137
+
138
+ def handle_response
139
+ until @buffer.empty?
140
+ sync_index = @buffer.index "\x02"
141
+
142
+ if sync_index != 0
143
+ if sync_index.nil?
144
+ presses = @buffer
145
+ @buffer = ""
146
+ else
147
+ presses = @buffer.slice! 0, sync_index
148
+ end
149
+
150
+ presses.each_char &method(:keypad)
151
+ end
152
+
153
+ break if sync_index.nil? || @buffer.length < 6
154
+
155
+ len = @buffer[1..3].to_i
156
+ full_length = 7 + len
157
+ break if @buffer.length < full_length
158
+
159
+ response = @buffer.slice! 0, full_length
160
+ crc = calculate_crc response[1...full_length - 2]
161
+ if response[-3] != "\x03"
162
+ retry_or_fail
163
+
164
+ next
165
+ end
166
+
167
+ if response[-2..-1].to_i(16) != crc
168
+ retry_or_fail
169
+
170
+ next
171
+
172
+ end
173
+
174
+ if @executing_command
175
+ complete response[4..-4]
176
+ else
177
+ Logging.logger.warn "SZZT: unexpected frame: #{response[4..-4].inspect}"
178
+ end
179
+ end
180
+
181
+ post_command
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,497 @@
1
+ require "smartware/drivers/common/szzt_connection"
2
+ require "openssl"
3
+
4
+ module Smartware
5
+ module Driver
6
+ module PinPad
7
+ class ZT588
8
+
9
+ class ZT588Error < Interface::PinPad::PinPadError; end
10
+
11
+ # Commands
12
+ LOAD_PLAIN_KEY = '1'
13
+ LOAD_ENCRYPTED_KEY = '2'
14
+ USER_INFORMATION = '3'
15
+ LOAD_SCANCODE_TABLE = '4'
16
+ CONTROL = '5'
17
+ START_PIN_INPUT = '6'
18
+ GET_PIN_VALUE = '7'
19
+ AUTH = ':'
20
+
21
+ # Control commands and values
22
+ MISC = '0'
23
+ RESET = '0'
24
+ WIPE = '1'
25
+ DISABLE_INPUT = '2'
26
+ ENABLE_INPUT = '3'
27
+ DISABLE_SOUND = '4'
28
+ ENABLE_SOUND = '5'
29
+ BEEP = '='
30
+ SET_INPUT_TIME_LIMIT = '2'
31
+ SET_PIN_MASK = '3'
32
+ SET_MIN_LENGTH = '4'
33
+ SET_MAX_LENGTH = '5'
34
+ SET_COMMUNICATION = '6'
35
+ SET_BROKEN_HEX = '0'
36
+ SET_HEX = '7'
37
+ SET_1200 = 'H'
38
+ SET_2400 = 'I'
39
+ SET_4800 = 'J'
40
+ SET_9600 = 'K'
41
+ SET_19200 = 'L'
42
+ SET_38400 = 'M'
43
+ SET_57600 = 'N'
44
+ SET_115200 = 'O'
45
+ SET_BEEP_TIME = '8'
46
+
47
+ # Authentication commands
48
+ AUTH_GET_CHALLENGE = 0xA0
49
+ AUTH_WITH_TMK = 0x90
50
+ AUTH_WITH_IMK = 0x91
51
+
52
+ # Settings layout data
53
+ REVERSE_BAUD_TABLE = {
54
+ 48 => 1200,
55
+ 49 => 2400,
56
+ 50 => 4800,
57
+ 51 => 9600,
58
+ 52 => 19200,
59
+ 53 => 38400,
60
+ 54 => 57600,
61
+ 55 => 115200
62
+ }
63
+
64
+ # Authentication data
65
+ UID = "0000000000000000"
66
+
67
+ # Keys
68
+ KEY_TMK = 0x00
69
+ KEY_IMK = 0x80
70
+
71
+ # Key types, etc
72
+ KEY_TYPE_IMK = 1
73
+ KEY_TYPE_TMK = 2
74
+ KEY_TYPE_TPK = 3
75
+
76
+ KEY_TYPE_TAK = 5
77
+ KEY_TYPE_TDK = 6
78
+ KEY_TYPE_TDEK = 7
79
+ KEY_TYPE_TDDK = 8
80
+ KEY_TYPE_TDSK = 9
81
+
82
+ KEY_LENGTH_SINGLE = 0
83
+ KEY_LENGTH_DOUBLE = 1
84
+ KEY_LENGTH_TRIPLE = 2
85
+
86
+ PIN_TYPE_MAP = {
87
+ Smartware::Interface::PinPad::ASCII => '@',
88
+ Smartware::Interface::PinPad::ISO9564_0 => ' ',
89
+ Smartware::Interface::PinPad::ISO9564_1 => '!',
90
+ Smartware::Interface::PinPad::ISO9564_3 => '#',
91
+ Smartware::Interface::PinPad::IBM3624 => '0'
92
+ }
93
+
94
+ DEFAULT_CONFIG = {
95
+ "sound" => true,
96
+ "input_time_limit" => 30,
97
+ "minimum_pin_length" => 1,
98
+ "maximum_pin_length" => 16,
99
+ "beep_time" => 0.1
100
+ }
101
+
102
+ # ZT88 keyboard matrix:
103
+ # [1 ] [2 ] [3 ] [cancel]
104
+ # [4 ] [5 ] [6 ] [clear ]
105
+ # [7 ] [8 ] [9 ] [ ]
106
+ # [. ] [0 ] [00] [enter ]
107
+ # [A ] [C ] [E ] [G ]
108
+ # [B ] [D ] [F ] [H ]
109
+ #
110
+ # A-D - left-side application keys,
111
+ # E-H - right-side application keys.
112
+ SCANCODES = %W{
113
+ 1 2 3 \e
114
+ 4 5 6 \b
115
+ 7 8 9 \a
116
+ . 0 # \r
117
+ A C E G
118
+ B D F H
119
+ }.join
120
+
121
+ ERRORS = {
122
+ 0xE0 => "Low battery",
123
+ 0xE1 => "IMK required",
124
+ 0xE2 => "TMK required",
125
+ 0xE3 => "Unexpected key size",
126
+ 0xE4 => "Key not found",
127
+ 0xE5 => "Key not found or not compatible",
128
+ 0xE6 => "Key parity check failed",
129
+ 0xE7 => "Key is not valid",
130
+ 0xE8 => "Unexpected command length",
131
+ 0xE9 => "Incorrect data",
132
+ 0xEB => "Incorrect parameter",
133
+ 0xEC => "Authorization required",
134
+ 0xED => "Authorization temporary locked",
135
+ 0xEE => "Input timed out",
136
+ 0xEF => "General error"
137
+ }
138
+
139
+ attr_accessor :imk_source, :post_configuration, :device_ready
140
+ attr_accessor :device_not_ready, :input_event
141
+ attr_reader :user_data, :model, :version
142
+
143
+ def initialize(config)
144
+ @config = DEFAULT_CONFIG.merge(config)
145
+ @plain_input = false
146
+ @auto_stop = nil
147
+
148
+ @port = SerialPort.new(config["port"], 9600, 8, 1, SerialPort::NONE)
149
+ @port.flow_control = SerialPort::HARD
150
+
151
+ @connection = EventMachine.attach @port, SZZTConnection
152
+ @connection.baud_test_command = '3'
153
+ @connection.baud_switch_command = ->(baud) {
154
+ sprintf('56%d', baud + 41)
155
+ }
156
+ @connection.dsr_fall = method :dsr_fall
157
+ @connection.initialize_device = method :initialize_device
158
+ @connection.handle_keypad = method :handle_keypad
159
+
160
+ @imk_source = nil
161
+ @post_configuration = nil
162
+ @device_ready = nil
163
+ @device_not_ready = nil
164
+ @input_event = nil
165
+ end
166
+
167
+ def user_data=(data)
168
+ safe_command USER_INFORMATION, data
169
+ info = query_user_information
170
+ @user_data = info[:user_info]
171
+ end
172
+
173
+ def wipe
174
+ control MISC, WIPE
175
+ sleep 3
176
+ initialize_device true
177
+ end
178
+
179
+ def restart
180
+ control MISC, RESET
181
+ sleep 3
182
+ initialize_device
183
+ end
184
+
185
+ def start_input(mode, options = {})
186
+ case mode
187
+ when Interface::PinPad::INPUT_PLAINTEXT
188
+ @plain_input = true
189
+ @auto_stop = nil
190
+ control MISC, ENABLE_INPUT
191
+
192
+ when Interface::PinPad::INPUT_PIN
193
+ tpk = 0x40 + 8 * options[:key_set]
194
+ @plain_input = false
195
+ @auto_stop = options[:length]
196
+
197
+ start_pin_input tpk, options[:format], 0, options[:length],
198
+ options[:pan]
199
+
200
+ else
201
+ raise ZT588Error, "unsupported input mode: #{mode}"
202
+ end
203
+
204
+ @input_event.call :start
205
+ end
206
+
207
+ def stop_input
208
+ do_stop_input
209
+ @input_event.call :cancel
210
+ end
211
+
212
+ def start_pin_input(key, format, hint_code, length, pan)
213
+ raise "unsupported PIN block format" unless PIN_TYPE_MAP.include? format
214
+
215
+ safe_command(START_PIN_INPUT,
216
+ sprintf("%02X%c%d%02d%s",
217
+ key,
218
+ PIN_TYPE_MAP[format],
219
+ hint_code,
220
+ length,
221
+ pan))
222
+ end
223
+
224
+ def load_working_keys(set, tpk_under_tmk)
225
+ raise "unsupported key set" unless (0..7).include? set
226
+
227
+ tpk = 0x40 + 8 * set
228
+ tpk_verify = load_encrypted_key tpk, KEY_TMK, KEY_TYPE_TPK, nil,
229
+ tpk_under_tmk
230
+
231
+ return tpk_verify
232
+ end
233
+
234
+ def get_pin
235
+ response = safe_command GET_PIN_VALUE
236
+
237
+ p response
238
+
239
+ [
240
+ response.slice(1, 2).to_i, # Track
241
+ response.slice(3, 2).to_i, # Length
242
+ bin(response[5..-1]) # Block
243
+ ]
244
+ end
245
+
246
+ private
247
+
248
+ def calculate_response(challenge, key)
249
+ cipher = OpenSSL::Cipher.new('DES-ECB')
250
+ cipher.encrypt
251
+ cipher.padding = 0
252
+ cipher.key = key
253
+ cipher.update(challenge) + cipher.final
254
+ end
255
+
256
+ def auth(command, data)
257
+ response = safe_command AUTH, sprintf("%02X", command), hex(data)
258
+ bin response[1..-1]
259
+ end
260
+
261
+ def probe_length(data)
262
+ case data.length
263
+ when 8
264
+ KEY_LENGTH_SINGLE
265
+
266
+ when 16
267
+ KEY_LENGTH_DOUBLE
268
+
269
+ when 24
270
+ KEY_LENGTH_TRIPLE
271
+
272
+ else
273
+ raise "unsupported key length: #{data.bytes}"
274
+ end
275
+ end
276
+
277
+ def erase_key(key)
278
+ safe_command LOAD_ENCRYPTED_KEY, sprintf("%02X%02X%d%d", 0, key, 0, 0)
279
+ nil
280
+ end
281
+
282
+ def load_encrypted_key(key, under, type, length, data)
283
+ length = probe_length(data) if length.nil?
284
+ response = safe_command(LOAD_ENCRYPTED_KEY,
285
+ sprintf("%02X%02X%d%d", under, key, type, length + 1),
286
+ hex(data))
287
+ bin response[1..-1]
288
+ end
289
+
290
+ def load_plain_key(key_index, key_type, length, data)
291
+ length = probe_length(data) if length.nil?
292
+ response = safe_command(LOAD_PLAIN_KEY,
293
+ sprintf("%02X%u%u", key_index, key_type, length),
294
+ hex(data))
295
+
296
+ verify = bin response[1..-1]
297
+
298
+ zeroes = "\x00" * data.length
299
+
300
+ cipher = OpenSSL::Cipher.new('DES-ECB')
301
+ cipher.reset
302
+ cipher.encrypt
303
+ cipher.padding = 0
304
+ cipher.key = data
305
+ check = cipher.update(zeroes) + cipher.final
306
+
307
+ if check.slice(0, verify.length) != verify
308
+ raise ZT588Error, "Plaintext key validation failed"
309
+ end
310
+
311
+ verify
312
+ end
313
+
314
+ def parse_settings(data)
315
+ bytes = data.unpack("C*")
316
+
317
+ {
318
+ blank: bytes[0] & 0x01 == 0x01,
319
+ input: bytes[0] & 0x02 == 0x02,
320
+ sound: bytes[0] & 0x04 == 0x04,
321
+
322
+ input_time: bytes[2],
323
+ pin_mask: bytes[3].chr,
324
+ min_length: bytes[4],
325
+ max_length: bytes[5],
326
+ baud: REVERSE_BAUD_TABLE[bytes[6]],
327
+ broken_hex: bytes[7].chr == SET_BROKEN_HEX,
328
+ beep_time: bytes[8] / 100.0,
329
+
330
+ raw: bytes
331
+ }
332
+ end
333
+
334
+ def query_user_information
335
+ info = safe_command USER_INFORMATION
336
+
337
+ parts = info.unpack("xa50a48a20a8A6A3A3a4a4")
338
+
339
+ {
340
+ user_info: parts[0],
341
+ scancodes: bin(parts[1]),
342
+ settings: parse_settings(bin(parts[2])),
343
+ function_code: parts[3],
344
+ model: parts[4],
345
+ hardware_version: parts[5],
346
+ software_version: parts[6],
347
+ production_date: parts[7],
348
+ serial: parts[8],
349
+ }
350
+ end
351
+
352
+ def dsr_fall
353
+ @device_not_ready.call
354
+ end
355
+
356
+ def initialize_device(reload = false)
357
+ Logging.logger.debug "ZT588: initializing"
358
+
359
+ control SET_COMMUNICATION, SET_HEX
360
+
361
+ info = query_user_information
362
+
363
+ @model = info[:model]
364
+ @version = "#{info[:hardware_version]}-#{info[:software_version]}"
365
+ @user_data = info[:user_info]
366
+
367
+ Logging.logger.debug "ZT588: It's #{@model}-#{@version}"
368
+ Logging.logger.debug "ZT588: Production date: #{info[:production_date]}, serial number: #{info[:serial]}"
369
+
370
+ safe_command LOAD_SCANCODE_TABLE, hex(SCANCODES)
371
+ control MISC, @config["sound"] ? ENABLE_SOUND : DISABLE_SOUND
372
+ control SET_INPUT_TIME_LIMIT, @config["input_time_limit"].chr
373
+ control SET_PIN_MASK, "\xFF"
374
+ control SET_MIN_LENGTH, @config["minimum_pin_length"].chr
375
+ control SET_MAX_LENGTH, @config["maximum_pin_length"].chr
376
+ control SET_BEEP_TIME, (@config["beep_time"] * 100).round.chr
377
+
378
+ if info[:settings][:input]
379
+ control MISC, DISABLE_INPUT
380
+ end
381
+
382
+ if !reload
383
+ if info[:settings][:blank]
384
+ Logging.logger.warn "ZT588: IMK not loaded, pinpad unoperational"
385
+
386
+ imk, tmk = @imk_source.call
387
+
388
+ return if imk.nil?
389
+
390
+ wipe
391
+ load_plain_key KEY_IMK, KEY_TYPE_IMK, KEY_LENGTH_TRIPLE, imk
392
+ imk.slice! 16
393
+
394
+ challenge = auth AUTH_GET_CHALLENGE, "0000000000000000"
395
+ response = calculate_response challenge, imk.slice(0, 16)
396
+ check = calculate_response response, imk.slice(0, 16)
397
+ verify = auth AUTH_WITH_IMK, response
398
+ raise ZT588Error, "verification failed" if check != verify
399
+
400
+ # it's likely that TMK is actually IMK, and IMK is something else
401
+ load_plain_key KEY_TMK, KEY_TYPE_IMK, KEY_LENGTH_DOUBLE, tmk
402
+ @post_configuration.call
403
+
404
+ restart
405
+ else
406
+ random = auth AUTH_GET_CHALLENGE, "0000000000000000"
407
+ challenge = auth KEY_TMK, random
408
+ response = calculate_response challenge, UID
409
+ check = calculate_response response, UID
410
+ verify = auth AUTH_WITH_TMK, response
411
+ raise ZT588Error, "verification failed" if check != verify
412
+ Logging.logger.debug "ZT588: authenticated"
413
+
414
+ @device_ready.call
415
+ end
416
+ end
417
+
418
+ rescue => e
419
+ Logging.logger.error "initialize_device failed: #{e}"
420
+ e.backtrace.each { |line| Logging.logger.error line }
421
+ end
422
+
423
+ def do_stop_input
424
+ @plain_input = false
425
+ @auto_stop = nil
426
+ control MISC, DISABLE_INPUT
427
+ end
428
+
429
+ def do_auto_stop(chars = 1)
430
+ unless @auto_stop.nil?
431
+ @auto_stop -= chars
432
+ if @auto_stop <= 0
433
+ @plain_input = false
434
+ @auto_stop = nil
435
+ @input_event.call :accept
436
+ end
437
+ end
438
+ end
439
+
440
+ def handle_keypad(char)
441
+ case char
442
+ when "\e", "\x80"
443
+ do_stop_input if @plain_input
444
+ @input_event.call :cancel
445
+
446
+ when "\b"
447
+ @input_event.call :backspace
448
+
449
+ when "\r"
450
+ do_stop_input if @plain_input
451
+ @input_event.call :accept
452
+
453
+ when "\a", 'A'..'H'
454
+ # unlabeled button and application buttons
455
+
456
+ when '#'
457
+ @input_event.call :input, '0'
458
+ @input_event.call :input, '0'
459
+ do_auto_stop 2
460
+
461
+ else
462
+ @input_event.call :input, char
463
+ do_auto_stop 1
464
+ end
465
+
466
+ end
467
+
468
+ def control(parameter, value)
469
+ safe_command CONTROL, parameter, hex(value)
470
+ end
471
+
472
+ def safe_command(*parts)
473
+ response = @connection.command parts.join
474
+ raise ZT588Error, "Communication error" if response.nil?
475
+
476
+ code = response.getbyte 0
477
+ if code >= 0xE0 && code <= 0xEF
478
+ description = ERRORS[code] || "unknown error #{code.to_s 16}"
479
+ raise ZT588Error, description
480
+ end
481
+
482
+ response
483
+ end
484
+
485
+ def hex(binary)
486
+ hex, = binary.unpack("H*")
487
+ hex.upcase!
488
+ hex
489
+ end
490
+
491
+ def bin(hex)
492
+ [ hex ].pack("H*")
493
+ end
494
+ end
495
+ end
496
+ end
497
+ end