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