rbzk 0.1.6 → 0.1.8
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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/bin/console +3 -3
- data/lib/rbzk/attendance.rb +19 -0
- data/lib/rbzk/cli/commands.rb +228 -214
- data/lib/rbzk/cli/config.rb +2 -2
- data/lib/rbzk/constants.rb +3 -3
- data/lib/rbzk/exceptions.rb +9 -3
- data/lib/rbzk/finger.rb +1 -1
- data/lib/rbzk/user.rb +11 -11
- data/lib/rbzk/version.rb +1 -1
- data/lib/rbzk/zk.rb +498 -651
- data/lib/rbzk.rb +7 -7
- metadata +8 -8
data/lib/rbzk/zk.rb
CHANGED
|
@@ -14,46 +14,45 @@ module RBZK
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def test_ping
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return true
|
|
22
|
-
end
|
|
23
|
-
rescue Timeout::Error, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError => e
|
|
24
|
-
return false
|
|
25
|
-
rescue => e
|
|
26
|
-
return false
|
|
17
|
+
Timeout.timeout(5) do
|
|
18
|
+
s = TCPSocket.new(@ip, @port)
|
|
19
|
+
s.close
|
|
20
|
+
return true
|
|
27
21
|
end
|
|
22
|
+
rescue Timeout::Error, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError
|
|
23
|
+
false
|
|
24
|
+
rescue StandardError
|
|
25
|
+
false
|
|
28
26
|
end
|
|
29
27
|
|
|
30
28
|
def test_tcp
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
client.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, [ 10, 0 ].pack('l_*'))
|
|
29
|
+
client = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM)
|
|
30
|
+
client.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, [ 10, 0 ].pack('l_*'))
|
|
34
31
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
client.close
|
|
46
|
-
return result
|
|
47
|
-
rescue => e
|
|
48
|
-
return e.errno || 1
|
|
32
|
+
sockaddr = Socket.pack_sockaddr_in(@port, @ip)
|
|
33
|
+
begin
|
|
34
|
+
client.connect(sockaddr)
|
|
35
|
+
result = 0 # Success code
|
|
36
|
+
rescue Errno::EISCONN
|
|
37
|
+
result = 0 # Already connected
|
|
38
|
+
rescue StandardError => e
|
|
39
|
+
# Some exceptions (e.g., Socket::ResolutionError) don't provide errno
|
|
40
|
+
result = e.respond_to?(:errno) ? e.errno : 1 # Connection failed
|
|
49
41
|
end
|
|
42
|
+
|
|
43
|
+
client.close
|
|
44
|
+
result
|
|
45
|
+
rescue StandardError => e
|
|
46
|
+
# Some exceptions (e.g., Socket::ResolutionError) don't provide errno
|
|
47
|
+
e.respond_to?(:errno) ? e.errno : 1
|
|
50
48
|
end
|
|
51
49
|
end
|
|
52
50
|
|
|
53
51
|
class ZK
|
|
54
52
|
include RBZK::Constants
|
|
55
53
|
|
|
56
|
-
def initialize(ip, port: 4370, timeout: 60, password: 0, force_udp: false, omit_ping: false, verbose: false,
|
|
54
|
+
def initialize(ip, port: 4370, timeout: 60, password: 0, force_udp: false, omit_ping: false, verbose: false,
|
|
55
|
+
encoding: 'UTF-8')
|
|
57
56
|
# Initialize the ZK device connection
|
|
58
57
|
RBZK::User.encoding = encoding
|
|
59
58
|
@address = [ ip, port ]
|
|
@@ -77,6 +76,8 @@ module RBZK
|
|
|
77
76
|
@data = nil
|
|
78
77
|
@connected = false
|
|
79
78
|
@next_uid = 1
|
|
79
|
+
@next_user_id = '1'
|
|
80
|
+
@user_packet_size = 28
|
|
80
81
|
|
|
81
82
|
# Storage for user and attendance data
|
|
82
83
|
@users = {}
|
|
@@ -102,22 +103,20 @@ module RBZK
|
|
|
102
103
|
# Create helper for ping and TCP tests
|
|
103
104
|
@helper = ZKHelper.new(ip, port)
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
return unless @verbose
|
|
107
|
+
|
|
108
|
+
puts "ZK instance created for device at #{@ip}:#{@port}"
|
|
109
|
+
puts "Using #{@force_udp ? 'UDP' : 'TCP'} mode"
|
|
109
110
|
end
|
|
110
111
|
|
|
111
112
|
def connect
|
|
112
113
|
return self if @connected
|
|
113
114
|
|
|
114
115
|
# Skip ping check if requested
|
|
115
|
-
if !@omit_ping && !@helper.test_ping
|
|
116
|
-
raise RBZK::ZKNetworkError, "Can't reach device (ping #{@ip})"
|
|
117
|
-
end
|
|
116
|
+
raise RBZK::ZKNetworkError, "Can't reach device (ping #{@ip})" if !@omit_ping && !@helper.test_ping
|
|
118
117
|
|
|
119
118
|
# Set user packet size if TCP connection is available
|
|
120
|
-
if !@force_udp && @helper.test_tcp
|
|
119
|
+
if !@force_udp && @helper.test_tcp.zero?
|
|
121
120
|
@user_packet_size = 72 # Default for ZK8
|
|
122
121
|
end
|
|
123
122
|
|
|
@@ -127,9 +126,7 @@ module RBZK
|
|
|
127
126
|
@session_id = 0
|
|
128
127
|
@reply_id = USHRT_MAX - 1
|
|
129
128
|
|
|
130
|
-
if @verbose
|
|
131
|
-
puts "Sending connect command to device"
|
|
132
|
-
end
|
|
129
|
+
puts 'Sending connect command to device' if @verbose
|
|
133
130
|
|
|
134
131
|
begin
|
|
135
132
|
cmd_response = send_command(CMD_CONNECT)
|
|
@@ -137,9 +134,7 @@ module RBZK
|
|
|
137
134
|
|
|
138
135
|
# Authenticate if needed
|
|
139
136
|
if cmd_response[:code] == CMD_ACK_UNAUTH
|
|
140
|
-
if @verbose
|
|
141
|
-
puts "try auth"
|
|
142
|
-
end
|
|
137
|
+
puts 'try auth' if @verbose
|
|
143
138
|
|
|
144
139
|
command_string = make_commkey(@password, @session_id)
|
|
145
140
|
cmd_response = send_command(CMD_AUTH, command_string)
|
|
@@ -148,23 +143,17 @@ module RBZK
|
|
|
148
143
|
# Check response status
|
|
149
144
|
if cmd_response[:status]
|
|
150
145
|
@connected = true
|
|
151
|
-
|
|
146
|
+
self
|
|
152
147
|
else
|
|
153
|
-
if cmd_response[:code] == CMD_ACK_UNAUTH
|
|
154
|
-
raise RBZK::ZKErrorResponse, "Unauthenticated"
|
|
155
|
-
end
|
|
148
|
+
raise RBZK::ZKErrorResponse, 'Unauthenticated' if cmd_response[:code] == CMD_ACK_UNAUTH
|
|
156
149
|
|
|
157
|
-
if @verbose
|
|
158
|
-
puts "Connect error response: #{cmd_response[:code]}"
|
|
159
|
-
end
|
|
150
|
+
puts "Connect error response: #{cmd_response[:code]}" if @verbose
|
|
160
151
|
|
|
161
152
|
raise RBZK::ZKErrorResponse, "Invalid response: Can't connect"
|
|
162
153
|
end
|
|
163
|
-
rescue => e
|
|
154
|
+
rescue StandardError => e
|
|
164
155
|
@connected = false
|
|
165
|
-
if @verbose
|
|
166
|
-
puts "Connection error: #{e.message}"
|
|
167
|
-
end
|
|
156
|
+
puts "Connection error: #{e.message}" if @verbose
|
|
168
157
|
raise e
|
|
169
158
|
end
|
|
170
159
|
end
|
|
@@ -176,11 +165,11 @@ module RBZK
|
|
|
176
165
|
def disconnect
|
|
177
166
|
return unless @connected
|
|
178
167
|
|
|
179
|
-
|
|
180
|
-
|
|
168
|
+
send_command(CMD_EXIT)
|
|
169
|
+
recv_reply
|
|
181
170
|
|
|
182
171
|
@connected = false
|
|
183
|
-
@socket
|
|
172
|
+
@socket&.close
|
|
184
173
|
|
|
185
174
|
@socket = nil
|
|
186
175
|
@tcp = nil
|
|
@@ -189,32 +178,28 @@ module RBZK
|
|
|
189
178
|
end
|
|
190
179
|
|
|
191
180
|
def enable_device
|
|
192
|
-
|
|
193
|
-
|
|
181
|
+
send_command(CMD_ENABLEDEVICE)
|
|
182
|
+
recv_reply
|
|
194
183
|
true
|
|
195
184
|
end
|
|
196
185
|
|
|
197
186
|
def disable_device
|
|
198
|
-
cmd_response =
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
raise RBZK::ZKErrorResponse, "Can't disable device"
|
|
204
|
-
end
|
|
187
|
+
cmd_response = send_command(CMD_DISABLEDEVICE)
|
|
188
|
+
raise RBZK::ZKErrorResponse, "Can't disable device" unless cmd_response[:status]
|
|
189
|
+
|
|
190
|
+
@is_enabled = false
|
|
191
|
+
true
|
|
205
192
|
end
|
|
206
193
|
|
|
207
194
|
def get_firmware_version
|
|
208
195
|
command = CMD_GET_VERSION
|
|
209
196
|
response_size = 1024
|
|
210
|
-
response =
|
|
197
|
+
response = send_command(command, '', response_size)
|
|
211
198
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
raise RBZK::ZKErrorResponse, "Can't read firmware version"
|
|
217
|
-
end
|
|
199
|
+
raise RBZK::ZKErrorResponse, "Can't read firmware version" unless response && response[:status]
|
|
200
|
+
|
|
201
|
+
firmware_version = @data.split("\x00")[0]
|
|
202
|
+
firmware_version.to_s
|
|
218
203
|
end
|
|
219
204
|
|
|
220
205
|
def get_serialnumber
|
|
@@ -222,15 +207,14 @@ module RBZK
|
|
|
222
207
|
command_string = "~SerialNumber\x00".b
|
|
223
208
|
response_size = 1024
|
|
224
209
|
|
|
225
|
-
response =
|
|
210
|
+
response = send_command(command, command_string, response_size)
|
|
226
211
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
end
|
|
212
|
+
raise RBZK::ZKErrorResponse, "Can't read serial number" unless response && response[:status]
|
|
213
|
+
|
|
214
|
+
serialnumber_field = @data.split('=', 2)[1]
|
|
215
|
+
serialnumber = serialnumber_field ? serialnumber_field.split("\x00")[0] : ''
|
|
216
|
+
serialnumber = serialnumber.gsub('=', '')
|
|
217
|
+
serialnumber.to_s
|
|
234
218
|
end
|
|
235
219
|
|
|
236
220
|
def get_mac
|
|
@@ -238,14 +222,12 @@ module RBZK
|
|
|
238
222
|
command_string = "MAC\x00".b
|
|
239
223
|
response_size = 1024
|
|
240
224
|
|
|
241
|
-
response =
|
|
225
|
+
response = send_command(command, command_string, response_size)
|
|
242
226
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
raise RBZK::ZKErrorResponse, "Can't read MAC address"
|
|
248
|
-
end
|
|
227
|
+
raise RBZK::ZKErrorResponse, "Can't read MAC address" unless response && response[:status]
|
|
228
|
+
|
|
229
|
+
mac = @data.split('=', 2)[1]&.split("\x00")&.[](0) || ''
|
|
230
|
+
mac.to_s
|
|
249
231
|
end
|
|
250
232
|
|
|
251
233
|
def get_device_name
|
|
@@ -253,13 +235,14 @@ module RBZK
|
|
|
253
235
|
command_string = "~DeviceName\x00".b
|
|
254
236
|
response_size = 1024
|
|
255
237
|
|
|
256
|
-
response =
|
|
238
|
+
response = send_command(command, command_string, response_size)
|
|
257
239
|
|
|
258
240
|
if response && response[:status]
|
|
259
|
-
|
|
241
|
+
device_field = @data.split('=', 2)[1]
|
|
242
|
+
device = device_field ? device_field.split("\x00")[0] : ''
|
|
260
243
|
device.to_s
|
|
261
244
|
else
|
|
262
|
-
|
|
245
|
+
''
|
|
263
246
|
end
|
|
264
247
|
end
|
|
265
248
|
|
|
@@ -268,13 +251,16 @@ module RBZK
|
|
|
268
251
|
command_string = "ZKFaceVersion\x00".b
|
|
269
252
|
response_size = 1024
|
|
270
253
|
|
|
271
|
-
response =
|
|
254
|
+
response = send_command(command, command_string, response_size)
|
|
272
255
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
256
|
+
return unless response && response[:status]
|
|
257
|
+
|
|
258
|
+
version_field = @data.split('=', 2)[1]
|
|
259
|
+
version = version_field ? version_field.split("\x00")[0] : ''
|
|
260
|
+
begin
|
|
261
|
+
version.to_i
|
|
262
|
+
rescue StandardError
|
|
263
|
+
0
|
|
278
264
|
end
|
|
279
265
|
end
|
|
280
266
|
|
|
@@ -283,13 +269,16 @@ module RBZK
|
|
|
283
269
|
command_string = "~ExtendFmt\x00".b
|
|
284
270
|
response_size = 1024
|
|
285
271
|
|
|
286
|
-
response =
|
|
272
|
+
response = send_command(command, command_string, response_size)
|
|
287
273
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
274
|
+
return unless response && response[:status]
|
|
275
|
+
|
|
276
|
+
fmt_field = @data.split('=', 2)[1]
|
|
277
|
+
fmt = fmt_field ? fmt_field.split("\x00")[0] : ''
|
|
278
|
+
begin
|
|
279
|
+
fmt.to_i
|
|
280
|
+
rescue StandardError
|
|
281
|
+
0
|
|
293
282
|
end
|
|
294
283
|
end
|
|
295
284
|
|
|
@@ -298,15 +287,14 @@ module RBZK
|
|
|
298
287
|
command_string = "~Platform\x00".b
|
|
299
288
|
response_size = 1024
|
|
300
289
|
|
|
301
|
-
response =
|
|
290
|
+
response = send_command(command, command_string, response_size)
|
|
302
291
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
end
|
|
292
|
+
raise RBZK::ZKErrorResponse, "Can't read platform name" unless response && response[:status]
|
|
293
|
+
|
|
294
|
+
platform_field = @data.split('=', 2)[1]
|
|
295
|
+
platform = platform_field ? platform_field.split("\x00")[0] : ''
|
|
296
|
+
platform = platform.gsub('=', '')
|
|
297
|
+
platform.to_s
|
|
310
298
|
end
|
|
311
299
|
|
|
312
300
|
def get_fp_version
|
|
@@ -314,32 +302,35 @@ module RBZK
|
|
|
314
302
|
command_string = "~ZKFPVersion\x00".b
|
|
315
303
|
response_size = 1024
|
|
316
304
|
|
|
317
|
-
response =
|
|
305
|
+
response = send_command(command, command_string, response_size)
|
|
318
306
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
307
|
+
raise RBZK::ZKErrorResponse, "Can't read fingerprint version" unless response && response[:status]
|
|
308
|
+
|
|
309
|
+
version_field = @data.split('=', 2)[1]
|
|
310
|
+
version = version_field ? version_field.split("\x00")[0] : ''
|
|
311
|
+
version = version.gsub('=', '')
|
|
312
|
+
begin
|
|
313
|
+
version.to_i
|
|
314
|
+
rescue StandardError
|
|
315
|
+
0
|
|
325
316
|
end
|
|
326
317
|
end
|
|
327
318
|
|
|
328
319
|
def restart
|
|
329
|
-
|
|
330
|
-
|
|
320
|
+
send_command(CMD_RESTART)
|
|
321
|
+
recv_reply
|
|
331
322
|
true
|
|
332
323
|
end
|
|
333
324
|
|
|
334
325
|
def poweroff
|
|
335
|
-
|
|
336
|
-
|
|
326
|
+
send_command(CMD_POWEROFF)
|
|
327
|
+
recv_reply
|
|
337
328
|
true
|
|
338
329
|
end
|
|
339
330
|
|
|
340
331
|
def test_voice(index = 0)
|
|
341
332
|
command_string = [ index ].pack('L<')
|
|
342
|
-
response =
|
|
333
|
+
response = send_command(CMD_TESTVOICE, command_string)
|
|
343
334
|
|
|
344
335
|
if response && response[:status]
|
|
345
336
|
true
|
|
@@ -352,20 +343,18 @@ module RBZK
|
|
|
352
343
|
# @param time [Integer] define delay in seconds
|
|
353
344
|
# @return [Boolean] true if successful, raises exception otherwise
|
|
354
345
|
def unlock(time = 3)
|
|
355
|
-
command_string = [time * 10].pack('L<')
|
|
356
|
-
response =
|
|
346
|
+
command_string = [ time * 10 ].pack('L<')
|
|
347
|
+
response = send_command(CMD_UNLOCK, command_string)
|
|
357
348
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
raise RBZK::ZKErrorResponse, "Can't open door"
|
|
362
|
-
end
|
|
349
|
+
raise RBZK::ZKErrorResponse, "Can't open door" unless response && response[:status]
|
|
350
|
+
|
|
351
|
+
true
|
|
363
352
|
end
|
|
364
353
|
|
|
365
354
|
# Get the lock state
|
|
366
355
|
# @return [Boolean] true if door is open, false otherwise
|
|
367
356
|
def get_lock_state
|
|
368
|
-
response =
|
|
357
|
+
response = send_command(CMD_DOORSTATE_RRQ)
|
|
369
358
|
|
|
370
359
|
if response && response[:status]
|
|
371
360
|
true
|
|
@@ -379,38 +368,32 @@ module RBZK
|
|
|
379
368
|
# @param text [String] text to write
|
|
380
369
|
# @return [Boolean] true if successful, raises exception otherwise
|
|
381
370
|
def write_lcd(line_number, text)
|
|
382
|
-
command_string = [line_number, 0].pack('s<c')
|
|
383
|
-
response =
|
|
371
|
+
command_string = "#{[ line_number, 0 ].pack('s<c')} #{text.encode(@encoding, invalid: :replace, undef: :replace)}"
|
|
372
|
+
response = send_command(CMD_WRITE_LCD, command_string)
|
|
384
373
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
raise RBZK::ZKErrorResponse, "Can't write lcd"
|
|
389
|
-
end
|
|
374
|
+
raise RBZK::ZKErrorResponse, "Can't write lcd" unless response && response[:status]
|
|
375
|
+
|
|
376
|
+
true
|
|
390
377
|
end
|
|
391
378
|
|
|
392
379
|
# Clear LCD
|
|
393
380
|
# @return [Boolean] true if successful, raises exception otherwise
|
|
394
381
|
def clear_lcd
|
|
395
|
-
response =
|
|
382
|
+
response = send_command(CMD_CLEAR_LCD)
|
|
396
383
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
raise RBZK::ZKErrorResponse, "Can't clear lcd"
|
|
401
|
-
end
|
|
384
|
+
raise RBZK::ZKErrorResponse, "Can't clear lcd" unless response && response[:status]
|
|
385
|
+
|
|
386
|
+
true
|
|
402
387
|
end
|
|
403
388
|
|
|
404
389
|
# Refresh the device data
|
|
405
390
|
# @return [Boolean] true if successful, raises exception otherwise
|
|
406
391
|
def refresh_data
|
|
407
|
-
response =
|
|
392
|
+
response = send_command(CMD_REFRESHDATA)
|
|
408
393
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
raise RBZK::ZKErrorResponse, "Can't refresh data"
|
|
413
|
-
end
|
|
394
|
+
raise RBZK::ZKErrorResponse, "Can't refresh data" unless response && response[:status]
|
|
395
|
+
|
|
396
|
+
true
|
|
414
397
|
end
|
|
415
398
|
|
|
416
399
|
# Create or update user by uid
|
|
@@ -423,23 +406,17 @@ module RBZK
|
|
|
423
406
|
# @param card [Integer] card number
|
|
424
407
|
# @return [Boolean] true if successful, raises exception otherwise
|
|
425
408
|
def set_user(uid: nil, name: '', privilege: 0, password: '', group_id: '', user_id: '', card: 0)
|
|
426
|
-
# If uid is not provided, use next_uid
|
|
427
409
|
if uid.nil?
|
|
410
|
+
ensure_user_metadata!
|
|
428
411
|
uid = @next_uid
|
|
429
|
-
if user_id.empty?
|
|
430
|
-
user_id = @next_user_id
|
|
431
|
-
end
|
|
412
|
+
user_id = @next_user_id if user_id.empty? && @next_user_id
|
|
432
413
|
end
|
|
433
414
|
|
|
434
|
-
# If
|
|
435
|
-
if user_id.empty?
|
|
436
|
-
user_id = uid.to_s # ZK6 needs uid2 == uid
|
|
437
|
-
end
|
|
415
|
+
# If uid is not provided, use next_uid
|
|
416
|
+
user_id = uid.to_s if user_id.nil? || user_id.empty? # ZK6 needs uid2 == uid
|
|
438
417
|
|
|
439
418
|
# Validate privilege
|
|
440
|
-
if privilege != USER_DEFAULT && privilege != USER_ADMIN
|
|
441
|
-
privilege = USER_DEFAULT
|
|
442
|
-
end
|
|
419
|
+
privilege = USER_DEFAULT if privilege != USER_DEFAULT && privilege != USER_ADMIN
|
|
443
420
|
privilege = privilege.to_i
|
|
444
421
|
|
|
445
422
|
# Create command string based on user_packet_size
|
|
@@ -447,73 +424,79 @@ module RBZK
|
|
|
447
424
|
group_id = 0 if group_id.empty?
|
|
448
425
|
|
|
449
426
|
begin
|
|
450
|
-
command_string = [uid, privilege].pack('S<C') +
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
rescue => e
|
|
455
|
-
if @verbose
|
|
456
|
-
puts "Error packing user: #{e.message}"
|
|
457
|
-
end
|
|
427
|
+
command_string = [ uid, privilege ].pack('S<C') +
|
|
428
|
+
password.encode(@encoding, invalid: :replace, undef: :replace).ljust(5, "\x00")[0...5] +
|
|
429
|
+
name.encode(@encoding, invalid: :replace, undef: :replace).ljust(8, "\x00")[0...8] +
|
|
430
|
+
[ card.to_i, 0, group_id.to_i, 0, user_id.to_i ].pack('L<CS<S<L<')
|
|
431
|
+
rescue StandardError => e
|
|
432
|
+
puts "Error packing user: #{e.message}" if @verbose
|
|
458
433
|
raise RBZK::ZKErrorResponse, "Can't pack user"
|
|
459
434
|
end
|
|
460
435
|
else
|
|
461
436
|
# For other firmware versions
|
|
462
437
|
name_pad = name.encode(@encoding, invalid: :replace, undef: :replace).ljust(24, "\x00")[0...24]
|
|
463
|
-
card_str = [card.to_i].pack('L<')[0...4]
|
|
464
|
-
command_string = [uid,
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
user_id.encode(@encoding, invalid: :replace, undef: :replace).ljust(24, "\x00")[0...24]
|
|
438
|
+
card_str = [ card.to_i ].pack('L<')[0...4]
|
|
439
|
+
command_string = "#{[ uid,
|
|
440
|
+
privilege ].pack('S<C')}#{password.encode(@encoding, invalid: :replace, undef: :replace).ljust(8,
|
|
441
|
+
"\x00")[0...8]}#{name_pad}#{card_str}\u0000#{group_id.encode(@encoding, invalid: :replace, undef: :replace).ljust(7,
|
|
442
|
+
"\x00")[0...7]}\u0000#{user_id.encode(@encoding, invalid: :replace, undef: :replace).ljust(
|
|
443
|
+
24, "\x00"
|
|
444
|
+
)[0...24]}"
|
|
471
445
|
end
|
|
472
446
|
|
|
473
447
|
# Send command
|
|
474
|
-
response =
|
|
448
|
+
response = send_command(CMD_USER_WRQ, command_string, 1024)
|
|
475
449
|
|
|
476
450
|
if response && response[:status]
|
|
477
451
|
# Update next_uid and next_user_id if necessary
|
|
478
|
-
|
|
479
|
-
if @next_uid == uid
|
|
480
|
-
|
|
481
|
-
end
|
|
482
|
-
if @next_user_id == user_id
|
|
483
|
-
@next_user_id = @next_uid.to_s
|
|
484
|
-
end
|
|
452
|
+
refresh_data
|
|
453
|
+
@next_uid += 1 if @next_uid == uid
|
|
454
|
+
@next_user_id = @next_uid.to_s if @next_user_id == user_id
|
|
485
455
|
true
|
|
486
456
|
else
|
|
487
|
-
|
|
457
|
+
code = response[:code] if response
|
|
458
|
+
data_msg = @data && !@data.empty? ? " Data: #{format_as_python_bytes(@data)}" : ''
|
|
459
|
+
raise RBZK::ZKErrorResponse, "Can't set user (device response: #{code || 'NO_CODE'})#{data_msg}"
|
|
488
460
|
end
|
|
489
461
|
end
|
|
490
462
|
|
|
463
|
+
def ensure_user_metadata!
|
|
464
|
+
@next_uid ||= 1
|
|
465
|
+
@next_user_id ||= '1'
|
|
466
|
+
@user_packet_size ||= 28
|
|
467
|
+
|
|
468
|
+
return unless @next_uid <= 1 || @next_user_id.nil? || @user_packet_size.nil?
|
|
469
|
+
|
|
470
|
+
get_users
|
|
471
|
+
rescue StandardError => e
|
|
472
|
+
puts "Warning: unable to refresh user metadata before creating user: #{e.message}" if @verbose
|
|
473
|
+
@next_uid ||= 1
|
|
474
|
+
@next_user_id ||= '1'
|
|
475
|
+
@user_packet_size ||= 28
|
|
476
|
+
end
|
|
477
|
+
private :ensure_user_metadata!
|
|
478
|
+
|
|
491
479
|
# Delete user by uid
|
|
492
480
|
# @param uid [Integer] user ID that are generated from device
|
|
493
481
|
# @return [Boolean] true if successful, raises exception otherwise
|
|
494
482
|
def delete_user(uid: 0)
|
|
495
483
|
# Send command
|
|
496
|
-
command_string = [uid].pack('S<')
|
|
497
|
-
response =
|
|
484
|
+
command_string = [ uid ].pack('S<')
|
|
485
|
+
response = send_command(CMD_DELETE_USER, command_string)
|
|
498
486
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
# Update next_uid if necessary
|
|
502
|
-
if uid == (@next_uid - 1)
|
|
503
|
-
@next_uid = uid
|
|
504
|
-
end
|
|
505
|
-
true
|
|
506
|
-
else
|
|
507
|
-
raise RBZK::ZKErrorResponse, "Can't delete user"
|
|
487
|
+
unless response && response[:status]
|
|
488
|
+
raise RBZK::ZKErrorResponse, "Can't delete user. User not found or other error."
|
|
508
489
|
end
|
|
490
|
+
|
|
491
|
+
refresh_data
|
|
492
|
+
# Update next_uid if necessary
|
|
493
|
+
@next_uid = uid if uid == (@next_uid - 1)
|
|
494
|
+
true
|
|
509
495
|
end
|
|
510
496
|
|
|
511
497
|
# Helper method to read data with buffer (ZK6: 1503)
|
|
512
498
|
def read_with_buffer(command, fct = 0, ext = 0)
|
|
513
|
-
|
|
514
|
-
if @verbose
|
|
515
|
-
puts "Reading data with buffer: command=#{command}, fct=#{fct}, ext=#{ext}"
|
|
516
|
-
end
|
|
499
|
+
puts "Reading data with buffer: command=#{command}, fct=#{fct}, ext=#{ext}" if @verbose
|
|
517
500
|
|
|
518
501
|
# Set max chunk size based on connection type
|
|
519
502
|
max_chunk = @tcp ? 0xFFc0 : 16 * 1024
|
|
@@ -523,54 +506,39 @@ module RBZK
|
|
|
523
506
|
# All in little-endian format
|
|
524
507
|
command_string = [ 1, command, fct, ext ].pack('cs<l<l<')
|
|
525
508
|
|
|
526
|
-
if @verbose
|
|
527
|
-
puts "Command string: #{python_format(command_string)}"
|
|
528
|
-
end
|
|
509
|
+
puts "Command string: #{python_format(command_string)}" if @verbose
|
|
529
510
|
|
|
530
511
|
# Send the command to prepare the buffer
|
|
531
512
|
response_size = 1024
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
response = self.send_command(CMD_PREPARE_BUFFER, command_string, response_size)
|
|
513
|
+
# local variables
|
|
514
|
+
response = send_command(CMD_PREPARE_BUFFER, command_string, response_size)
|
|
535
515
|
|
|
536
|
-
if !response || !response[:status]
|
|
537
|
-
raise RBZK::ZKErrorResponse, "Read with buffer not supported"
|
|
538
|
-
end
|
|
516
|
+
raise RBZK::ZKErrorResponse, 'Read with buffer not supported' if !response || !response[:status]
|
|
539
517
|
|
|
540
518
|
# Get data from the response
|
|
541
519
|
data = @data
|
|
542
520
|
|
|
543
|
-
if @verbose
|
|
544
|
-
puts "Received #{data.size} bytes of data"
|
|
545
|
-
end
|
|
521
|
+
puts "Received #{data.size} bytes of data" if @verbose
|
|
546
522
|
|
|
547
523
|
# Check if we need more data
|
|
548
524
|
if response[:code] == CMD_DATA
|
|
549
525
|
if @tcp
|
|
550
|
-
if @verbose
|
|
551
|
-
puts "DATA! is #{data.size} bytes, tcp length is #{@tcp_length}"
|
|
552
|
-
end
|
|
526
|
+
puts "DATA! is #{data.size} bytes, tcp length is #{@tcp_length}" if @verbose
|
|
553
527
|
|
|
554
528
|
if data.size < (@tcp_length - 8)
|
|
555
529
|
need = (@tcp_length - 8) - data.size
|
|
556
|
-
if @verbose
|
|
557
|
-
puts "need more data: #{need}"
|
|
558
|
-
end
|
|
530
|
+
puts "need more data: #{need}" if @verbose
|
|
559
531
|
|
|
560
532
|
# Receive more data to complete the buffer
|
|
561
533
|
more_data = receive_raw_data(need)
|
|
562
534
|
|
|
563
|
-
if @verbose
|
|
564
|
-
puts "Read #{more_data.size} more bytes"
|
|
565
|
-
end
|
|
535
|
+
puts "Read #{more_data.size} more bytes" if @verbose
|
|
566
536
|
|
|
567
537
|
# Combine the data
|
|
568
538
|
result = data + more_data
|
|
569
539
|
return result, data.size + more_data.size
|
|
570
540
|
else
|
|
571
|
-
if @verbose
|
|
572
|
-
puts "Enough data"
|
|
573
|
-
end
|
|
541
|
+
puts 'Enough data' if @verbose
|
|
574
542
|
size = data.size
|
|
575
543
|
return data, size
|
|
576
544
|
end
|
|
@@ -581,38 +549,30 @@ module RBZK
|
|
|
581
549
|
end
|
|
582
550
|
|
|
583
551
|
# Get the size from the first 4 bytes (unsigned long, little-endian)
|
|
584
|
-
size = data[1..4].
|
|
552
|
+
size = data[1..4].unpack1('L<')
|
|
585
553
|
|
|
586
|
-
if @verbose
|
|
587
|
-
puts "size fill be #{size}"
|
|
588
|
-
end
|
|
554
|
+
puts "size fill be #{size}" if @verbose
|
|
589
555
|
|
|
590
556
|
# Calculate chunks
|
|
591
557
|
remain = size % max_chunk
|
|
592
558
|
# Calculate number of full-sized packets (integer division)
|
|
593
559
|
packets = (size - remain).div(max_chunk)
|
|
594
560
|
|
|
595
|
-
if @verbose
|
|
596
|
-
puts "rwb: ##{packets} packets of max #{max_chunk} bytes, and extra #{remain} bytes remain"
|
|
597
|
-
end
|
|
561
|
+
puts "rwb: ##{packets} packets of max #{max_chunk} bytes, and extra #{remain} bytes remain" if @verbose
|
|
598
562
|
|
|
599
563
|
# Read chunks
|
|
600
564
|
result_data = []
|
|
601
565
|
start = 0
|
|
602
566
|
|
|
603
567
|
packets.times do
|
|
604
|
-
if @verbose
|
|
605
|
-
puts "recieve chunk: prepare data size is #{max_chunk}"
|
|
606
|
-
end
|
|
568
|
+
puts "recieve chunk: prepare data size is #{max_chunk}" if @verbose
|
|
607
569
|
chunk = read_chunk(start, max_chunk)
|
|
608
570
|
result_data << chunk
|
|
609
571
|
start += max_chunk
|
|
610
572
|
end
|
|
611
573
|
|
|
612
|
-
if remain
|
|
613
|
-
if @verbose
|
|
614
|
-
puts "recieve chunk: prepare data size is #{remain}"
|
|
615
|
-
end
|
|
574
|
+
if remain.positive?
|
|
575
|
+
puts "recieve chunk: prepare data size is #{remain}" if @verbose
|
|
616
576
|
chunk = read_chunk(start, remain)
|
|
617
577
|
result_data << chunk
|
|
618
578
|
start += remain
|
|
@@ -621,9 +581,7 @@ module RBZK
|
|
|
621
581
|
# Free data (equivalent to Python's self.free_data())
|
|
622
582
|
free_data
|
|
623
583
|
|
|
624
|
-
if @verbose
|
|
625
|
-
puts "_read w/chunk #{start} bytes"
|
|
626
|
-
end
|
|
584
|
+
puts "_read w/chunk #{start} bytes" if @verbose
|
|
627
585
|
|
|
628
586
|
# In Python: return b''.join(data), start
|
|
629
587
|
result = result_data.join
|
|
@@ -633,8 +591,8 @@ module RBZK
|
|
|
633
591
|
# Helper method to get data size from the current data
|
|
634
592
|
def get_data_size
|
|
635
593
|
if @data && @data.size >= 4
|
|
636
|
-
|
|
637
|
-
|
|
594
|
+
@data[0...4].unpack1('L<')
|
|
595
|
+
|
|
638
596
|
else
|
|
639
597
|
0
|
|
640
598
|
end
|
|
@@ -648,12 +606,12 @@ module RBZK
|
|
|
648
606
|
puts "tcp_length #{tcp_length}, size #{size}" if @verbose
|
|
649
607
|
|
|
650
608
|
if tcp_length <= 0
|
|
651
|
-
puts
|
|
652
|
-
return nil,
|
|
609
|
+
puts 'Incorrect tcp packet' if @verbose
|
|
610
|
+
return nil, ''.b
|
|
653
611
|
end
|
|
654
612
|
|
|
655
613
|
if (tcp_length - 8) < size
|
|
656
|
-
puts
|
|
614
|
+
puts 'tcp length too small... retrying' if @verbose
|
|
657
615
|
|
|
658
616
|
# Recursive call to handle smaller packet
|
|
659
617
|
resp, bh = receive_tcp_data(data_recv, tcp_length - 8)
|
|
@@ -682,7 +640,7 @@ module RBZK
|
|
|
682
640
|
|
|
683
641
|
# In Python: response = unpack('HHHH', data_recv[8:16])[0]
|
|
684
642
|
# This unpacks 4 shorts (8 bytes) but only uses the first one
|
|
685
|
-
response = data_recv[8...16].
|
|
643
|
+
response = data_recv[8...16].unpack1('S<S<S<S<')
|
|
686
644
|
|
|
687
645
|
if received >= (size + 32)
|
|
688
646
|
if response == CMD_DATA
|
|
@@ -690,28 +648,26 @@ module RBZK
|
|
|
690
648
|
|
|
691
649
|
puts "resp complete len #{resp.size}" if @verbose
|
|
692
650
|
|
|
693
|
-
|
|
651
|
+
[ resp, data_recv[(size + 16)..-1] ]
|
|
694
652
|
else
|
|
695
653
|
puts "incorrect response!!! #{response}" if @verbose
|
|
696
654
|
|
|
697
|
-
|
|
655
|
+
[ nil, ''.b ]
|
|
698
656
|
end
|
|
699
657
|
else
|
|
700
658
|
puts "try DATA incomplete (actual valid #{received - 16})" if @verbose
|
|
701
659
|
|
|
702
660
|
data << data_recv[16...(size + 16)]
|
|
703
661
|
size -= received - 16
|
|
704
|
-
broken_header =
|
|
662
|
+
broken_header = ''.b
|
|
705
663
|
|
|
706
|
-
if size
|
|
707
|
-
broken_header = data_recv[size
|
|
664
|
+
if size.negative?
|
|
665
|
+
broken_header = data_recv[size..-1]
|
|
708
666
|
|
|
709
|
-
if @verbose
|
|
710
|
-
puts "broken: #{broken_header.bytes.map { |b| format('%02x', b) }.join}"
|
|
711
|
-
end
|
|
667
|
+
puts "broken: #{broken_header.bytes.map { |b| format('%02x', b) }.join}" if @verbose
|
|
712
668
|
end
|
|
713
669
|
|
|
714
|
-
if size
|
|
670
|
+
if size.positive?
|
|
715
671
|
data_recv = receive_raw_data(size)
|
|
716
672
|
data << data_recv
|
|
717
673
|
end
|
|
@@ -730,14 +686,14 @@ module RBZK
|
|
|
730
686
|
need = (@tcp_length - 8) - @data.size
|
|
731
687
|
puts "need more data: #{need}" if @verbose
|
|
732
688
|
more_data = receive_raw_data(need)
|
|
733
|
-
|
|
689
|
+
@data + more_data
|
|
734
690
|
else
|
|
735
|
-
puts
|
|
736
|
-
|
|
691
|
+
puts 'Enough data' if @verbose
|
|
692
|
+
@data
|
|
737
693
|
end
|
|
738
694
|
else
|
|
739
695
|
puts "_rc len is #{@data.size}" if @verbose
|
|
740
|
-
|
|
696
|
+
@data
|
|
741
697
|
end
|
|
742
698
|
elsif @response == CMD_PREPARE_DATA
|
|
743
699
|
data = []
|
|
@@ -747,10 +703,10 @@ module RBZK
|
|
|
747
703
|
|
|
748
704
|
if @tcp
|
|
749
705
|
if @data.size >= (8 + size)
|
|
750
|
-
data_recv = @data[8
|
|
706
|
+
data_recv = @data[8..-1]
|
|
751
707
|
else
|
|
752
708
|
# [80, 80, 130, 125, 92, 7, 0, 0, 221, 5, 142, 172, 0, 0, 4, 0, 80, 7, 0, 0, 1, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 65, 98, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 49, 0, 0, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 2, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 65, 110, 97, 115, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 50, 0, 0, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 83, 111, 110, 100, 111, 115, 44, 65, 98, 117, 107, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 51, 0, 0, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 116, 97, 0, 111, 115, 44, 65, 98, 117, 107, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 52, 0, 0, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 97, 121, 115, 0, 115, 44, 65, 98, 117, 107, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 53, 0, 0, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 97, 119, 97, 110, 0, 44, 65, 98, 117, 107, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 54, 0, 0, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74, 101, 110, 97, 110, 0, 44, 65, 98, 117, 107, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 55, 0, 0, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 97, 114, 97, 104, 0, 44, 65, 98, 117, 107, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 56, 0, 0, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 83, 97, 98, 114, 101, 101, 110, 0, 98, 117, 107, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 57, 0, 0, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 83, 97, 101, 101, 100, 0, 110, 0, 98, 117, 107, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 49, 48, 0, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 111, 102, 114, 97, 110, 0, 0, 98, 117, 107, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 49, 49, 0, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 97, 110, 105, 97, 0, 0, 0, 98, 117, 107, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 49, 50, 0, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 83, 97, 109, 105, 97, 0, 0, 0, 98, 117, 107, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 49, 51, 0, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 101, 101, 109, 0, 0, 0, 0, 98, 117, 107, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 49, 52, 0, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 87, 97, 108, 97, 97, 0, 0, 0, 98, 117, 107, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 49, 53, 0, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 97, 110, 101, 101, 109, 0, 0, 98, 117, 107, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 49, 54, 0, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 79, 108, 97, 0, 101, 109, 0, 0, 98, 117, 107, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 49, 55, 0, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 97, 114, 97, 104, 0, 0, 0, 98, 117, 107, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 49, 56, 0, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 83, 111, 110, 100, 111, 115, 72, 65, 0, 117, 107, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 49, 57, 0, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 111, 0, 100, 111, 115, 72, 65, 0, 117, 107, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 49, 52, 53, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 119, 115, 0, 111, 115, 72, 65, 0, 117, 107, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 49, 52, 54, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 83, 97, 109, 97, 114, 97, 0, 65, 0, 117, 107, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 52, 53, 50, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 97, 115, 110, 101, 101, 109, 0, 0, 117, 107, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 50, 48, 0, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 69, 115, 114, 97, 97, 0, 109, 0, 0, 117, 107, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 50, 49, 0, 0, 1, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 83, 97, 114, 97, 106, 0, 109, 0, 0, 117, 107, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 50, 49, 51, 53, 0, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 108, 97, 97, 110, 97, 115, 115, 101, 114, 0, 104, 100, 97, 105, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 50, 50, 0, 53, 0, 0, 0, 0, 25, 0, 0, 0, 0, 254, 6, 119, 255, 255, 255, 255, 212, 10, 159, 127, 80, 80, 130, 125, 8, 0, 0, 0, 208, 7, 73, 81, 226, 166, 4, 0]
|
|
753
|
-
data_recv = @data[8
|
|
709
|
+
data_recv = @data[8..-1] + @socket.recv(size + 32)
|
|
754
710
|
end
|
|
755
711
|
|
|
756
712
|
puts "data_recv: #{python_format(data_recv)}" if @verbose
|
|
@@ -760,36 +716,30 @@ module RBZK
|
|
|
760
716
|
data << resp if resp
|
|
761
717
|
|
|
762
718
|
# get CMD_ACK_OK
|
|
763
|
-
if broken_header.size < 16
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
719
|
+
data_recv = if broken_header.size < 16
|
|
720
|
+
broken_header + @socket.recv(16)
|
|
721
|
+
else
|
|
722
|
+
broken_header
|
|
723
|
+
end
|
|
768
724
|
|
|
769
725
|
if data_recv.size < 16
|
|
770
726
|
puts "trying to complete broken ACK #{data_recv.size} /16"
|
|
771
|
-
if @verbose
|
|
772
|
-
puts "data_recv: #{data_recv.bytes.map { |b| "0x#{b.to_s(16).rjust(2, '0')}" }.join(' ')}"
|
|
773
|
-
end
|
|
727
|
+
puts "data_recv: #{data_recv.bytes.map { |b| "0x#{b.to_s(16).rjust(2, '0')}" }.join(' ')}" if @verbose
|
|
774
728
|
data_recv += @socket.recv(16 - data_recv.size) # TODO: CHECK HERE_!
|
|
775
729
|
end
|
|
776
730
|
|
|
777
|
-
|
|
778
|
-
if @verbose
|
|
779
|
-
puts "invalid chunk tcp ACK OK"
|
|
780
|
-
end
|
|
731
|
+
unless test_tcp_top(data_recv)
|
|
732
|
+
puts 'invalid chunk tcp ACK OK' if @verbose
|
|
781
733
|
return nil
|
|
782
734
|
end
|
|
783
735
|
|
|
784
736
|
# In Python: response = unpack('HHHH', data_recv[8:16])[0]
|
|
785
737
|
# This unpacks 4 shorts (8 bytes) but only uses the first one
|
|
786
738
|
# In Ruby, we need to use 'S<4' to unpack 4 shorts in little-endian format
|
|
787
|
-
response = data_recv[8...16].
|
|
739
|
+
response = data_recv[8...16].unpack1('S<4')
|
|
788
740
|
|
|
789
741
|
if response == CMD_ACK_OK
|
|
790
|
-
if @verbose
|
|
791
|
-
puts "chunk tcp ACK OK!"
|
|
792
|
-
end
|
|
742
|
+
puts 'chunk tcp ACK OK!' if @verbose
|
|
793
743
|
return data.join
|
|
794
744
|
end
|
|
795
745
|
|
|
@@ -798,40 +748,32 @@ module RBZK
|
|
|
798
748
|
puts "data: #{data.map { |d| format_as_python_bytes(d) }.join(', ')}"
|
|
799
749
|
end
|
|
800
750
|
|
|
801
|
-
|
|
751
|
+
nil
|
|
802
752
|
else
|
|
803
753
|
# Non-TCP implementation
|
|
804
754
|
loop do
|
|
805
755
|
data_recv = @socket.recv(1024 + 8)
|
|
806
|
-
response = data_recv[0...8].
|
|
756
|
+
response = data_recv[0...8].unpack1('S<S<S<S<')
|
|
807
757
|
|
|
808
|
-
if @verbose
|
|
809
|
-
puts "# packet response is: #{response}"
|
|
810
|
-
end
|
|
758
|
+
puts "# packet response is: #{response}" if @verbose
|
|
811
759
|
|
|
812
760
|
if response == CMD_DATA
|
|
813
|
-
data << data_recv[8
|
|
761
|
+
data << data_recv[8..-1]
|
|
814
762
|
size -= 1024
|
|
815
763
|
elsif response == CMD_ACK_OK
|
|
816
764
|
break
|
|
817
765
|
else
|
|
818
|
-
if @verbose
|
|
819
|
-
puts "broken!"
|
|
820
|
-
end
|
|
766
|
+
puts 'broken!' if @verbose
|
|
821
767
|
break
|
|
822
768
|
end
|
|
823
769
|
|
|
824
|
-
if @verbose
|
|
825
|
-
puts "still needs #{size}"
|
|
826
|
-
end
|
|
770
|
+
puts "still needs #{size}" if @verbose
|
|
827
771
|
end
|
|
828
772
|
|
|
829
|
-
|
|
773
|
+
data.join
|
|
830
774
|
end
|
|
831
775
|
else
|
|
832
|
-
if @verbose
|
|
833
|
-
puts "invalid response #{@response}"
|
|
834
|
-
end
|
|
776
|
+
puts "invalid response #{@response}" if @verbose
|
|
835
777
|
nil
|
|
836
778
|
end
|
|
837
779
|
end
|
|
@@ -839,27 +781,21 @@ module RBZK
|
|
|
839
781
|
# Helper method to receive raw data (like Python's __recieve_raw_data)
|
|
840
782
|
def receive_raw_data(size)
|
|
841
783
|
data = []
|
|
842
|
-
if @verbose
|
|
843
|
-
puts "expecting #{size} bytes raw data"
|
|
844
|
-
end
|
|
784
|
+
puts "expecting #{size} bytes raw data" if @verbose
|
|
845
785
|
|
|
846
|
-
while size
|
|
786
|
+
while size.positive?
|
|
847
787
|
data_recv = @socket.recv(size)
|
|
848
788
|
received = data_recv.size
|
|
849
789
|
|
|
850
790
|
if @verbose
|
|
851
791
|
puts "partial recv #{received}"
|
|
852
|
-
if received < 100
|
|
853
|
-
puts " recv #{data_recv.bytes.map { |b| "0x#{b.to_s(16).rjust(2, '0')}" }.join(' ')}"
|
|
854
|
-
end
|
|
792
|
+
puts " recv #{data_recv.bytes.map { |b| "0x#{b.to_s(16).rjust(2, '0')}" }.join(' ')}" if received < 100
|
|
855
793
|
end
|
|
856
794
|
|
|
857
795
|
data << data_recv
|
|
858
796
|
size -= received
|
|
859
797
|
|
|
860
|
-
if @verbose
|
|
861
|
-
puts "still need #{size}"
|
|
862
|
-
end
|
|
798
|
+
puts "still need #{size}" if @verbose
|
|
863
799
|
end
|
|
864
800
|
|
|
865
801
|
data.join
|
|
@@ -868,13 +804,11 @@ module RBZK
|
|
|
868
804
|
# Helper method to clear buffer (like Python's free_data)
|
|
869
805
|
def free_data
|
|
870
806
|
command = CMD_FREE_DATA
|
|
871
|
-
response =
|
|
807
|
+
response = send_command(command)
|
|
872
808
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
raise RBZK::ZKErrorResponse, "Can't free data"
|
|
877
|
-
end
|
|
809
|
+
raise RBZK::ZKErrorResponse, "Can't free data" unless response && response[:status]
|
|
810
|
+
|
|
811
|
+
true
|
|
878
812
|
end
|
|
879
813
|
|
|
880
814
|
# Send data with buffer
|
|
@@ -883,15 +817,13 @@ module RBZK
|
|
|
883
817
|
def send_with_buffer(buffer)
|
|
884
818
|
max_chunk = 1024
|
|
885
819
|
size = buffer.size
|
|
886
|
-
|
|
820
|
+
free_data
|
|
887
821
|
|
|
888
822
|
command = CMD_PREPARE_DATA
|
|
889
|
-
command_string = [size].pack('L<')
|
|
890
|
-
response =
|
|
823
|
+
command_string = [ size ].pack('L<')
|
|
824
|
+
response = send_command(command, command_string)
|
|
891
825
|
|
|
892
|
-
if !response || !response[:status]
|
|
893
|
-
raise RBZK::ZKErrorResponse, "Can't prepare data"
|
|
894
|
-
end
|
|
826
|
+
raise RBZK::ZKErrorResponse, "Can't prepare data" if !response || !response[:status]
|
|
895
827
|
|
|
896
828
|
remain = size % max_chunk
|
|
897
829
|
packets = (size - remain) / max_chunk
|
|
@@ -902,7 +834,7 @@ module RBZK
|
|
|
902
834
|
start += max_chunk
|
|
903
835
|
end
|
|
904
836
|
|
|
905
|
-
send_chunk(buffer[start, remain]) if remain
|
|
837
|
+
send_chunk(buffer[start, remain]) if remain.positive?
|
|
906
838
|
|
|
907
839
|
true
|
|
908
840
|
end
|
|
@@ -912,22 +844,18 @@ module RBZK
|
|
|
912
844
|
# @return [Boolean] true if successful, raises exception otherwise
|
|
913
845
|
def send_chunk(command_string)
|
|
914
846
|
command = CMD_DATA
|
|
915
|
-
response =
|
|
847
|
+
response = send_command(command, command_string)
|
|
916
848
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
raise RBZK::ZKErrorResponse, "Can't send chunk"
|
|
921
|
-
end
|
|
849
|
+
raise RBZK::ZKErrorResponse, "Can't send chunk" unless response && response[:status]
|
|
850
|
+
|
|
851
|
+
true
|
|
922
852
|
end
|
|
923
853
|
|
|
924
854
|
# Helper method to read a chunk of data
|
|
925
855
|
def read_chunk(start, size)
|
|
926
|
-
if @verbose
|
|
927
|
-
puts "Reading chunk: start=#{start}, size=#{size}"
|
|
928
|
-
end
|
|
856
|
+
puts "Reading chunk: start=#{start}, size=#{size}" if @verbose
|
|
929
857
|
|
|
930
|
-
3.times do |
|
|
858
|
+
3.times do |attempt|
|
|
931
859
|
# In Python: command = const._CMD_READ_BUFFER (which is 1504)
|
|
932
860
|
# In Ruby, we should use CMD_READ_BUFFER (1504) instead of CMD_READFILE_DATA (81)
|
|
933
861
|
command = 1504 # CMD_READ_BUFFER
|
|
@@ -939,25 +867,21 @@ module RBZK
|
|
|
939
867
|
response_size = @tcp ? size + 32 : 1024 + 8
|
|
940
868
|
|
|
941
869
|
# In Python: cmd_response = self.__send_command(command, command_string, response_size)
|
|
942
|
-
response =
|
|
870
|
+
response = send_command(command, command_string, response_size)
|
|
943
871
|
|
|
944
872
|
if !response || !response[:status]
|
|
945
|
-
if @verbose
|
|
946
|
-
puts "Failed to read chunk on attempt #{_retries + 1}"
|
|
947
|
-
end
|
|
873
|
+
puts "Failed to read chunk on attempt #{attempt + 1}" if @verbose
|
|
948
874
|
next
|
|
949
875
|
end
|
|
950
876
|
|
|
951
877
|
# In Python: data = self.__recieve_chunk()
|
|
952
878
|
data = receive_chunk
|
|
953
879
|
|
|
954
|
-
|
|
955
|
-
if @verbose
|
|
956
|
-
puts "Received chunk of #{data.size} bytes"
|
|
957
|
-
end
|
|
880
|
+
next unless data
|
|
958
881
|
|
|
959
|
-
|
|
960
|
-
|
|
882
|
+
puts "Received chunk of #{data.size} bytes" if @verbose
|
|
883
|
+
|
|
884
|
+
return data
|
|
961
885
|
end
|
|
962
886
|
|
|
963
887
|
# If we get here, all retries failed
|
|
@@ -966,12 +890,12 @@ module RBZK
|
|
|
966
890
|
|
|
967
891
|
def get_users
|
|
968
892
|
# Read sizes
|
|
969
|
-
|
|
893
|
+
read_sizes
|
|
970
894
|
|
|
971
895
|
puts "Device has #{@users} users" if @verbose
|
|
972
896
|
|
|
973
897
|
# If no users, return empty array
|
|
974
|
-
if @users
|
|
898
|
+
if @users.zero?
|
|
975
899
|
@next_uid = 1
|
|
976
900
|
@next_user_id = '1'
|
|
977
901
|
return []
|
|
@@ -979,46 +903,48 @@ module RBZK
|
|
|
979
903
|
|
|
980
904
|
users = []
|
|
981
905
|
max_uid = 0
|
|
982
|
-
userdata, size =
|
|
906
|
+
userdata, size = read_with_buffer(CMD_USERTEMP_RRQ, FCT_USER)
|
|
983
907
|
puts "user size #{size} (= #{userdata.length})" if @verbose
|
|
984
908
|
|
|
985
909
|
if size <= 4
|
|
986
|
-
puts
|
|
910
|
+
puts 'WRN: missing user data'
|
|
987
911
|
return []
|
|
988
912
|
end
|
|
989
913
|
|
|
990
914
|
total_size = userdata[0, 4].unpack1('L<')
|
|
991
915
|
@user_packet_size = total_size / @users
|
|
992
916
|
|
|
993
|
-
if ![ 28, 72 ].include?(@user_packet_size)
|
|
994
|
-
puts "WRN packet size would be #{@user_packet_size}" if @verbose
|
|
995
|
-
end
|
|
917
|
+
puts "WRN packet size would be #{@user_packet_size}" if ![ 28, 72 ].include?(@user_packet_size) && @verbose
|
|
996
918
|
|
|
997
919
|
userdata = userdata[4..-1]
|
|
998
920
|
|
|
999
921
|
if @user_packet_size == 28
|
|
1000
922
|
while userdata.length >= 28
|
|
1001
|
-
uid, privilege, password, name, card, group_id, timezone, user_id = userdata.ljust(28, "\x00")[0,
|
|
923
|
+
uid, privilege, password, name, card, group_id, timezone, user_id = userdata.ljust(28, "\x00")[0,
|
|
924
|
+
28].unpack('S<Ca5a8L<xCs<L<')
|
|
1002
925
|
max_uid = uid if uid > max_uid
|
|
1003
926
|
password = password.split("\x00").first&.force_encoding(@encoding)&.encode('UTF-8', invalid: :replace)
|
|
1004
927
|
name = name.split("\x00").first&.force_encoding(@encoding)&.encode('UTF-8', invalid: :replace)&.strip
|
|
1005
928
|
group_id = group_id.to_s
|
|
1006
929
|
user_id = user_id.to_s
|
|
1007
|
-
name
|
|
930
|
+
name ||= "NN-#{user_id}"
|
|
1008
931
|
user = User.new(uid, name, privilege, password, group_id, user_id, card)
|
|
1009
932
|
users << user
|
|
1010
|
-
|
|
933
|
+
if @verbose
|
|
934
|
+
puts "[6]user: #{uid}, #{privilege}, #{password}, #{name}, #{card}, #{group_id}, #{timezone}, #{user_id}"
|
|
935
|
+
end
|
|
1011
936
|
userdata = userdata[28..-1]
|
|
1012
937
|
end
|
|
1013
938
|
else
|
|
1014
939
|
while userdata.length >= 72
|
|
1015
|
-
uid, privilege, password, name, card, group_id, user_id = userdata.ljust(72, "\x00")[0,
|
|
940
|
+
uid, privilege, password, name, card, group_id, user_id = userdata.ljust(72, "\x00")[0,
|
|
941
|
+
72].unpack('S<Ca8a24L<xa7xa24')
|
|
1016
942
|
max_uid = uid if uid > max_uid
|
|
1017
943
|
password = password.split("\x00").first&.force_encoding(@encoding)&.encode('UTF-8', invalid: :replace)
|
|
1018
944
|
name = name.split("\x00").first&.force_encoding(@encoding)&.encode('UTF-8', invalid: :replace)&.strip
|
|
1019
945
|
group_id = group_id.split("\x00").first&.force_encoding(@encoding)&.encode('UTF-8', invalid: :replace)&.strip
|
|
1020
946
|
user_id = user_id.split("\x00").first&.force_encoding(@encoding)&.encode('UTF-8', invalid: :replace)
|
|
1021
|
-
name
|
|
947
|
+
name ||= "NN-#{user_id}"
|
|
1022
948
|
user = User.new(uid, name, privilege, password, group_id, user_id, card)
|
|
1023
949
|
users << user
|
|
1024
950
|
userdata = userdata[72..-1]
|
|
@@ -1030,12 +956,10 @@ module RBZK
|
|
|
1030
956
|
@next_user_id = max_uid.to_s
|
|
1031
957
|
|
|
1032
958
|
loop do
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
break
|
|
1038
|
-
end
|
|
959
|
+
break unless users.any? { |u| u.user_id == @next_user_id }
|
|
960
|
+
|
|
961
|
+
max_uid += 1
|
|
962
|
+
@next_user_id = max_uid.to_s
|
|
1039
963
|
end
|
|
1040
964
|
|
|
1041
965
|
users
|
|
@@ -1043,41 +967,33 @@ module RBZK
|
|
|
1043
967
|
|
|
1044
968
|
def get_attendance_logs
|
|
1045
969
|
# First, read device sizes to get record count
|
|
1046
|
-
|
|
970
|
+
read_sizes
|
|
1047
971
|
|
|
1048
972
|
# If no records, return empty array
|
|
1049
|
-
if @records
|
|
1050
|
-
return []
|
|
1051
|
-
end
|
|
973
|
+
return [] if @records.zero?
|
|
1052
974
|
|
|
1053
975
|
# Get users for lookup
|
|
1054
|
-
users =
|
|
976
|
+
users = get_users
|
|
1055
977
|
|
|
1056
|
-
if @verbose
|
|
1057
|
-
puts "Found #{users.size} users"
|
|
1058
|
-
end
|
|
978
|
+
puts "Found #{users.size} users" if @verbose
|
|
1059
979
|
|
|
1060
980
|
logs = []
|
|
1061
981
|
|
|
1062
982
|
# Read attendance data with buffer
|
|
1063
|
-
attendance_data, size =
|
|
983
|
+
attendance_data, size = read_with_buffer(CMD_ATTLOG_RRQ)
|
|
1064
984
|
|
|
1065
985
|
if size < 4
|
|
1066
|
-
if @verbose
|
|
1067
|
-
puts "WRN: no attendance data"
|
|
1068
|
-
end
|
|
986
|
+
puts 'WRN: no attendance data' if @verbose
|
|
1069
987
|
return []
|
|
1070
988
|
end
|
|
1071
989
|
|
|
1072
990
|
# Get total size from first 4 bytes
|
|
1073
|
-
total_size = attendance_data[0...4].
|
|
991
|
+
total_size = attendance_data[0...4].unpack1('I')
|
|
1074
992
|
|
|
1075
993
|
# Calculate record size
|
|
1076
|
-
record_size = @records
|
|
994
|
+
record_size = @records.positive? ? total_size / @records : 0
|
|
1077
995
|
|
|
1078
|
-
if @verbose
|
|
1079
|
-
puts "record_size is #{record_size}"
|
|
1080
|
-
end
|
|
996
|
+
puts "record_size is #{record_size}" if @verbose
|
|
1081
997
|
|
|
1082
998
|
# Remove the first 4 bytes (total size)
|
|
1083
999
|
attendance_data = attendance_data[4..-1]
|
|
@@ -1089,18 +1005,20 @@ module RBZK
|
|
|
1089
1005
|
uid, status, timestamp_raw, punch = attendance_data[0...8].ljust(8, "\x00".b).unpack('S<C4sC')
|
|
1090
1006
|
|
|
1091
1007
|
if @verbose
|
|
1092
|
-
puts "Attendance data (hex): #{attendance_data[0...8].bytes.map
|
|
1008
|
+
puts "Attendance data (hex): #{attendance_data[0...8].bytes.map do |b|
|
|
1009
|
+
"0x#{b.to_s(16).rjust(2, '0')}"
|
|
1010
|
+
end.join(' ')}"
|
|
1093
1011
|
end
|
|
1094
1012
|
|
|
1095
1013
|
attendance_data = attendance_data[8..-1]
|
|
1096
1014
|
|
|
1097
1015
|
# Look up user by uid
|
|
1098
1016
|
tuser = users.find { |u| u.uid == uid }
|
|
1099
|
-
if !tuser
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1017
|
+
user_id = if !tuser
|
|
1018
|
+
uid.to_s
|
|
1019
|
+
else
|
|
1020
|
+
tuser.user_id
|
|
1021
|
+
end
|
|
1104
1022
|
|
|
1105
1023
|
# Decode timestamp
|
|
1106
1024
|
timestamp = decode_time(timestamp_raw)
|
|
@@ -1113,10 +1031,13 @@ module RBZK
|
|
|
1113
1031
|
# Handle 16-byte records
|
|
1114
1032
|
while attendance_data && attendance_data.size >= 16
|
|
1115
1033
|
# In Python: user_id, timestamp, status, punch, reserved, workcode = unpack('<I4sBB2sI', attendance_data.ljust(16, b'\x00')[:16])
|
|
1116
|
-
user_id_raw, timestamp_raw, status, punch,
|
|
1034
|
+
user_id_raw, timestamp_raw, status, punch, _reserved, _workcode = attendance_data[0...16].ljust(16,
|
|
1035
|
+
"\x00".b).unpack('L<4sCCa2L<')
|
|
1117
1036
|
|
|
1118
1037
|
if @verbose
|
|
1119
|
-
puts "Attendance data (hex): #{attendance_data[0...16].bytes.map
|
|
1038
|
+
puts "Attendance data (hex): #{attendance_data[0...16].bytes.map do |b|
|
|
1039
|
+
"0x#{b.to_s(16).rjust(2, '0')}"
|
|
1040
|
+
end.join(' ')}"
|
|
1120
1041
|
end
|
|
1121
1042
|
|
|
1122
1043
|
attendance_data = attendance_data[16..-1]
|
|
@@ -1127,9 +1048,7 @@ module RBZK
|
|
|
1127
1048
|
# Look up user by user_id and uid
|
|
1128
1049
|
tuser = users.find { |u| u.user_id == user_id }
|
|
1129
1050
|
if !tuser
|
|
1130
|
-
if @verbose
|
|
1131
|
-
puts "no uid #{user_id}"
|
|
1132
|
-
end
|
|
1051
|
+
puts "no uid #{user_id}" if @verbose
|
|
1133
1052
|
uid = user_id
|
|
1134
1053
|
tuser = users.find { |u| u.uid.to_s == user_id }
|
|
1135
1054
|
if !tuser
|
|
@@ -1153,10 +1072,13 @@ module RBZK
|
|
|
1153
1072
|
# Handle 40-byte records (default)
|
|
1154
1073
|
while attendance_data && attendance_data.size >= 40
|
|
1155
1074
|
# In Python: uid, user_id, status, timestamp, punch, space = unpack('<H24sB4sB8s', attendance_data.ljust(40, b'\x00')[:40])
|
|
1156
|
-
uid, user_id_raw, status, timestamp_raw, punch,
|
|
1075
|
+
uid, user_id_raw, status, timestamp_raw, punch, _space = attendance_data[0...40].ljust(40,
|
|
1076
|
+
"\x00".b).unpack('S<a24Ca4Ca8')
|
|
1157
1077
|
|
|
1158
1078
|
if @verbose
|
|
1159
|
-
puts "Attendance data (hex): #{attendance_data[0...40].bytes.map
|
|
1079
|
+
puts "Attendance data (hex): #{attendance_data[0...40].bytes.map do |b|
|
|
1080
|
+
"0x#{b.to_s(16).rjust(2, '0')}"
|
|
1081
|
+
end.join(' ')}"
|
|
1160
1082
|
end
|
|
1161
1083
|
|
|
1162
1084
|
# Extract user_id from null-terminated string
|
|
@@ -1178,23 +1100,23 @@ module RBZK
|
|
|
1178
1100
|
|
|
1179
1101
|
def decode_time(t)
|
|
1180
1102
|
# Convert binary timestamp to integer
|
|
1181
|
-
t = t.
|
|
1103
|
+
t = t.unpack1('L<')
|
|
1182
1104
|
|
|
1183
1105
|
# Extract time components
|
|
1184
1106
|
second = t % 60
|
|
1185
|
-
t
|
|
1107
|
+
t /= 60
|
|
1186
1108
|
|
|
1187
1109
|
minute = t % 60
|
|
1188
|
-
t
|
|
1110
|
+
t /= 60
|
|
1189
1111
|
|
|
1190
1112
|
hour = t % 24
|
|
1191
|
-
t
|
|
1113
|
+
t /= 24
|
|
1192
1114
|
|
|
1193
1115
|
day = t % 31 + 1
|
|
1194
|
-
t
|
|
1116
|
+
t /= 31
|
|
1195
1117
|
|
|
1196
1118
|
month = t % 12 + 1
|
|
1197
|
-
t
|
|
1119
|
+
t /= 12
|
|
1198
1120
|
|
|
1199
1121
|
year = t + 2000
|
|
1200
1122
|
|
|
@@ -1206,7 +1128,7 @@ module RBZK
|
|
|
1206
1128
|
# Match Python's __decode_timehex method
|
|
1207
1129
|
def decode_timehex(timehex)
|
|
1208
1130
|
# Extract time components
|
|
1209
|
-
year, month, day, hour, minute, second = timehex.unpack(
|
|
1131
|
+
year, month, day, hour, minute, second = timehex.unpack('C6')
|
|
1210
1132
|
year += 2000
|
|
1211
1133
|
|
|
1212
1134
|
# Create Time object
|
|
@@ -1215,23 +1137,20 @@ module RBZK
|
|
|
1215
1137
|
|
|
1216
1138
|
def encode_time(t)
|
|
1217
1139
|
# Calculate encoded timestamp
|
|
1218
|
-
|
|
1140
|
+
(
|
|
1219
1141
|
((t.year % 100) * 12 * 31 + ((t.month - 1) * 31) + t.day - 1) *
|
|
1220
1142
|
(24 * 60 * 60) + (t.hour * 60 + t.minute) * 60 + t.second
|
|
1221
1143
|
)
|
|
1222
|
-
d
|
|
1223
1144
|
end
|
|
1224
1145
|
|
|
1225
1146
|
def get_time
|
|
1226
1147
|
command = CMD_GET_TIME
|
|
1227
1148
|
response_size = 1032
|
|
1228
|
-
response =
|
|
1149
|
+
response = send_command(command, '', response_size)
|
|
1229
1150
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
raise RBZK::ZKErrorResponse, "Can't get time"
|
|
1234
|
-
end
|
|
1151
|
+
raise RBZK::ZKErrorResponse, "Can't get time" unless response && response[:status]
|
|
1152
|
+
|
|
1153
|
+
decode_time(@data[0...4])
|
|
1235
1154
|
end
|
|
1236
1155
|
|
|
1237
1156
|
def set_time(timestamp = nil)
|
|
@@ -1240,24 +1159,22 @@ module RBZK
|
|
|
1240
1159
|
|
|
1241
1160
|
command = CMD_SET_TIME
|
|
1242
1161
|
command_string = [ encode_time(timestamp) ].pack('L<')
|
|
1243
|
-
response =
|
|
1162
|
+
response = send_command(command, command_string)
|
|
1244
1163
|
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
raise RBZK::ZKErrorResponse, "Can't set time"
|
|
1249
|
-
end
|
|
1164
|
+
raise RBZK::ZKErrorResponse, "Can't set time" unless response && response[:status]
|
|
1165
|
+
|
|
1166
|
+
true
|
|
1250
1167
|
end
|
|
1251
1168
|
|
|
1252
1169
|
def clear_attendance_logs
|
|
1253
|
-
|
|
1254
|
-
|
|
1170
|
+
send_command(CMD_CLEAR_ATTLOG)
|
|
1171
|
+
recv_reply
|
|
1255
1172
|
true
|
|
1256
1173
|
end
|
|
1257
1174
|
|
|
1258
1175
|
def clear_data
|
|
1259
|
-
|
|
1260
|
-
|
|
1176
|
+
send_command(CMD_CLEAR_DATA)
|
|
1177
|
+
recv_reply
|
|
1261
1178
|
true
|
|
1262
1179
|
end
|
|
1263
1180
|
|
|
@@ -1267,33 +1184,33 @@ module RBZK
|
|
|
1267
1184
|
|
|
1268
1185
|
result = "b'"
|
|
1269
1186
|
binary_string.each_byte do |byte|
|
|
1270
|
-
case byte
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1187
|
+
result += case byte
|
|
1188
|
+
when 0x0d # Carriage return - Python shows as \r
|
|
1189
|
+
'\\r'
|
|
1190
|
+
when 0x0a # Line feed - Python shows as \n
|
|
1191
|
+
'\\n'
|
|
1192
|
+
when 0x09 # Tab - Python shows as \t
|
|
1193
|
+
'\\t'
|
|
1194
|
+
when 0x07 # Bell - Python can show as \a or \x07
|
|
1195
|
+
'\\x07'
|
|
1196
|
+
when 0x08 # Backspace - Python shows as \b
|
|
1197
|
+
'\\b'
|
|
1198
|
+
when 0x0c # Form feed - Python shows as \f
|
|
1199
|
+
'\\f'
|
|
1200
|
+
when 0x0b # Vertical tab - Python shows as \v
|
|
1201
|
+
'\\v'
|
|
1202
|
+
when 0x5c # Backslash - Python shows as \\
|
|
1203
|
+
'\\\\'
|
|
1204
|
+
when 0x27 # Single quote - Python shows as \'
|
|
1205
|
+
"\\'"
|
|
1206
|
+
when 0x22 # Double quote - Python shows as \"
|
|
1207
|
+
'\"'
|
|
1208
|
+
when 32..126 # Printable ASCII
|
|
1209
|
+
byte.chr
|
|
1210
|
+
else
|
|
1211
|
+
# All other bytes - Python shows as \xHH
|
|
1212
|
+
"\\x#{byte.to_s(16).rjust(2, '0')}"
|
|
1213
|
+
end
|
|
1297
1214
|
end
|
|
1298
1215
|
result += "'"
|
|
1299
1216
|
result
|
|
@@ -1308,7 +1225,7 @@ module RBZK
|
|
|
1308
1225
|
puts "Python expected: #{python_expected}"
|
|
1309
1226
|
|
|
1310
1227
|
if ruby_formatted != python_expected
|
|
1311
|
-
puts
|
|
1228
|
+
puts 'DIFFERENCE DETECTED!'
|
|
1312
1229
|
# Show byte-by-byte comparison
|
|
1313
1230
|
ruby_bytes = binary_string.bytes
|
|
1314
1231
|
# Parse Python bytes string (format: b'\x01\x02')
|
|
@@ -1344,16 +1261,27 @@ module RBZK
|
|
|
1344
1261
|
end
|
|
1345
1262
|
|
|
1346
1263
|
# Show differences
|
|
1347
|
-
puts
|
|
1264
|
+
puts 'Byte-by-byte comparison:'
|
|
1348
1265
|
max_len = [ ruby_bytes.length, python_bytes.length ].max
|
|
1349
1266
|
(0...max_len).each do |j|
|
|
1350
1267
|
ruby_byte = j < ruby_bytes.length ? ruby_bytes[j] : nil
|
|
1351
1268
|
python_byte = j < python_bytes.length ? python_bytes[j] : nil
|
|
1352
|
-
match = ruby_byte == python_byte ?
|
|
1353
|
-
puts " Byte #{j}: Ruby=#{
|
|
1269
|
+
match = ruby_byte == python_byte ? '✓' : '✗'
|
|
1270
|
+
puts " Byte #{j}: Ruby=#{if ruby_byte.nil?
|
|
1271
|
+
'nil'
|
|
1272
|
+
else
|
|
1273
|
+
"0x#{ruby_byte.to_s(16).rjust(2,
|
|
1274
|
+
'0')}"
|
|
1275
|
+
end}, Python=#{if python_byte.nil?
|
|
1276
|
+
'nil'
|
|
1277
|
+
else
|
|
1278
|
+
"0x#{python_byte.to_s(16).rjust(
|
|
1279
|
+
2, '0'
|
|
1280
|
+
)}"
|
|
1281
|
+
end} #{match}"
|
|
1354
1282
|
end
|
|
1355
1283
|
else
|
|
1356
|
-
puts
|
|
1284
|
+
puts 'Binary data matches exactly!'
|
|
1357
1285
|
end
|
|
1358
1286
|
end
|
|
1359
1287
|
|
|
@@ -1371,70 +1299,62 @@ module RBZK
|
|
|
1371
1299
|
def read_sizes
|
|
1372
1300
|
command = CMD_GET_FREE_SIZES
|
|
1373
1301
|
response_size = 1024
|
|
1374
|
-
cmd_response =
|
|
1302
|
+
cmd_response = send_command(command, '', response_size)
|
|
1375
1303
|
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1304
|
+
raise RBZK::ZKErrorResponse, "Can't read sizes" unless cmd_response && cmd_response[:status]
|
|
1305
|
+
|
|
1306
|
+
if @verbose
|
|
1307
|
+
puts "Data hex: #{@data.bytes.map { |b| "0x#{b.to_s(16).rjust(2, '0')}" }.join(' ')}"
|
|
1308
|
+
puts "Data Python format: #{python_format(@data)}"
|
|
1309
|
+
end
|
|
1310
|
+
|
|
1311
|
+
size = @data.size
|
|
1312
|
+
puts "Data size: #{size} bytes" if @verbose
|
|
1313
|
+
|
|
1314
|
+
if @data.size >= 80
|
|
1315
|
+
# In Python: fields = unpack('20i', self.__data[:80])
|
|
1316
|
+
# In Ruby, 'l<' is a signed 32-bit integer (4 bytes) in little-endian format, which matches Python's 'i'
|
|
1317
|
+
fields = @data[0...80].unpack('l<20')
|
|
1318
|
+
|
|
1319
|
+
puts "Unpacked fields: #{fields.inspect}" if @verbose
|
|
1320
|
+
|
|
1321
|
+
@users = fields[4]
|
|
1322
|
+
@fingers = fields[6]
|
|
1323
|
+
@records = fields[8]
|
|
1324
|
+
@dummy = fields[10] # ???
|
|
1325
|
+
@cards = fields[12]
|
|
1326
|
+
@fingers_cap = fields[14]
|
|
1327
|
+
@users_cap = fields[15]
|
|
1328
|
+
@rec_cap = fields[16]
|
|
1329
|
+
@fingers_av = fields[17]
|
|
1330
|
+
@users_av = fields[18]
|
|
1331
|
+
@rec_av = fields[19]
|
|
1332
|
+
@data = @data[80..-1]
|
|
1333
|
+
|
|
1334
|
+
# Check for face information (added to match Python implementation)
|
|
1335
|
+
if @data.size >= 12 # face info
|
|
1336
|
+
# In Python: fields = unpack('3i', self.__data[:12]) #dirty hack! we need more information
|
|
1337
|
+
face_fields = @data[0...12].unpack('l<3')
|
|
1338
|
+
@faces = face_fields[0]
|
|
1339
|
+
@faces_cap = face_fields[2]
|
|
1340
|
+
|
|
1341
|
+
puts "Face info: faces=#{@faces}, capacity=#{@faces_cap}" if @verbose
|
|
1380
1342
|
end
|
|
1381
1343
|
|
|
1382
|
-
size = @data.size
|
|
1383
1344
|
if @verbose
|
|
1384
|
-
puts "
|
|
1345
|
+
puts "Device info: users=#{@users}, fingers=#{@fingers}, records=#{@records}"
|
|
1346
|
+
puts "Capacity: users=#{@users_cap}, fingers=#{@fingers_cap}, records=#{@rec_cap}"
|
|
1385
1347
|
end
|
|
1386
1348
|
|
|
1387
|
-
|
|
1388
|
-
# In Python: fields = unpack('20i', self.__data[:80])
|
|
1389
|
-
# In Ruby, 'l<' is a signed 32-bit integer (4 bytes) in little-endian format, which matches Python's 'i'
|
|
1390
|
-
fields = @data[0...80].unpack('l<20')
|
|
1391
|
-
|
|
1392
|
-
if @verbose
|
|
1393
|
-
puts "Unpacked fields: #{fields.inspect}"
|
|
1394
|
-
end
|
|
1395
|
-
|
|
1396
|
-
@users = fields[4]
|
|
1397
|
-
@fingers = fields[6]
|
|
1398
|
-
@records = fields[8]
|
|
1399
|
-
@dummy = fields[10] # ???
|
|
1400
|
-
@cards = fields[12]
|
|
1401
|
-
@fingers_cap = fields[14]
|
|
1402
|
-
@users_cap = fields[15]
|
|
1403
|
-
@rec_cap = fields[16]
|
|
1404
|
-
@fingers_av = fields[17]
|
|
1405
|
-
@users_av = fields[18]
|
|
1406
|
-
@rec_av = fields[19]
|
|
1407
|
-
@data = @data[80..-1]
|
|
1408
|
-
|
|
1409
|
-
# Check for face information (added to match Python implementation)
|
|
1410
|
-
if @data.size >= 12 # face info
|
|
1411
|
-
# In Python: fields = unpack('3i', self.__data[:12]) #dirty hack! we need more information
|
|
1412
|
-
face_fields = @data[0...12].unpack('l<3')
|
|
1413
|
-
@faces = face_fields[0]
|
|
1414
|
-
@faces_cap = face_fields[2]
|
|
1415
|
-
|
|
1416
|
-
if @verbose
|
|
1417
|
-
puts "Face info: faces=#{@faces}, capacity=#{@faces_cap}"
|
|
1418
|
-
end
|
|
1419
|
-
end
|
|
1420
|
-
|
|
1421
|
-
if @verbose
|
|
1422
|
-
puts "Device info: users=#{@users}, fingers=#{@fingers}, records=#{@records}"
|
|
1423
|
-
puts "Capacity: users=#{@users_cap}, fingers=#{@fingers_cap}, records=#{@rec_cap}"
|
|
1424
|
-
end
|
|
1425
|
-
|
|
1426
|
-
return true
|
|
1427
|
-
end
|
|
1428
|
-
else
|
|
1429
|
-
raise RBZK::ZKErrorResponse, "Can't read sizes"
|
|
1349
|
+
return true
|
|
1430
1350
|
end
|
|
1431
1351
|
|
|
1432
1352
|
false
|
|
1433
1353
|
end
|
|
1434
1354
|
|
|
1435
1355
|
def get_free_sizes
|
|
1436
|
-
|
|
1437
|
-
reply =
|
|
1356
|
+
send_command(CMD_GET_FREE_SIZES)
|
|
1357
|
+
reply = recv_reply
|
|
1438
1358
|
|
|
1439
1359
|
if reply && reply.size >= 8
|
|
1440
1360
|
sizes_data = reply[8..-1].unpack('S<*')
|
|
@@ -1454,11 +1374,11 @@ module RBZK
|
|
|
1454
1374
|
def get_templates
|
|
1455
1375
|
fingers = []
|
|
1456
1376
|
|
|
1457
|
-
|
|
1458
|
-
|
|
1377
|
+
send_command(CMD_PREPARE_DATA, [ FCT_FINGERTMP ].pack('C'))
|
|
1378
|
+
recv_reply
|
|
1459
1379
|
|
|
1460
|
-
data_size =
|
|
1461
|
-
templates_data =
|
|
1380
|
+
data_size = recv_long
|
|
1381
|
+
templates_data = recv_chunk(data_size)
|
|
1462
1382
|
|
|
1463
1383
|
if templates_data && !templates_data.empty?
|
|
1464
1384
|
offset = 0
|
|
@@ -1478,8 +1398,8 @@ module RBZK
|
|
|
1478
1398
|
end
|
|
1479
1399
|
|
|
1480
1400
|
def get_user_template(uid, finger_id)
|
|
1481
|
-
|
|
1482
|
-
reply =
|
|
1401
|
+
send_command(CMD_GET_USERTEMP, [ uid, finger_id ].pack('S<S<'))
|
|
1402
|
+
reply = recv_reply
|
|
1483
1403
|
|
|
1484
1404
|
if reply && reply.size >= 8
|
|
1485
1405
|
template_data = reply[8..-1]
|
|
@@ -1510,9 +1430,7 @@ module RBZK
|
|
|
1510
1430
|
# Use connect_ex like Python (returns error code instead of raising exception)
|
|
1511
1431
|
sockaddr = Socket.pack_sockaddr_in(@port, @ip)
|
|
1512
1432
|
@socket.connect_nonblock(sockaddr)
|
|
1513
|
-
if @verbose
|
|
1514
|
-
puts "TCP socket connected successfully"
|
|
1515
|
-
end
|
|
1433
|
+
puts 'TCP socket connected successfully' if @verbose
|
|
1516
1434
|
rescue IO::WaitWritable
|
|
1517
1435
|
# Socket is in progress of connecting
|
|
1518
1436
|
ready = IO.select(nil, [ @socket ], nil, @timeout)
|
|
@@ -1521,42 +1439,30 @@ module RBZK
|
|
|
1521
1439
|
@socket.connect_nonblock(sockaddr)
|
|
1522
1440
|
rescue Errno::EISCONN
|
|
1523
1441
|
# Already connected, which is fine
|
|
1524
|
-
if @verbose
|
|
1525
|
-
|
|
1526
|
-
end
|
|
1527
|
-
rescue => e
|
|
1442
|
+
puts 'TCP socket connected successfully' if @verbose
|
|
1443
|
+
rescue StandardError => e
|
|
1528
1444
|
# Connection failed
|
|
1529
|
-
if @verbose
|
|
1530
|
-
puts "TCP socket connection failed: #{e.message}"
|
|
1531
|
-
end
|
|
1445
|
+
puts "TCP socket connection failed: #{e.message}" if @verbose
|
|
1532
1446
|
raise e
|
|
1533
1447
|
end
|
|
1534
1448
|
else
|
|
1535
1449
|
# Connection timed out
|
|
1536
|
-
if @verbose
|
|
1537
|
-
puts "TCP socket connection timed out"
|
|
1538
|
-
end
|
|
1450
|
+
puts 'TCP socket connection timed out' if @verbose
|
|
1539
1451
|
raise Errno::ETIMEDOUT
|
|
1540
1452
|
end
|
|
1541
1453
|
rescue Errno::EISCONN
|
|
1542
1454
|
# Already connected, which is fine
|
|
1543
|
-
if @verbose
|
|
1544
|
-
puts "TCP socket already connected"
|
|
1545
|
-
end
|
|
1455
|
+
puts 'TCP socket already connected' if @verbose
|
|
1546
1456
|
end
|
|
1547
1457
|
else
|
|
1548
1458
|
# Create UDP socket (like Python's socket(AF_INET, SOCK_DGRAM))
|
|
1549
1459
|
@socket = Socket.new(Socket::AF_INET, Socket::SOCK_DGRAM)
|
|
1550
1460
|
@socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, [ @timeout, 0 ].pack('l_*'))
|
|
1551
1461
|
|
|
1552
|
-
if @verbose
|
|
1553
|
-
puts "UDP socket created successfully"
|
|
1554
|
-
end
|
|
1555
|
-
end
|
|
1556
|
-
rescue => e
|
|
1557
|
-
if @verbose
|
|
1558
|
-
puts "Socket creation failed: #{e.message}"
|
|
1462
|
+
puts 'UDP socket created successfully' if @verbose
|
|
1559
1463
|
end
|
|
1464
|
+
rescue StandardError => e
|
|
1465
|
+
puts "Socket creation failed: #{e.message}" if @verbose
|
|
1560
1466
|
raise RBZK::ZKNetworkError, "Failed to create socket: #{e.message}"
|
|
1561
1467
|
end
|
|
1562
1468
|
|
|
@@ -1582,24 +1488,18 @@ module RBZK
|
|
|
1582
1488
|
end
|
|
1583
1489
|
|
|
1584
1490
|
def ping
|
|
1585
|
-
if @verbose
|
|
1586
|
-
puts "Pinging device at #{@ip}:#{@port}..."
|
|
1587
|
-
end
|
|
1491
|
+
puts "Pinging device at #{@ip}:#{@port}..." if @verbose
|
|
1588
1492
|
|
|
1589
1493
|
# Try TCP ping first
|
|
1590
1494
|
begin
|
|
1591
1495
|
Timeout.timeout(5) do
|
|
1592
1496
|
s = TCPSocket.new(@ip, @port)
|
|
1593
1497
|
s.close
|
|
1594
|
-
if @verbose
|
|
1595
|
-
puts "TCP ping successful"
|
|
1596
|
-
end
|
|
1498
|
+
puts 'TCP ping successful' if @verbose
|
|
1597
1499
|
return true
|
|
1598
1500
|
end
|
|
1599
1501
|
rescue Timeout::Error, Errno::ECONNREFUSED, Errno::EHOSTUNREACH => e
|
|
1600
|
-
if @verbose
|
|
1601
|
-
puts "TCP ping failed: #{e.message}"
|
|
1602
|
-
end
|
|
1502
|
+
puts "TCP ping failed: #{e.message}" if @verbose
|
|
1603
1503
|
end
|
|
1604
1504
|
|
|
1605
1505
|
# If TCP ping fails, try UDP ping
|
|
@@ -1610,23 +1510,17 @@ module RBZK
|
|
|
1610
1510
|
ready = IO.select([ udp_socket ], nil, nil, 5)
|
|
1611
1511
|
|
|
1612
1512
|
if ready
|
|
1613
|
-
if @verbose
|
|
1614
|
-
puts "UDP ping successful"
|
|
1615
|
-
end
|
|
1513
|
+
puts 'UDP ping successful' if @verbose
|
|
1616
1514
|
udp_socket.close
|
|
1617
|
-
|
|
1515
|
+
true
|
|
1618
1516
|
else
|
|
1619
|
-
if @verbose
|
|
1620
|
-
puts "UDP ping timed out"
|
|
1621
|
-
end
|
|
1517
|
+
puts 'UDP ping timed out' if @verbose
|
|
1622
1518
|
udp_socket.close
|
|
1623
|
-
|
|
1624
|
-
end
|
|
1625
|
-
rescue => e
|
|
1626
|
-
if @verbose
|
|
1627
|
-
puts "UDP ping failed: #{e.message}"
|
|
1519
|
+
false
|
|
1628
1520
|
end
|
|
1629
|
-
|
|
1521
|
+
rescue StandardError => e
|
|
1522
|
+
puts "UDP ping failed: #{e.message}" if @verbose
|
|
1523
|
+
false
|
|
1630
1524
|
end
|
|
1631
1525
|
end
|
|
1632
1526
|
|
|
@@ -1645,24 +1539,18 @@ module RBZK
|
|
|
1645
1539
|
|
|
1646
1540
|
# In Python: if checksum > const.USHRT_MAX: checksum -= const.USHRT_MAX
|
|
1647
1541
|
# Handle overflow immediately after each addition
|
|
1648
|
-
if checksum > USHRT_MAX
|
|
1649
|
-
checksum -= USHRT_MAX
|
|
1650
|
-
end
|
|
1542
|
+
checksum -= USHRT_MAX if checksum > USHRT_MAX
|
|
1651
1543
|
|
|
1652
1544
|
l -= 2
|
|
1653
1545
|
end
|
|
1654
1546
|
|
|
1655
1547
|
# Handle odd byte if present
|
|
1656
1548
|
# In Python: if l: checksum = checksum + p[-1]
|
|
1657
|
-
if l
|
|
1658
|
-
checksum += buf[i]
|
|
1659
|
-
end
|
|
1549
|
+
checksum += buf[i] if l.positive?
|
|
1660
1550
|
|
|
1661
1551
|
# Handle overflow
|
|
1662
1552
|
# In Python: while checksum > const.USHRT_MAX: checksum -= const.USHRT_MAX
|
|
1663
|
-
while checksum > USHRT_MAX
|
|
1664
|
-
checksum -= USHRT_MAX
|
|
1665
|
-
end
|
|
1553
|
+
checksum -= USHRT_MAX while checksum > USHRT_MAX
|
|
1666
1554
|
|
|
1667
1555
|
# Bitwise complement
|
|
1668
1556
|
# In Python: checksum = ~checksum
|
|
@@ -1670,9 +1558,7 @@ module RBZK
|
|
|
1670
1558
|
|
|
1671
1559
|
# Handle negative values
|
|
1672
1560
|
# In Python: while checksum < 0: checksum += const.USHRT_MAX
|
|
1673
|
-
while checksum
|
|
1674
|
-
checksum += USHRT_MAX
|
|
1675
|
-
end
|
|
1561
|
+
checksum += USHRT_MAX while checksum.negative?
|
|
1676
1562
|
|
|
1677
1563
|
# Return the checksum
|
|
1678
1564
|
checksum
|
|
@@ -1681,6 +1567,7 @@ module RBZK
|
|
|
1681
1567
|
# Helper method to debug binary data in both Python and Ruby formats
|
|
1682
1568
|
def debug_binary(name, data)
|
|
1683
1569
|
return unless @verbose
|
|
1570
|
+
|
|
1684
1571
|
puts "#{name} (hex): #{data.bytes.map { |b| "\\x#{b.to_s(16).rjust(2, '0')}" }.join('')}"
|
|
1685
1572
|
puts "#{name} (Ruby): #{data.bytes.map { |b| "0x#{b.to_s(16).rjust(2, '0')}" }.join(' ')}"
|
|
1686
1573
|
puts "#{name} (Python): #{format_as_python_bytes(data)}"
|
|
@@ -1692,18 +1579,18 @@ module RBZK
|
|
|
1692
1579
|
top = [ MACHINE_PREPARE_DATA_1, MACHINE_PREPARE_DATA_2, length ].pack('S<S<I<')
|
|
1693
1580
|
|
|
1694
1581
|
if @verbose
|
|
1695
|
-
puts
|
|
1582
|
+
puts 'TCP header components:'
|
|
1696
1583
|
puts " MACHINE_PREPARE_DATA_1: 0x#{MACHINE_PREPARE_DATA_1.to_s(16)}"
|
|
1697
1584
|
puts " MACHINE_PREPARE_DATA_2: 0x#{MACHINE_PREPARE_DATA_2.to_s(16)}"
|
|
1698
1585
|
puts " packet length: #{length}"
|
|
1699
|
-
debug_binary(
|
|
1700
|
-
debug_binary(
|
|
1586
|
+
debug_binary('TCP header', top)
|
|
1587
|
+
debug_binary('Full TCP packet', top + packet)
|
|
1701
1588
|
end
|
|
1702
1589
|
|
|
1703
1590
|
top + packet
|
|
1704
1591
|
end
|
|
1705
1592
|
|
|
1706
|
-
def create_header(command, command_string =
|
|
1593
|
+
def create_header(command, command_string = ''.b, session_id = 0, reply_id = 0)
|
|
1707
1594
|
# Ensure command_string is a binary string
|
|
1708
1595
|
command_string = command_string.to_s.b
|
|
1709
1596
|
|
|
@@ -1718,35 +1605,33 @@ module RBZK
|
|
|
1718
1605
|
|
|
1719
1606
|
# Update reply_id
|
|
1720
1607
|
reply_id += 1
|
|
1721
|
-
if reply_id >= USHRT_MAX
|
|
1722
|
-
reply_id -= USHRT_MAX
|
|
1723
|
-
end
|
|
1608
|
+
reply_id -= USHRT_MAX if reply_id >= USHRT_MAX
|
|
1724
1609
|
|
|
1725
1610
|
# Create final header with updated values
|
|
1726
1611
|
buf = [ command, checksum, session_id, reply_id ].pack('v4')
|
|
1727
1612
|
|
|
1728
1613
|
if @verbose
|
|
1729
|
-
puts
|
|
1614
|
+
puts 'Header components:'
|
|
1730
1615
|
puts " Command: #{command}"
|
|
1731
1616
|
puts " Checksum: #{checksum}"
|
|
1732
1617
|
puts " Session ID: #{session_id}"
|
|
1733
1618
|
puts " Reply ID: #{reply_id}"
|
|
1734
1619
|
|
|
1735
1620
|
if !command_string.empty?
|
|
1736
|
-
debug_binary(
|
|
1621
|
+
debug_binary('Command string', command_string)
|
|
1737
1622
|
else
|
|
1738
|
-
puts
|
|
1623
|
+
puts 'Command string: (empty)'
|
|
1739
1624
|
end
|
|
1740
|
-
debug_binary(
|
|
1625
|
+
debug_binary('Final header', buf)
|
|
1741
1626
|
end
|
|
1742
1627
|
|
|
1743
1628
|
buf + command_string
|
|
1744
1629
|
end
|
|
1745
1630
|
|
|
1746
|
-
def send_command(command, command_string =
|
|
1631
|
+
def send_command(command, command_string = ''.b, response_size = 8)
|
|
1747
1632
|
# Check connection status (except for connect and auth commands)
|
|
1748
1633
|
if command != CMD_CONNECT && command != CMD_AUTH && !@connected
|
|
1749
|
-
raise RBZK::ZKErrorConnection,
|
|
1634
|
+
raise RBZK::ZKErrorConnection, 'Instance are not connected.'
|
|
1750
1635
|
end
|
|
1751
1636
|
|
|
1752
1637
|
# In Python, command_string is a bytes object (b'')
|
|
@@ -1780,9 +1665,9 @@ module RBZK
|
|
|
1780
1665
|
if @verbose
|
|
1781
1666
|
puts "\nSending TCP packet:"
|
|
1782
1667
|
puts "Note: In send_command, 'top' variable contains the full packet (header + command packet)"
|
|
1783
|
-
puts
|
|
1784
|
-
debug_binary(
|
|
1785
|
-
debug_binary(
|
|
1668
|
+
puts 'This is because create_tcp_top returns the full packet, not just the header'
|
|
1669
|
+
debug_binary('Command packet (buf)', buf)
|
|
1670
|
+
debug_binary('Full TCP packet (top)', top) # 'top' contains the full packet here
|
|
1786
1671
|
end
|
|
1787
1672
|
|
|
1788
1673
|
@socket.send(top, 0)
|
|
@@ -1791,12 +1676,10 @@ module RBZK
|
|
|
1791
1676
|
|
|
1792
1677
|
if @verbose
|
|
1793
1678
|
puts "\nReceived TCP response:"
|
|
1794
|
-
debug_binary(
|
|
1679
|
+
debug_binary('TCP response', @tcp_data_recv)
|
|
1795
1680
|
end
|
|
1796
1681
|
|
|
1797
|
-
if @tcp_length
|
|
1798
|
-
raise RBZK::ZKNetworkError, "TCP packet invalid"
|
|
1799
|
-
end
|
|
1682
|
+
raise RBZK::ZKNetworkError, 'TCP packet invalid' if @tcp_length.zero?
|
|
1800
1683
|
|
|
1801
1684
|
@header = @tcp_data_recv[8..15].unpack('v4')
|
|
1802
1685
|
@data_recv = @tcp_data_recv[8..-1]
|
|
@@ -1806,10 +1689,8 @@ module RBZK
|
|
|
1806
1689
|
@data_recv = @socket.recv(response_size)
|
|
1807
1690
|
@header = @data_recv[0..7].unpack('S<4')
|
|
1808
1691
|
end
|
|
1809
|
-
rescue => e
|
|
1810
|
-
if @verbose
|
|
1811
|
-
puts "Connection error during send: #{e.message}"
|
|
1812
|
-
end
|
|
1692
|
+
rescue StandardError => e
|
|
1693
|
+
puts "Connection error during send: #{e.message}" if @verbose
|
|
1813
1694
|
raise RBZK::ZKNetworkError, e.message
|
|
1814
1695
|
end
|
|
1815
1696
|
|
|
@@ -1834,9 +1715,7 @@ module RBZK
|
|
|
1834
1715
|
|
|
1835
1716
|
def recv_reply
|
|
1836
1717
|
begin
|
|
1837
|
-
if @verbose
|
|
1838
|
-
puts "Waiting for TCP reply"
|
|
1839
|
-
end
|
|
1718
|
+
puts 'Waiting for TCP reply' if @verbose
|
|
1840
1719
|
|
|
1841
1720
|
# Set a timeout for the read operation
|
|
1842
1721
|
Timeout.timeout(5) do
|
|
@@ -1847,15 +1726,11 @@ module RBZK
|
|
|
1847
1726
|
# Parse TCP header
|
|
1848
1727
|
tcp_format1, tcp_format2, tcp_length = tcp_header.unpack('S<S<I<')
|
|
1849
1728
|
|
|
1850
|
-
if @verbose
|
|
1851
|
-
puts "TCP header: format1=#{tcp_format1}, format2=#{tcp_format2}, length=#{tcp_length}"
|
|
1852
|
-
end
|
|
1729
|
+
puts "TCP header: format1=#{tcp_format1}, format2=#{tcp_format2}, length=#{tcp_length}" if @verbose
|
|
1853
1730
|
|
|
1854
1731
|
# Verify TCP header format
|
|
1855
1732
|
if tcp_format1 != MACHINE_PREPARE_DATA_1 || tcp_format2 != MACHINE_PREPARE_DATA_2
|
|
1856
|
-
if @verbose
|
|
1857
|
-
puts "Invalid TCP header format: #{tcp_format1}, #{tcp_format2}"
|
|
1858
|
-
end
|
|
1733
|
+
puts "Invalid TCP header format: #{tcp_format1}, #{tcp_format2}" if @verbose
|
|
1859
1734
|
return nil
|
|
1860
1735
|
end
|
|
1861
1736
|
|
|
@@ -1874,30 +1749,24 @@ module RBZK
|
|
|
1874
1749
|
data_size = tcp_length - 8
|
|
1875
1750
|
|
|
1876
1751
|
# Read data if available
|
|
1877
|
-
data =
|
|
1878
|
-
if data_size
|
|
1879
|
-
if @verbose
|
|
1880
|
-
puts "Reading #{data_size} bytes of data"
|
|
1881
|
-
end
|
|
1752
|
+
data = ''
|
|
1753
|
+
if data_size.positive?
|
|
1754
|
+
puts "Reading #{data_size} bytes of data" if @verbose
|
|
1882
1755
|
|
|
1883
1756
|
# Read data in chunks to handle large responses (like Python implementation)
|
|
1884
1757
|
remaining = data_size
|
|
1885
|
-
while remaining
|
|
1758
|
+
while remaining.positive?
|
|
1886
1759
|
chunk_size = [ remaining, 4096 ].min
|
|
1887
1760
|
chunk = @socket.read(chunk_size)
|
|
1888
1761
|
if chunk.nil? || chunk.empty?
|
|
1889
|
-
if @verbose
|
|
1890
|
-
puts "Failed to read data chunk, got #{chunk.inspect}"
|
|
1891
|
-
end
|
|
1762
|
+
puts "Failed to read data chunk, got #{chunk.inspect}" if @verbose
|
|
1892
1763
|
break
|
|
1893
1764
|
end
|
|
1894
1765
|
|
|
1895
1766
|
data += chunk
|
|
1896
1767
|
remaining -= chunk.size
|
|
1897
1768
|
|
|
1898
|
-
if @verbose && remaining
|
|
1899
|
-
puts "Read #{chunk.size} bytes, #{remaining} remaining"
|
|
1900
|
-
end
|
|
1769
|
+
puts "Read #{chunk.size} bytes, #{remaining} remaining" if @verbose && remaining.positive?
|
|
1901
1770
|
end
|
|
1902
1771
|
end
|
|
1903
1772
|
|
|
@@ -1908,47 +1777,33 @@ module RBZK
|
|
|
1908
1777
|
@session_id = session_id
|
|
1909
1778
|
|
|
1910
1779
|
# Check command type and handle accordingly (like Python implementation)
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
end
|
|
1780
|
+
case command
|
|
1781
|
+
when CMD_ACK_OK
|
|
1782
|
+
puts 'Received ACK_OK' if @verbose
|
|
1915
1783
|
return cmd_header + data
|
|
1916
|
-
|
|
1917
|
-
if @verbose
|
|
1918
|
-
puts "Received ACK_ERROR"
|
|
1919
|
-
end
|
|
1784
|
+
when CMD_ACK_ERROR
|
|
1785
|
+
puts 'Received ACK_ERROR' if @verbose
|
|
1920
1786
|
return nil
|
|
1921
|
-
|
|
1922
|
-
if @verbose
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
else
|
|
1928
|
-
return nil
|
|
1929
|
-
end
|
|
1787
|
+
when CMD_ACK_DATA
|
|
1788
|
+
puts 'Received ACK_DATA' if @verbose
|
|
1789
|
+
return cmd_header + data if data_size.positive?
|
|
1790
|
+
|
|
1791
|
+
return nil
|
|
1792
|
+
|
|
1930
1793
|
else
|
|
1931
|
-
if @verbose
|
|
1932
|
-
puts "Received unknown command: #{command}"
|
|
1933
|
-
end
|
|
1794
|
+
puts "Received unknown command: #{command}" if @verbose
|
|
1934
1795
|
return cmd_header + data
|
|
1935
1796
|
end
|
|
1936
1797
|
end
|
|
1937
1798
|
rescue Timeout::Error => e
|
|
1938
|
-
if @verbose
|
|
1939
|
-
|
|
1940
|
-
end
|
|
1941
|
-
raise RBZK::ZKErrorResponse, "Timeout waiting for response"
|
|
1799
|
+
puts "Timeout waiting for response: #{e.message}" if @verbose
|
|
1800
|
+
raise RBZK::ZKErrorResponse, 'Timeout waiting for response'
|
|
1942
1801
|
rescue Errno::ECONNRESET, Errno::EPIPE => e
|
|
1943
|
-
if @verbose
|
|
1944
|
-
puts "Connection error during receive: #{e.message}"
|
|
1945
|
-
end
|
|
1802
|
+
puts "Connection error during receive: #{e.message}" if @verbose
|
|
1946
1803
|
raise RBZK::ZKNetworkError, "Connection error: #{e.message}"
|
|
1947
1804
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK => e
|
|
1948
|
-
if @verbose
|
|
1949
|
-
|
|
1950
|
-
end
|
|
1951
|
-
raise RBZK::ZKErrorResponse, "Timeout waiting for response"
|
|
1805
|
+
puts "Timeout waiting for response: #{e.message}" if @verbose
|
|
1806
|
+
raise RBZK::ZKErrorResponse, 'Timeout waiting for response'
|
|
1952
1807
|
end
|
|
1953
1808
|
|
|
1954
1809
|
nil
|
|
@@ -1958,31 +1813,25 @@ module RBZK
|
|
|
1958
1813
|
if @data_recv && @data_recv.size >= 4
|
|
1959
1814
|
data = @data_recv[0..3]
|
|
1960
1815
|
@data_recv = @data_recv[4..-1]
|
|
1961
|
-
return data.
|
|
1816
|
+
return data.unpack1('L<')
|
|
1962
1817
|
end
|
|
1963
1818
|
|
|
1964
1819
|
0
|
|
1965
1820
|
end
|
|
1966
1821
|
|
|
1967
1822
|
def recv_chunk(size)
|
|
1968
|
-
if @verbose
|
|
1969
|
-
puts "Receiving chunk of #{size} bytes"
|
|
1970
|
-
end
|
|
1823
|
+
puts "Receiving chunk of #{size} bytes" if @verbose
|
|
1971
1824
|
|
|
1972
1825
|
if @data_recv && @data_recv.size >= size
|
|
1973
1826
|
data = @data_recv[0...size]
|
|
1974
1827
|
@data_recv = @data_recv[size..-1]
|
|
1975
1828
|
|
|
1976
|
-
if @verbose
|
|
1977
|
-
puts "Received #{data.size} bytes from buffer"
|
|
1978
|
-
end
|
|
1829
|
+
puts "Received #{data.size} bytes from buffer" if @verbose
|
|
1979
1830
|
|
|
1980
1831
|
return data
|
|
1981
1832
|
end
|
|
1982
1833
|
|
|
1983
|
-
if @verbose
|
|
1984
|
-
puts "Warning: No data available in buffer"
|
|
1985
|
-
end
|
|
1834
|
+
puts 'Warning: No data available in buffer' if @verbose
|
|
1986
1835
|
|
|
1987
1836
|
# Return empty string if no data is available
|
|
1988
1837
|
''
|
|
@@ -1999,11 +1848,11 @@ module RBZK
|
|
|
1999
1848
|
k = 0
|
|
2000
1849
|
|
|
2001
1850
|
32.times do |i|
|
|
2002
|
-
if (key & (1 << i)) != 0
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
1851
|
+
k = if (key & (1 << i)) != 0
|
|
1852
|
+
(k << 1 | 1)
|
|
1853
|
+
else
|
|
1854
|
+
k << 1
|
|
1855
|
+
end
|
|
2007
1856
|
end
|
|
2008
1857
|
|
|
2009
1858
|
k += session_id
|
|
@@ -2036,9 +1885,7 @@ module RBZK
|
|
|
2036
1885
|
# Note: The third byte is just B, not k[2] ^ B
|
|
2037
1886
|
result = [ k_bytes[0] ^ b, k_bytes[1] ^ b, b, k_bytes[3] ^ b ].pack('C4')
|
|
2038
1887
|
|
|
2039
|
-
if @verbose
|
|
2040
|
-
puts "Final commkey bytes: #{result.bytes.map { |b| "0x#{b.to_s(16).rjust(2, '0')}" }.join(' ')}"
|
|
2041
|
-
end
|
|
1888
|
+
puts "Final commkey bytes: #{result.bytes.map { |b| "0x#{b.to_s(16).rjust(2, '0')}" }.join(' ')}" if @verbose
|
|
2042
1889
|
|
|
2043
1890
|
result
|
|
2044
1891
|
end
|