smartware 0.4.3 → 0.4.4

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