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.
- data/bin/smartware-readepp +61 -0
- data/lib/smartware.rb +17 -15
- data/lib/smartware/client.rb +14 -0
- data/lib/smartware/drivers/common/sankyo_connection.rb +2 -2
- data/lib/smartware/drivers/common/szzt_connection.rb +184 -0
- data/lib/smartware/drivers/pin_pad/zt588.rb +497 -0
- data/lib/smartware/drivers/printer/esc_pos.rb +57 -15
- data/lib/smartware/interfaces/cash_acceptor.rb +5 -5
- data/lib/smartware/interfaces/pin_pad.rb +90 -0
- data/lib/smartware/version.rb +1 -1
- data/smartware.gemspec +3 -3
- metadata +11 -8
- data/lib/smartware/clients/card_reader.rb +0 -41
- data/lib/smartware/clients/cash_acceptor.rb +0 -52
- data/lib/smartware/clients/modem.rb +0 -33
- data/lib/smartware/clients/printer.rb +0 -40
- data/lib/smartware/clients/watchdog.rb +0 -28
@@ -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/
|
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.
|
39
|
-
Smartware::Client
|
36
|
+
def self.cash_acceptor
|
37
|
+
Smartware::Client.instance('druby://localhost:6001')
|
40
38
|
end
|
41
39
|
|
42
|
-
def self.
|
43
|
-
Smartware::Client
|
40
|
+
def self.modem
|
41
|
+
Smartware::Client.instance('druby://localhost:6002')
|
44
42
|
end
|
45
43
|
|
46
|
-
def self.
|
47
|
-
Smartware::Client
|
44
|
+
def self.watchdog
|
45
|
+
Smartware::Client.instance('druby://localhost:6003')
|
48
46
|
end
|
49
47
|
|
50
|
-
def self.
|
51
|
-
Smartware::Client
|
48
|
+
def self.card_reader
|
49
|
+
Smartware::Client.instance('druby://localhost:6004')
|
52
50
|
end
|
53
51
|
|
54
|
-
def self.
|
55
|
-
Smartware::Client
|
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)
|
@@ -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
|