From e59bd5ead914593ddd330cc47ded7900f9284d71 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Tue, 28 Nov 2017 18:57:17 -0400 Subject: [PATCH 01/83] =?UTF-8?q?agregado=20autenticacion=20con=20contrase?= =?UTF-8?q?=C3=B1a=20del=20equipo=20(ZK800)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- zk/base.py | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/zk/base.py b/zk/base.py index 82432cc..c361a5a 100644 --- a/zk/base.py +++ b/zk/base.py @@ -8,6 +8,40 @@ from zk.attendance import Attendance from zk.exception import ZKErrorResponse, ZKNetworkError from zk.user import User +def make_commkey(key, session_id, ticks=50): + """take a password and session_id and scramble them to send to the time + clock. + copied from commpro.c - MakeKey""" + key = int(key) + session_id = int(session_id) + k = 0 + for i in range(32): + if (key & (1 << i)): + k = (k << 1 | 1) + else: + k = k << 1 + k += session_id + + k = pack(b'I', k) + k = unpack(b'BBBB', k) + k = pack( + b'BBBB', + k[0] ^ ord('Z'), + k[1] ^ ord('K'), + k[2] ^ ord('S'), + k[3] ^ ord('O')) + k = unpack(b'HH', k) + k = pack(b'HH', k[1], k[0]) + + B = 0xff & ticks + k = unpack(b'BBBB', k) + k = pack( + b'BBBB', + k[0] ^ B, + k[1] ^ B, + B, + k[3] ^ B) + return k class ZK(object): @@ -17,10 +51,11 @@ class ZK(object): __sesion_id = 0 __reply_id = 0 - def __init__(self, ip, port=4370, timeout=60): + def __init__(self, ip, port=4370, timeout=60, password=0): self.__address = (ip, port) self.__sock = socket(AF_INET, SOCK_DGRAM) self.__sock.settimeout(timeout) + self.__password = password # passint def __create_header(self, command, command_string, checksum, session_id, reply_id): ''' @@ -146,12 +181,17 @@ class ZK(object): response_size = 8 cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + self.__sesion_id = unpack('HHHH', self.__data_recv[:8])[2] + if cmd_response.get('code')==const.CMD_ACK_UNAUTH: + print "try auth" + command_string = make_commkey(self.__password,self.__sesion_id) + cmd_response = self.__send_command(const.CMD_AUTH, command_string , checksum, self.__sesion_id, self.__reply_id, response_size) if cmd_response.get('status'): self.is_connect = True # set the session id - self.__sesion_id = unpack('HHHH', self.__data_recv[:8])[2] return self else: + print "connect err {} ".format(cmd_response["code"]) raise ZKErrorResponse("Invalid response") def disconnect(self): From 13d40ef6c48e9f66f862c15b265052d5468b1ec6 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Wed, 6 Dec 2017 10:11:22 -0400 Subject: [PATCH 02/83] =?UTF-8?q?agregado=20contrase=C3=B1a=20INT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- zk/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zk/base.py b/zk/base.py index c361a5a..bec7f29 100644 --- a/zk/base.py +++ b/zk/base.py @@ -52,6 +52,7 @@ class ZK(object): __reply_id = 0 def __init__(self, ip, port=4370, timeout=60, password=0): + self.is_connect = False self.__address = (ip, port) self.__sock = socket(AF_INET, SOCK_DGRAM) self.__sock.settimeout(timeout) @@ -208,6 +209,7 @@ class ZK(object): cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) if cmd_response.get('status'): + self.is_connect = False return True else: raise ZKErrorResponse("Invalid response") From cf81b697c7d77a894eb1c786459ca7f3b9266421 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Wed, 6 Dec 2017 19:28:41 -0400 Subject: [PATCH 03/83] agregado get set time --- zk/base.py | 44 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/zk/base.py b/zk/base.py index bec7f29..ba67c02 100644 --- a/zk/base.py +++ b/zk/base.py @@ -147,7 +147,7 @@ class ZK(object): copied from zkemsdk.c - DecodeTime""" t = t.encode('hex') t = int(self.__reverse_hex(t), 16) - + #print "decode from %s "% format(t, '04x') second = t % 60 t = t / 60 @@ -169,6 +169,16 @@ class ZK(object): return d + def __encode_time(self, t): + """Encode a timestamp so that it can be read on the timeclock + """ + # formula taken from zkemsdk.c - EncodeTime + # can also be found in the technical manual + d = ( + ((t.year % 100) * 12 * 31 + ((t.month - 1) * 31) + t.day - 1) * + (24 * 60 * 60) + (t.hour * 60 + t.minute) * 60 + t.second + ) + return d def connect(self): ''' connect to the device @@ -184,7 +194,7 @@ class ZK(object): cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) self.__sesion_id = unpack('HHHH', self.__data_recv[:8])[2] if cmd_response.get('code')==const.CMD_ACK_UNAUTH: - print "try auth" + #print "try auth" command_string = make_commkey(self.__password,self.__sesion_id) cmd_response = self.__send_command(const.CMD_AUTH, command_string , checksum, self.__sesion_id, self.__reply_id, response_size) if cmd_response.get('status'): @@ -305,6 +315,34 @@ class ZK(object): else: raise ZKErrorResponse("Invalid response") + def get_time(self): + """obtener la hora del equipo""" + command = const.CMD_GET_TIME + command_string = '' + checksum = 0 + session_id = self.__sesion_id + reply_id = self.__reply_id + response_size = 1032 + + cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + if cmd_response.get('status'): + return self.__decode_time(self.__data_recv[8:12]) + else: + raise ZKErrorResponse("Invalid response") + def set_time(self, timestamp): + """ colocar la hora del sistema al zk """ + command = const.CMD_SET_TIME + command_string = pack(b'I', self.__encode_time(timestamp)) + checksum = 0 + session_id = self.__sesion_id + reply_id = self.__reply_id + response_size = 8 + cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + if cmd_response.get('status'): + return True + else: + raise ZKErrorResponse("Invalid response") + def poweroff(self): ''' shutdown the device @@ -315,7 +353,7 @@ class ZK(object): checksum = 0 session_id = self.__sesion_id reply_id = self.__reply_id - response_size = 8 + response_size = 1032 cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) if cmd_response.get('status'): From 27b4996b022eb29c4abe3ed5ea323a11d2045d74 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Thu, 7 Dec 2017 20:02:18 -0400 Subject: [PATCH 04/83] fix menor --- zk/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/zk/base.py b/zk/base.py index ba67c02..43504cc 100644 --- a/zk/base.py +++ b/zk/base.py @@ -179,6 +179,7 @@ class ZK(object): (24 * 60 * 60) + (t.hour * 60 + t.minute) * 60 + t.second ) return d + def connect(self): ''' connect to the device From 2adf1d3e1bd4465d4269bf2b58670999e494f8cc Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Sat, 9 Dec 2017 13:39:16 -0400 Subject: [PATCH 05/83] test udp limits 366KB --- zk/base.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/zk/base.py b/zk/base.py index 43504cc..6583fa6 100644 --- a/zk/base.py +++ b/zk/base.py @@ -441,11 +441,15 @@ class ZK(object): if cmd_response.get('status'): if cmd_response.get('code') == const.CMD_PREPARE_DATA: bytes = self.__get_data_size() + print "user size is %s" % bytes userdata = [] while bytes > 0: data_recv = self.__sock.recv(1032) + response = unpack('HHHH', data_recv[:8])[0] + print "this packet response is: %s" % response userdata.append(data_recv) bytes -= 1024 + print "user still needs %s" % bytes data_recv = self.__sock.recv(8) response = unpack('HHHH', data_recv[:8])[0] @@ -542,11 +546,21 @@ class ZK(object): if cmd_response.get('status'): if cmd_response.get('code') == const.CMD_PREPARE_DATA: bytes = self.__get_data_size() + print "size is %s" % bytes attendance_data = [] + pac = 1 while bytes > 0: data_recv = self.__sock.recv(1032) + response = unpack('HHHH', data_recv[:8])[0] + print "# %s packet response is: %s" % (pac, response) + if response == const.CMD_ACK_OK: + #truncado! continuar? + print "broken!" + break + pac += 1 attendance_data.append(data_recv) bytes -= 1024 + #print "still needs %s" % bytes data_recv = self.__sock.recv(8) response = unpack('HHHH', data_recv[:8])[0] From 949ec914474c57e4af825e463fac6523225d4454 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Thu, 28 Dec 2017 12:01:12 -0400 Subject: [PATCH 06/83] agregado uid en attendance record --- zk/attendance.py | 3 +- zk/base.py | 71 ++++++++++++++++++++++-------------------------- 2 files changed, 35 insertions(+), 39 deletions(-) diff --git a/zk/attendance.py b/zk/attendance.py index ff5d3b3..790f721 100644 --- a/zk/attendance.py +++ b/zk/attendance.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- class Attendance(object): - def __init__(self, user_id, timestamp, status): + def __init__(self, uid, user_id, timestamp, status): + self.uid = uid self.user_id = user_id self.timestamp = timestamp self.status = status diff --git a/zk/base.py b/zk/base.py index 6583fa6..15c71ed 100644 --- a/zk/base.py +++ b/zk/base.py @@ -438,36 +438,36 @@ class ZK(object): cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) users = [] + pac = 0 if cmd_response.get('status'): if cmd_response.get('code') == const.CMD_PREPARE_DATA: bytes = self.__get_data_size() - print "user size is %s" % bytes userdata = [] - while bytes > 0: + while True: data_recv = self.__sock.recv(1032) response = unpack('HHHH', data_recv[:8])[0] - print "this packet response is: %s" % response - userdata.append(data_recv) - bytes -= 1024 - print "user still needs %s" % bytes - - data_recv = self.__sock.recv(8) - response = unpack('HHHH', data_recv[:8])[0] + if response == const.CMD_DATA: + pac += 1 + userdata.append(data_recv[8:]) #header turncated + bytes -= 1024 + elif response == const.CMD_ACK_OK: + break #without problem. + else: + #truncado! continuar? + print "broken! with %s" % response + print "user still needs %s" % bytes + break + if response == const.CMD_ACK_OK: if userdata: # The first 4 bytes don't seem to be related to the user - for x in xrange(len(userdata)): - if x > 0: - userdata[x] = userdata[x][8:] - userdata = ''.join(userdata) - userdata = userdata[12:] + userdata = userdata[4:] while len(userdata) >= 72: - uid, privilege, password, name, sparator, group_id, user_id = unpack('2sc8s28sc7sx24s', userdata.ljust(72)[:72]) - u1 = int(uid[0].encode("hex"), 16) - u2 = int(uid[1].encode("hex"), 16) - - uid = u1 + (u2 * 256) + uid, privilege, password, name, sparator, group_id, user_id = unpack('hc8s28sc7sx24s', userdata.ljust(72)[:72]) + #u1 = int(uid[0].encode("hex"), 16) + #u2 = int(uid[1].encode("hex"), 16) + #uid = u1 + (u2 * 256) privilege = int(privilege.encode("hex"), 16) password = unicode(password.split('\x00')[0], errors='ignore') name = unicode(name.split('\x00')[0], errors='ignore') @@ -546,41 +546,36 @@ class ZK(object): if cmd_response.get('status'): if cmd_response.get('code') == const.CMD_PREPARE_DATA: bytes = self.__get_data_size() - print "size is %s" % bytes attendance_data = [] pac = 1 - while bytes > 0: + while True: #limitado por respuesta no por tamaƱo data_recv = self.__sock.recv(1032) response = unpack('HHHH', data_recv[:8])[0] - print "# %s packet response is: %s" % (pac, response) - if response == const.CMD_ACK_OK: + #print "# %s packet response is: %s" % (pac, response) + if response == const.CMD_DATA: + pac += 1 + attendance_data.append(data_recv[8:]) #header turncated + bytes -= 1024 + elif response == const.CMD_ACK_OK: + break #without problem. + else: #truncado! continuar? print "broken!" break - pac += 1 - attendance_data.append(data_recv) - bytes -= 1024 + #print "still needs %s" % bytes - data_recv = self.__sock.recv(8) - response = unpack('HHHH', data_recv[:8])[0] if response == const.CMD_ACK_OK: if attendance_data: - # The first 4 bytes don't seem to be related to the user - for x in xrange(len(attendance_data)): - if x > 0: - attendance_data[x] = attendance_data[x][8:] - attendance_data = ''.join(attendance_data) - attendance_data = attendance_data[14:] - while len(attendance_data) >= 38: - user_id, sparator, timestamp, status, space = unpack('24sc4sc10s', attendance_data.ljust(40)[:40]) - + attendance_data = attendance_data[4:] + while len(attendance_data) >= 40: + uid, user_id, sparator, timestamp, status, space = unpack('h24sc4sc8s', attendance_data.ljust(40)[:40]) user_id = user_id.split('\x00')[0] timestamp = self.__decode_time(timestamp) status = int(status.encode("hex"), 16) - attendance = Attendance(user_id, timestamp, status) + attendance = Attendance(uid, user_id, timestamp, status) attendances.append(attendance) attendance_data = attendance_data[40:] From 0a5322528e0fe80a157dce55ff5fb6dbe248d0fd Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Tue, 30 Jan 2018 18:17:08 -0400 Subject: [PATCH 07/83] soporte inicial para firmware version 6 --- zk/base.py | 124 +++++++++++++++++++++++++++++++++-------------------- zk/user.py | 2 +- 2 files changed, 78 insertions(+), 48 deletions(-) diff --git a/zk/base.py b/zk/base.py index 15c71ed..f81fba0 100644 --- a/zk/base.py +++ b/zk/base.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import sys from datetime import datetime from socket import AF_INET, SOCK_DGRAM, socket from struct import pack, unpack @@ -51,12 +52,13 @@ class ZK(object): __sesion_id = 0 __reply_id = 0 - def __init__(self, ip, port=4370, timeout=60, password=0): + def __init__(self, ip, port=4370, timeout=60, password=0, firmware=8): self.is_connect = False self.__address = (ip, port) self.__sock = socket(AF_INET, SOCK_DGRAM) self.__sock.settimeout(timeout) self.__password = password # passint + self.__firmware = int(firmware) #TODO check minor version? def __create_header(self, command, command_string, checksum, session_id, reply_id): ''' @@ -184,7 +186,6 @@ class ZK(object): ''' connect to the device ''' - command = const.CMD_CONNECT command_string = '' checksum = 0 @@ -210,7 +211,6 @@ class ZK(object): ''' diconnect from the connected device ''' - command = const.CMD_EXIT command_string = '' checksum = 0 @@ -229,7 +229,6 @@ class ZK(object): ''' disable (lock) device, ensure no activity when process run ''' - command = const.CMD_DISABLEDEVICE command_string = '' checksum = 0 @@ -247,7 +246,6 @@ class ZK(object): ''' re-enable the connected device ''' - command = const.CMD_ENABLEDEVICE command_string = '' checksum = 0 @@ -265,7 +263,6 @@ class ZK(object): ''' return the firmware version ''' - command = const.CMD_GET_VERSION command_string = '' checksum = 0 @@ -302,7 +299,6 @@ class ZK(object): ''' restart the device ''' - command = const.CMD_RESTART command_string = '' checksum = 0 @@ -348,7 +344,6 @@ class ZK(object): ''' shutdown the device ''' - command = const.CMD_POWEROFF command_string = '' checksum = 0 @@ -366,7 +361,6 @@ class ZK(object): ''' play test voice ''' - command = const.CMD_TESTVOICE command_string = '' checksum = 0 @@ -384,15 +378,29 @@ class ZK(object): ''' create or update user by uid ''' - command = const.CMD_USER_WRQ - uid = chr(uid % 256) + chr(uid >> 8) + #uid = chr(uid % 256) + chr(uid >> 8) if privilege not in [const.USER_DEFAULT, const.USER_ADMIN]: privilege = const.USER_DEFAULT privilege = chr(privilege) - - command_string = pack('2sc8s28sc7sx24s', uid, privilege, password, name, chr(0), group_id, user_id) + if self.__firmware == 6: + print "uid : %i" % uid + print "pri : %c" % privilege + print "pass: %s" % str(password) + print "name: %s" % str(name) + print type(name) + print "group %i" % int(group_id) + print "uid2: %i" % int(user_id) + try: + command_string = pack('Hc5s8s5sBHI', uid, privilege, str(password), str(name), chr(0), int(group_id), 0, int(user_id)) + print "cmd : %s" % command_string + except Exception, e: + print "s_h Error pack: %s" % e + print "Error pack: %s" % sys.exc_info()[0] + raise ZKErrorResponse("Invalid response") + else: + command_string = pack('Hc8s28sc7sx24s', uid, privilege, password, name, chr(0), group_id, user_id) checksum = 0 session_id = self.__sesion_id reply_id = self.__reply_id @@ -423,12 +431,12 @@ class ZK(object): return True else: raise ZKErrorResponse("Invalid response") + #def get_user_template(self, uid, finger): def get_users(self): ''' return all user ''' - command = const.CMD_USERTEMP_RRQ command_string = chr(const.FCT_USER) checksum = 0 @@ -454,8 +462,8 @@ class ZK(object): break #without problem. else: #truncado! continuar? - print "broken! with %s" % response - print "user still needs %s" % bytes + #print "broken! with %s" % response + #print "user still needs %s" % bytes break if response == const.CMD_ACK_OK: @@ -463,31 +471,45 @@ class ZK(object): # The first 4 bytes don't seem to be related to the user userdata = ''.join(userdata) userdata = userdata[4:] - while len(userdata) >= 72: - uid, privilege, password, name, sparator, group_id, user_id = unpack('hc8s28sc7sx24s', userdata.ljust(72)[:72]) - #u1 = int(uid[0].encode("hex"), 16) - #u2 = int(uid[1].encode("hex"), 16) - #uid = u1 + (u2 * 256) - privilege = int(privilege.encode("hex"), 16) - password = unicode(password.split('\x00')[0], errors='ignore') - name = unicode(name.split('\x00')[0], errors='ignore') - group_id = unicode(group_id.split('\x00')[0], errors='ignore') - user_id = unicode(user_id.split('\x00')[0], errors='ignore') + if self.__firmware == 6: + while len(userdata) >= 28: + uid, privilege, password, name, card, group_id, timezone, user_id = unpack('HB5s8s5sBhI',userdata.ljust(28)[:28]) + password = unicode(password.split('\x00')[0], errors='ignore') + name = unicode(name.split('\x00')[0], errors='ignore').strip() + card = unpack('Q', card.ljust(8,'\x00'))[0] #or hex value? + group_id = str(group_id) + user_id = str(user_id) + #TODO: check card value and find in ver8 + if not name: + name = user_id + user = User(uid, name, privilege, password, group_id, user_id) + users.append(user) + print "[6]user:",uid, privilege, password, name, card, group_id, timezone, user_id + userdata = userdata[28:] + else: + while len(userdata) >= 72: + uid, privilege, password, name, sparator, group_id, user_id = unpack('Hc8s28sc7sx24s', userdata.ljust(72)[:72]) + #u1 = int(uid[0].encode("hex"), 16) + #u2 = int(uid[1].encode("hex"), 16) + #uid = u1 + (u2 * 256) + privilege = int(privilege.encode("hex"), 16) + password = unicode(password.split('\x00')[0], errors='ignore') + name = unicode(name.split('\x00')[0], errors='ignore').strip() + group_id = unicode(group_id.split('\x00')[0], errors='ignore').strip() + user_id = unicode(user_id.split('\x00')[0], errors='ignore') - user = User(uid, name, privilege, password, group_id, user_id) - users.append(user) + user = User(uid, name, privilege, password, group_id, user_id) + users.append(user) - userdata = userdata[72:] + userdata = userdata[72:] else: raise ZKErrorResponse("Invalid response") - return users def cancel_capture(self): ''' cancel capturing finger ''' - command = const.CMD_CANCELCAPTURE cmd_response = self.__send_command(command=command) print cmd_response @@ -496,7 +518,6 @@ class ZK(object): ''' verify finger ''' - command = const.CMD_STARTVERIFY # uid = chr(uid % 256) + chr(uid >> 8) cmd_response = self.__send_command(command=command) @@ -506,11 +527,15 @@ class ZK(object): ''' start enroll user ''' - command = const.CMD_STARTENROLL uid = chr(uid % 256) + chr(uid >> 8) command_string = pack('2s', uid) - cmd_response = self.__send_command(command=command, command_string=command_string) + checksum = 0 + session_id = self.__sesion_id + reply_id = self.__reply_id + response_size = 8 + + cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) print cmd_response def clear_data(self): @@ -560,28 +585,34 @@ class ZK(object): break #without problem. else: #truncado! continuar? - print "broken!" + #print "broken!" break - #print "still needs %s" % bytes - if response == const.CMD_ACK_OK: if attendance_data: attendance_data = ''.join(attendance_data) attendance_data = attendance_data[4:] - while len(attendance_data) >= 40: - uid, user_id, sparator, timestamp, status, space = unpack('h24sc4sc8s', attendance_data.ljust(40)[:40]) - user_id = user_id.split('\x00')[0] - timestamp = self.__decode_time(timestamp) - status = int(status.encode("hex"), 16) + if self.__firmware == 6: + while len(attendance_data) >= 8: + uid, status, timestamp = unpack('HH4s', attendance_data.ljust(8)[:8]) + user_id = str(uid) #TODO revisar posibles valores cruzar con userdata + timestamp = self.__decode_time(timestamp) + attendance = Attendance(uid, user_id, timestamp, status) + attendances.append(attendance) + attendance_data = attendance_data[8:] + else: + while len(attendance_data) >= 40: + uid, user_id, sparator, timestamp, status, space = unpack('H24sc4sc8s', attendance_data.ljust(40)[:40]) + user_id = user_id.split('\x00')[0] + timestamp = self.__decode_time(timestamp) + status = int(status.encode("hex"), 16) - attendance = Attendance(uid, user_id, timestamp, status) - attendances.append(attendance) + attendance = Attendance(uid, user_id, timestamp, status) + attendances.append(attendance) - attendance_data = attendance_data[40:] + attendance_data = attendance_data[40:] else: raise ZKErrorResponse("Invalid response") - return attendances def clear_attendance(self): @@ -594,7 +625,6 @@ class ZK(object): session_id = self.__sesion_id reply_id = self.__reply_id response_size = 1024 - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) if cmd_response.get('status'): return True diff --git a/zk/user.py b/zk/user.py index 46fa14a..5da5a97 100644 --- a/zk/user.py +++ b/zk/user.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- class User(object): - def __init__(self, uid, name, privilege, password='', group_id='', user_id=''): + def __init__(self, uid, name, privilege, password='', group_id='', user_id='', card=0): self.uid = uid self.name = name self.privilege = privilege From a3a184af084ca002570023dc3cb7c681b540c1a0 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Fri, 16 Mar 2018 20:00:24 -0400 Subject: [PATCH 08/83] testing extra commands --- zk/base.py | 57 ++++++++ zk6.lua | 387 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 444 insertions(+) create mode 100644 zk6.lua diff --git a/zk/base.py b/zk/base.py index f81fba0..82fcaa3 100644 --- a/zk/base.py +++ b/zk/base.py @@ -295,6 +295,61 @@ class ZK(object): else: raise ZKErrorResponse("Invalid response") + def get_platform(self): + ''' + return the serial number + ''' + command = const.CMD_OPTIONS_RRQ + command_string = '~Platform' + checksum = 0 + session_id = self.__sesion_id + reply_id = self.__reply_id + response_size = 1024 + + cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + if cmd_response.get('status'): + platform = self.__data_recv[8:].split('=')[-1].split('\x00')[0] + return platform + else: + raise ZKErrorResponse("Invalid response") + + def get_device_name(self): + ''' + return the serial number + ''' + command = const.CMD_OPTIONS_RRQ + command_string = '~DeviceName' + checksum = 0 + session_id = self.__sesion_id + reply_id = self.__reply_id + response_size = 1024 + + cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + if cmd_response.get('status'): + device = self.__data_recv[8:].split('=')[-1].split('\x00')[0] + return device + else: + raise ZKErrorResponse("Invalid response") + + def get_pin_width(self): + ''' + return the serial number + ''' + command = const.CMD_GET_PINWIDTH + command_string = ' P' + checksum = 0 + session_id = self.__sesion_id + reply_id = self.__reply_id + response_size = 1024 + + cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + if cmd_response.get('status'): + width = self.__data_recv[8:]..split('\x00')[0] + returnbytearray(width)[0] + else: + raise ZKErrorResponse("Invalid response") + + def restart(self): ''' restart the device @@ -528,7 +583,9 @@ class ZK(object): start enroll user ''' command = const.CMD_STARTENROLL + print "enrol uid b", uid uid = chr(uid % 256) + chr(uid >> 8) + print "enrol uid a", uid command_string = pack('2s', uid) checksum = 0 session_id = self.__sesion_id diff --git a/zk6.lua b/zk6.lua new file mode 100644 index 0000000..f8db47b --- /dev/null +++ b/zk6.lua @@ -0,0 +1,387 @@ +---------------------------------------- +-- script-name: zk6_udp_dissector.lua +-- +-- author: Arturo Hernandez +-- Copyright (c) 2018 +-- This code is in the Public Domain, or the BSD (3 clause) license if Public Domain does not apply +-- in your country. +-- +-- Version: 1.0 +-- +-- BACKGROUND: +-- based on the example dns_dissector.lua from Hadriel Kaplan +-- +-- OVERVIEW: +-- This script creates an dissector for the UDP protocol on ZK products. +-- to the DNS protocol. That's OK. The goal isn't to fully dissect DNS properly - Wireshark already has a good +-- DNS dissector built-in. We don't need another one. We also have other example Lua scripts, but I don't think +-- they do a good job of explaining things, and the nice thing about this one is getting capture files to +-- run it against is trivial. (plus I uploaded one) +-- +-- HOW TO RUN THIS SCRIPT: +-- Wireshark and Tshark support multiple ways of loading Lua scripts: through a dofile() call in init.lua, +-- through the file being in either the global or personal plugins directories, or via the command line. +-- See the Wireshark USer's Guide chapter on Lua (http://www.wireshark.org/docs/wsug_html_chunked/wsluarm.html). +-- Once the script is loaded, it creates a new protocol named "MyDNS" (or "MYDNS" in some places). If you have +-- a capture file with DNS packets in it, simply select one in the Packet List pane, right-click on it, and +-- select "Decode As ...", and then in the dialog box that shows up scroll down the list of protocols to one +-- called "MYDNS", select that and click the "ok" or "apply" button. Voila`, you're now decoding DNS packets +-- using the simplistic dissector in this script. Another way is to download the capture file made for +-- this script, and open that - since the DNS packets in it use UDP port 65333 (instead of the default 53), +-- and since the MyDNS protocol in this script has been set to automatically decode UDP port 65333, it will +-- automagically do it without doing "Decode As ...". +-- +---------------------------------------- +print("hello world!") +-- do not modify this table +local debug_level = { + DISABLED = 0, + LEVEL_1 = 1, + LEVEL_2 = 2 +} + +-- set this DEBUG to debug_level.LEVEL_1 to enable printing debug_level info +-- set it to debug_level.LEVEL_2 to enable really verbose printing +-- note: this will be overridden by user's preference settings +local DEBUG = debug_level.LEVEL_1 + +local default_settings = { + debug_level = DEBUG, + port = 4370, + heur_enabled = false, +} + +-- for testing purposes, we want to be able to pass in changes to the defaults +-- from the command line; because you can't set lua preferences from the command +-- line using the '-o' switch (the preferences don't exist until this script is +-- loaded, so the command line thinks they're invalid preferences being set) +-- so we pass them in as command arguments insetad, and handle it here: +local args={...} -- get passed-in args +if args and #args > 0 then + for _, arg in ipairs(args) do + local name, value = arg:match("(.+)=(.+)") + if name and value then + if tonumber(value) then + value = tonumber(value) + elseif value == "true" or value == "TRUE" then + value = true + elseif value == "false" or value == "FALSE" then + value = false + elseif value == "DISABLED" then + value = debug_level.DISABLED + elseif value == "LEVEL_1" then + value = debug_level.LEVEL_1 + elseif value == "LEVEL_2" then + value = debug_level.LEVEL_2 + else + error("invalid commandline argument value") + end + else + error("invalid commandline argument syntax") + end + + default_settings[name] = value + end +end + +local dprint = function() end +local dprint2 = function() end +local function reset_debug_level() + if default_settings.debug_level > debug_level.DISABLED then + dprint = function(...) + print(table.concat({"Lua:", ...}," ")) + end + + if default_settings.debug_level > debug_level.LEVEL_1 then + dprint2 = dprint + end + end +end +-- call it now +reset_debug_level() + +dprint2("Wireshark version = ", get_version()) +dprint2("Lua version = ", _VERSION) + +---------------------------------------- +-- Unfortunately, the older Wireshark/Tshark versions have bugs, and part of the point +-- of this script is to test those bugs are now fixed. So we need to check the version +-- end error out if it's too old. +local major, minor, micro = get_version():match("(%d+)%.(%d+)%.(%d+)") +if major and tonumber(major) <= 1 and ((tonumber(minor) <= 10) or (tonumber(minor) == 11 and tonumber(micro) < 3)) then + error( "Sorry, but your Wireshark/Tshark version ("..get_version()..") is too old for this script!\n".. + "This script needs Wireshark/Tshark version 1.11.3 or higher.\n" ) +end + +-- more sanity checking +-- verify we have the ProtoExpert class in wireshark, as that's the newest thing this file uses +assert(ProtoExpert.new, "Wireshark does not have the ProtoExpert class, so it's too old - get the latest 1.11.3 or higher") + +---------------------------------------- + + +---------------------------------------- +-- creates a Proto object, but doesn't register it yet +local zk = Proto("zk6","ZK600 UDP Protocol") + +local rcomands = { + [7] = "CMD_DB_RRQ", + [8] = "CMD_USER_WRQ", + [9] = "CMD_USERTEMP_RRQ", + [10] = "CMD_USERTEMP_WRQ", + [11] = "CMD_OPTIONS_RRQ", + [12] = "CMD_OPTIONS_WRQ", + [13] = "CMD_ATTLOG_RRQ", + [14] = "CMD_CLEAR_DATA", + [15] = "CMD_CLEAR_ATTLOG", + [18] = "CMD_DELETE_USER", + [19] = "CMD_DELETE_USERTEMP", + [20] = "CMD_CLEAR_ADMIN", + [21] = "CMD_USERGRP_RRQ", + [22] = "CMD_USERGRP_WRQ", + [23] = "CMD_USERTZ_RRQ", + [24] = "CMD_USERTZ_WRQ", + [25] = "CMD_GRPTZ_RRQ", + [26] = "CMD_GRPTZ_WRQ", + [27] = "CMD_TZ_RRQ", + [28] = "CMD_TZ_WRQ", + [29] = "CMD_ULG_RRQ", + [30] = "CMD_ULG_WRQ", + [31] = "CMD_UNLOCK", + [32] = "CMD_CLEAR_ACC", + [33] = "CMD_CLEAR_OPLOG", + [34] = "CMD_OPLOG_RRQ", + [50] = "CMD_GET_FREE_SIZES", + [57] = "CMD_ENABLE_CLOCK", + [60] = "CMD_STARTVERIFY", + [61] = "CMD_STARTENROLL", + [62] = "CMD_CANCELCAPTURE", + [64] = "CMD_STATE_RRQ", + [66] = "CMD_WRITE_LCD", + [67] = "CMD_CLEAR_LCD", + [69] = "CMD_GET_PINWIDTH", + [70] = "CMD_SMS_WRQ", + [71] = "CMD_SMS_RRQ", + [72] = "CMD_DELETE_SMS", + [73] = "CMD_UDATA_WRQ", + [74] = "CMD_DELETE_UDATA", + [75] = "CMD_DOORSTATE_RRQ", + [76] = "CMD_WRITE_MIFARE", + [78] = "CMD_EMPTY_MIFARE", + [201] = "CMD_GET_TIME", + [202] = "CMD_SET_TIME", + [500] = "CMD_REG_EVENT", + [1000] = "CMD_CONNECT", + [1001] = "CMD_EXIT", + [1002] = "CMD_ENABLEDEVICE", + [1003] = "CMD_DISABLEDEVICE", + [1004] = "CMD_RESTART", + [1005] = "CMD_POWEROFF", + [1006] = "CMD_SLEEP", + [1007] = "CMD_RESUME", + [1009] = "CMD_CAPTUREFINGER", + [1011] = "CMD_TEST_TEMP", + [1012] = "CMD_CAPTUREIMAGE", + [1013] = "CMD_REFRESHDATA", + [1014] = "CMD_REFRESHOPTION", + [1017] = "CMD_TESTVOICE", + [1100] = "CMD_GET_VERSION", + [1101] = "CMD_CHANGE_SPEED", + [1102] = "CMD_AUTH", + [1500] = "CMD_PREPARE_DATA", + [1501] = "CMD_DATA", + [1502] = "CMD_FREE_DATA", + [2000] = "CMD_ACK_OK", + [2001] = "CMD_ACK_ERROR", + [2002] = "CMD_ACK_DATA", + [2003] = "CMD_ACK_RETRY", + [2004] = "CMD_ACK_REPEAT", + [2005] = "CMD_ACK_UNAUTH", + [65535] = "CMD_ACK_UNKNOWN", + [65533] = "CMD_ACK_ERROR_CMD", + [65532] = "CMD_ACK_ERROR_INIT", + [65531] = "CMD_ACK_ERROR_DATA" +} +---------------------------------------- +local pf_command = ProtoField.new ("Command", "zk6.command", ftypes.UINT16, rcomands, base.DEC) +local pf_checksum = ProtoField.new ("CheckSum", "zk6.checksum", ftypes.UINT16, nil, base.HEX) +local pf_sesion_id = ProtoField.uint16("zk6.session_id", "ID session", base.HEX) +local pf_reply_id = ProtoField.uint16("zk6.reply_id", "ID Reply") +local pf_data = ProtoField.new ("Data", "zk6.data", ftypes.BYTES, nil, base.DOT) +local pf_time = ProtoField.new ("Time", "zk6.time", ftypes.UINT32, nil) + +---------------------------------------- +-- this actually registers the ProtoFields above, into our new Protocol +-- in a real script I wouldn't do it this way; I'd build a table of fields programmatically +-- and then set dns.fields to it, so as to avoid forgetting a field +zk.fields = { pf_command, pf_checksum, pf_sesion_id, pf_reply_id, pf_data, pf_time} + +---------------------------------------- +-- we don't just want to display our protocol's fields, we want to access the value of some of them too! +-- There are several ways to do that. One is to just parse the buffer contents in Lua code to find +-- the values. But since ProtoFields actually do the parsing for us, and can be retrieved using Field +-- objects, it's kinda cool to do it that way. So let's create some Fields to extract the values. +-- The following creates the Field objects, but they're not 'registered' until after this script is loaded. +-- Also, these lines can't be before the 'dns.fields = ...' line above, because the Field.new() here is +-- referencing fields we're creating, and they're not "created" until that line above. +-- Furthermore, you cannot put these 'Field.new()' lines inside the dissector function. +-- Before Wireshark version 1.11, you couldn't even do this concept (of using fields you just created). +local command_field = Field.new("zk6.command") +local checksum_field = Field.new("zk6.checksum") +local session_id_field = Field.new("zk6.session_id") +local reply_id_field = Field.new("zk6.reply_id") +local data_field = Field.new("zk6.data") +local time_field =Field.new("zk6.time") +-- here's a little helper function to access the response_field value later. +-- Like any Field retrieval, you can't retrieve a field's value until its value has been +-- set, which won't happen until we actually use our ProtoFields in TreeItem:add() calls. +-- So this isResponse() function can't be used until after the pf_flag_response ProtoField +-- has been used inside the dissector. +-- Note that calling the Field object returns a FieldInfo object, and calling that +-- returns the value of the field - in this case a boolean true/false, since we set the +-- "mydns.flags.response" ProtoField to ftype.BOOLEAN way earlier when we created the +-- pf_flag_response ProtoField. Clear as mud? +-- +-- A shorter version of this function would be: +-- local function isResponse() return response_field()() end +-- but I though the below is easier to understand. +local function isResponse() + local response_fieldinfo = response_field() + return response_fieldinfo() +end + +-------------------------------------------------------------------------------- +-- preferences handling stuff +-------------------------------------------------------------------------------- + +-- a "enum" table for our enum pref, as required by Pref.enum() +-- having the "index" number makes ZERO sense, and is completely illogical +-- but it's what the code has expected it to be for a long time. Ugh. +local debug_pref_enum = { + { 1, "Disabled", debug_level.DISABLED }, + { 2, "Level 1", debug_level.LEVEL_1 }, + { 3, "Level 2", debug_level.LEVEL_2 }, +} + +zk.prefs.debug = Pref.enum("Debug", default_settings.debug_level, + "The debug printing level", debug_pref_enum) + +zk.prefs.port = Pref.uint("Port number", default_settings.port, + "The UDP port number for MyDNS") + +zk.prefs.heur = Pref.bool("Heuristic enabled", default_settings.heur_enabled, + "Whether heuristic dissection is enabled or not") + +---------------------------------------- +-- a function for handling prefs being changed +function zk.prefs_changed() + dprint2("prefs_changed called") + + default_settings.debug_level = zk.prefs.debug + reset_debug_level() + + default_settings.heur_enabled = zk.prefs.heur + + if default_settings.port ~= zk.prefs.port then + -- remove old one, if not 0 + if default_settings.port ~= 0 then + dprint2("removing ZK6 from port",default_settings.port) + DissectorTable.get("udp.port"):remove(default_settings.port, zk) + end + -- set our new default + default_settings.port = dns.prefs.port + -- add new one, if not 0 + if default_settings.port ~= 0 then + dprint2("adding ZK6 to port",default_settings.port) + DissectorTable.get("udp.port"):add(default_settings.port, zk) + end + end + +end + +dprint2("ZK6 Prefs registered") + + +---------------------------------------- +---- some constants for later use ---- +-- the DNS header size +local ZK_HDR_LEN = 8 + +-- the smallest possible DNS query field size +-- has to be at least a label length octet, label character, label null terminator, 2-bytes type and 2-bytes class +local MIN_QUERY_LEN = 7 + +---------------------------------------- +-- some forward "declarations" of helper functions we use in the dissector +-- I don't usually use this trick, but it'll help reading/grok'ing this script I think +-- if we don't focus on them. +local getQueryName + +local prevCommand = 0 + +---------------------------------------- +-- The following creates the callback function for the dissector. +-- It's the same as doing "dns.dissector = function (tvbuf,pkt,root)" +-- The 'tvbuf' is a Tvb object, 'pktinfo' is a Pinfo object, and 'root' is a TreeItem object. +-- Whenever Wireshark dissects a packet that our Proto is hooked into, it will call +-- this function and pass it these arguments for the packet it's dissecting. +function zk.dissector(tvbuf,pktinfo,root) + dprint2("zk.dissector called") + + -- set the protocol column to show our protocol name + pktinfo.cols.protocol:set("ZK6") + + -- We want to check that the packet size is rational during dissection, so let's get the length of the + -- packet buffer (Tvb). + -- Because DNS has no additional payload data other than itself, and it rides on UDP without padding, + -- we can use tvb:len() or tvb:reported_len() here; but I prefer tvb:reported_length_remaining() as it's safer. + local pktlen = tvbuf:reported_length_remaining() + + -- We start by adding our protocol to the dissection display tree. + -- A call to tree:add() returns the child created, so we can add more "under" it using that return value. + -- The second argument is how much of the buffer/packet this added tree item covers/represents - in this + -- case (DNS protocol) that's the remainder of the packet. + local tree = root:add(zk, tvbuf:range(0,pktlen)) + + -- now let's check it's not too short + if pktlen < ZK_HDR_LEN then + -- since we're going to add this protocol to a specific UDP port, we're going to + -- assume packets in this port are our protocol, so the packet being too short is an error + -- the old way: tree:add_expert_info(PI_MALFORMED, PI_ERROR, "packet too short") + -- the correct way now: + tree:add_proto_expert_info(ef_too_short) + dprint("packet length",pktlen,"too short") + return + end + + -- Now let's add our transaction id under our dns protocol tree we just created. + -- The transaction id starts at offset 0, for 2 bytes length. + tree:add_le(pf_command, tvbuf:range(0,2)) + tree:add_le(pf_checksum, tvbuf:range(2,2)) + tree:add_le(pf_sesion_id, tvbuf:range(4,2)) + tree:add_le(pf_reply_id, tvbuf:range(6,2)) + if pktlen > ZK_HDR_LEN then + remain = pktlen - ZK_HDR_LEN -- TODO: no funciona el prevCommand, + if (prevCommand == 201) or (prevCommand == 202) then + local ts = tvbuf:range(8,4):le_uint() + tree:add_le(pf_time, tvbuf:range(8,4)) + else + tree:add_le(pf_data, tvbuf:range(8,remain)) + end + end + dprint2("zk.dissector returning",pktlen) + prevCommand = tvbuf:range(0,2):le_uint() + -- tell wireshark how much of tvbuff we dissected + return pktlen +end + +---------------------------------------- +-- we want to have our protocol dissection invoked for a specific UDP port, +-- so get the udp dissector table and add our protocol to it +DissectorTable.get("udp.port"):add(default_settings.port, zk) + + +-- We're done! +-- our protocol (Proto) gets automatically registered after this script finishes loading +---------------------------------------- + From d123ddbd63f0024aae61b9ef822e22d75d82e78f Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Tue, 20 Mar 2018 19:59:04 -0400 Subject: [PATCH 09/83] test, read fingerprint data works! but needs more test, we need to test write and pass to another device --- zk/base.py | 140 ++++++++++++++++++++++++++++++++++++++++++++++++--- zk/finger.py | 14 ++++++ 2 files changed, 147 insertions(+), 7 deletions(-) create mode 100644 zk/finger.py diff --git a/zk/base.py b/zk/base.py index 82fcaa3..80679ce 100644 --- a/zk/base.py +++ b/zk/base.py @@ -8,6 +8,7 @@ from zk import const from zk.attendance import Attendance from zk.exception import ZKErrorResponse, ZKNetworkError from zk.user import User +from zk.finger import Finger def make_commkey(key, session_id, ticks=50): """take a password and session_id and scramble them to send to the time @@ -59,7 +60,17 @@ class ZK(object): self.__sock.settimeout(timeout) self.__password = password # passint self.__firmware = int(firmware) #TODO check minor version? - + self.users = 0 + self.fingers = 0 + self.records = 0 + self.dummy = 0 + self.cards = 0 + self.fingers_cap = 0 + self.users_cap = 0 + self.rec_cap = 0 + self.fingers_av = 0 + self.users_av = 0 + self.rec_av = 0 def __create_header(self, command, command_string, checksum, session_id, reply_id): ''' Puts a the parts that make up a packet together and packs them into a byte string @@ -201,7 +212,7 @@ class ZK(object): cmd_response = self.__send_command(const.CMD_AUTH, command_string , checksum, self.__sesion_id, self.__reply_id, response_size) if cmd_response.get('status'): self.is_connect = True - # set the session id + # set the session iduid, privilege, password, name, card, group_id, timezone, user_id = unpack('HB5s8s5sBhI',userdata.ljust(28)[:28]) return self else: print "connect err {} ".format(cmd_response["code"]) @@ -344,11 +355,62 @@ class ZK(object): cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) if cmd_response.get('status'): - width = self.__data_recv[8:]..split('\x00')[0] - returnbytearray(width)[0] + width = self.__data_recv[8:].split('\x00')[0] + return bytearray(width)[0] else: raise ZKErrorResponse("Invalid response") + def free_data(self): + """ clear buffer""" + command = const.CMD_FREE_DATA + command_string = '' + checksum = 0 + session_id = self.__sesion_id + reply_id = self.__reply_id + response_size = 1024 + + cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + if cmd_response.get('status'): + return True + else: + raise ZKErrorResponse("Invalid response") + + def read_sizes(self): + """ read sizes """ + command = const.CMD_GET_FREE_SIZES + command_string = '' + checksum = 0 + session_id = self.__sesion_id + reply_id = self.__reply_id + response_size = 1024 + + cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + if cmd_response.get('status'): + fields = unpack('iiiiiiiiiiiiiiiiiiii', self.__data_recv[8:]) + self.users = fields[4] + self.fingers = fields[6] + self.records = fields[8] + self.dummy = fields[10] #??? + self.cards = fields[12] + self.fingers_cap = fields[14] + self.users_cap = fields[15] + self.rec_cap = fields[16] + self.fingers_av = fields[17] + self.users_av = fields[18] + self.rec_av = fields[19] + #TODO: get faces size... + + return True + else: + raise ZKErrorResponse("Invalid response") + + def __str__(self): + """ for debug""" + return "ZK%i adr:%s:%s users:%i/%i fingers:%i/%i, records:%i/%i" % ( + self.__firmware, self.__address[0], self.__address[1], + self.users, self.users_cap, self.fingers, self.fingers_cap, + self.records, self.rec_cap + ) def restart(self): ''' @@ -381,6 +443,7 @@ class ZK(object): return self.__decode_time(self.__data_recv[8:12]) else: raise ZKErrorResponse("Invalid response") + def set_time(self, timestamp): """ colocar la hora del sistema al zk """ command = const.CMD_SET_TIME @@ -416,7 +479,7 @@ class ZK(object): ''' play test voice ''' - command = const.CMD_TESTVOICE + command = const.CMD_TESTVOICEuid, privilege, password, name, card, group_id, timezone, user_id = unpack('HB5s8s5sBhI',userdata.ljust(28)[:28]) command_string = '' checksum = 0 session_id = self.__sesion_id @@ -487,7 +550,67 @@ class ZK(object): else: raise ZKErrorResponse("Invalid response") #def get_user_template(self, uid, finger): + def get_templates(self): + """ return array of fingers""" + command = const.CMD_DB_RRQ + command_string = chr(const.FCT_FINGERTMP) + checksum = 0 + session_id = self.__sesion_id + reply_id = self.__reply_id + response_size = 1024 + cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + templates =[] + pac = 0 + if cmd_response.get('status'): + if cmd_response.get('code') == const.CMD_PREPARE_DATA: + bytes = self.__get_data_size() + templatedata = [] + while True: + data_recv = self.__sock.recv(1032) + response = unpack('HHHH', data_recv[:8])[0] + if response == const.CMD_DATA: + pac += 1 + templatedata.append(data_recv[8:]) #header turncated + bytes -= 1024 + elif response == const.CMD_ACK_OK: + break #without problem. + else: + #truncado! continuar? + #print "broken! with %s" % response + #print "user still needs %s" % bytes + break + + if response == const.CMD_ACK_OK: + if templatedata: + # first 4 bytes, total size of template data + templatedata = ''.join(templatedata) + total_size = unpack('i', templatedata[0:4])[0] + print "total size: ", total_size + templatedata = templatedata[4:] + if self.__firmware == 6: #tested! + while total_size: + size, uid, fid, valid = unpack('HHbb',templatedata[:6]) + template = unpack("%is" % (size-6), templatedata[6:size]) + finger = Finger(size,uid,fid,valid,template) + print finger # test + templates.append(finger) + templatedata = templatedata[(size):] + total_size -= size + else: # TODO: test!!! + while total_size: + size, uid, fid, valid = unpack('HHbb',templatedata[:6]) + template = unpack("%is" % (size-6), templatedata[6:size]) + finger = Finger(size,uid,fid,valid,template) + print finger # test + templates.append(finger) + templatedata = templatedata[(size):] + total_size -= size + self.free_data() + else: + raise ZKErrorResponse("Invalid response") + return templates + def get_users(self): ''' return all user @@ -536,7 +659,7 @@ class ZK(object): user_id = str(user_id) #TODO: check card value and find in ver8 if not name: - name = user_id + name = "NN-%s" % user_id user = User(uid, name, privilege, password, group_id, user_id) users.append(user) print "[6]user:",uid, privilege, password, name, card, group_id, timezone, user_id @@ -552,11 +675,13 @@ class ZK(object): name = unicode(name.split('\x00')[0], errors='ignore').strip() group_id = unicode(group_id.split('\x00')[0], errors='ignore').strip() user_id = unicode(user_id.split('\x00')[0], errors='ignore') - + if not name: + name = "NN-%s" % user_id user = User(uid, name, privilege, password, group_id, user_id) users.append(user) userdata = userdata[72:] + self.free_data() else: raise ZKErrorResponse("Invalid response") return users @@ -668,6 +793,7 @@ class ZK(object): attendances.append(attendance) attendance_data = attendance_data[40:] + self.free_data() else: raise ZKErrorResponse("Invalid response") return attendances diff --git a/zk/finger.py b/zk/finger.py new file mode 100644 index 0000000..bc3038e --- /dev/null +++ b/zk/finger.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +class Finger(object): + def __init__(self, size, uid, fid, valid, template): + self.size = size + self.uid = uid + self.fid = fid + self.valid = valid + self.template = template + + def __str__(self): + return " [u:%i, fid:%i, size:%i ]" % (self.uid, self.fid, self.size) + + def __repr__(self): + return " [u:%i, fid:%i, size:%i ]" % (self.uid, self.fid, self.size) From f5f1b1c1c4c7301769e734be3eb62c7124709cc2 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Fri, 23 Mar 2018 19:44:00 -0400 Subject: [PATCH 10/83] agregado lectura de huellas y lectura bulk --- zk/base.py | 172 +++++++++++++++++++++++++++++++++++++++++++++++++-- zk/finger.py | 3 + 2 files changed, 171 insertions(+), 4 deletions(-) diff --git a/zk/base.py b/zk/base.py index 80679ce..cf56ff5 100644 --- a/zk/base.py +++ b/zk/base.py @@ -497,7 +497,8 @@ class ZK(object): create or update user by uid ''' command = const.CMD_USER_WRQ - + if not user_id: + user_id = str(uid) #ZK6 needs uid2 == uid #uid = chr(uid % 256) + chr(uid >> 8) if privilege not in [const.USER_DEFAULT, const.USER_ADMIN]: privilege = const.USER_DEFAULT @@ -550,7 +551,37 @@ class ZK(object): else: raise ZKErrorResponse("Invalid response") #def get_user_template(self, uid, finger): + def get_templates(self): + """ return array of finger """ + templates = [] + templatedata, size = self.read_with_buffer(const.CMD_DB_RRQ, const.FCT_FINGERTMP) + if size < 4: + print "WRN: no user data" # debug + return [] + total_size = unpack('i', templatedata[0:4])[0] + templatedata = templatedata[4:] #total size not used + if self.__firmware == 6: #tested! + while total_size: + size, uid, fid, valid = unpack('HHbb',templatedata[:6]) + template = unpack("%is" % (size-6), templatedata[6:size]) + finger = Finger(size, uid, fid, valid, template) + print finger # test + templates.append(finger) + templatedata = templatedata[size:] + total_size -= size + else: # TODO: test!!! + while total_size: + size, uid, fid, valid = unpack('HHbb',templatedata[:6]) + template = unpack("%is" % (size-6), templatedata[6:size]) + finger = Finger(size, uid, fid, valid, template) + print finger # test + templates.append(finger) + templatedata = templatedata[(size):] + total_size -= size + return templates + + def _get_templates(self): """ return array of fingers""" command = const.CMD_DB_RRQ command_string = chr(const.FCT_FINGERTMP) @@ -610,8 +641,49 @@ class ZK(object): raise ZKErrorResponse("Invalid response") return templates - def get_users(self): + """ return all user """ + users = [] + userdata, size = self.read_with_buffer(const.CMD_USERTEMP_RRQ, const.FCT_USER) + print "user size %i" % size + if size < 4: + print "WRN: no user data" # debug + return [] + userdata = userdata[4:] #total size not used + if self.__firmware == 6: + while len(userdata) >= 28: + uid, privilege, password, name, card, group_id, timezone, user_id = unpack('HB5s8s5sBhI',userdata.ljust(28)[:28]) + password = unicode(password.split('\x00')[0], errors='ignore') + name = unicode(name.split('\x00')[0], errors='ignore').strip() + card = unpack('Q', card.ljust(8,'\x00'))[0] #or hex value? + group_id = str(group_id) + user_id = str(user_id) + #TODO: check card value and find in ver8 + if not name: + name = "NN-%s" % user_id + user = User(uid, name, privilege, password, group_id, user_id) + users.append(user) + print "[6]user:",uid, privilege, password, name, card, group_id, timezone, user_id + userdata = userdata[28:] + else: + while len(userdata) >= 72: + uid, privilege, password, name, sparator, group_id, user_id = unpack('Hc8s28sc7sx24s', userdata.ljust(72)[:72]) + #u1 = int(uid[0].encode("hex"), 16) + #u2 = int(uid[1].encode("hex"), 16) + #uid = u1 + (u2 * 256) + privilege = int(privilege.encode("hex"), 16) + password = unicode(password.split('\x00')[0], errors='ignore') + name = unicode(name.split('\x00')[0], errors='ignore').strip() + group_id = unicode(group_id.split('\x00')[0], errors='ignore').strip() + user_id = unicode(user_id.split('\x00')[0], errors='ignore') + if not name: + name = "NN-%s" % user_id + user = User(uid, name, privilege, password, group_id, user_id) + users.append(user) + userdata = userdata[72:] + return users + + def _get_users(self): ''' return all user ''' @@ -691,8 +763,13 @@ class ZK(object): cancel capturing finger ''' command = const.CMD_CANCELCAPTURE - cmd_response = self.__send_command(command=command) - print cmd_response + command_string = '' + checksum = 0 + session_id = self.__sesion_id + reply_id = self.__reply_id + response_size = 8 + cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + return bool(cmd_response.get('status')) def verify_user(self): ''' @@ -737,7 +814,94 @@ class ZK(object): else: raise ZKErrorResponse("Invalid response") + def __read_chunk(self, start, size): + """ read a chunk from buffer """ + command = 1504 #CMD_READ_BUFFER + command_string = pack('= 8: + uid, status, timestamp = unpack('HH4s', attendance_data.ljust(8)[:8]) + user_id = str(uid) #TODO revisar posibles valores cruzar con userdata + timestamp = self.__decode_time(timestamp) + attendance = Attendance(uid, user_id, timestamp, status) + attendances.append(attendance) + attendance_data = attendance_data[8:] + else: + while len(attendance_data) >= 40: + uid, user_id, sparator, timestamp, status, space = unpack('H24sc4sc8s', attendance_data.ljust(40)[:40]) + user_id = user_id.split('\x00')[0] + timestamp = self.__decode_time(timestamp) + status = int(status.encode("hex"), 16) + + attendance = Attendance(uid, user_id, timestamp, status) + attendances.append(attendance) + attendance_data = attendance_data[40:] + return attendances + def _get_attendance(self): ''' return all attendance record ''' diff --git a/zk/finger.py b/zk/finger.py index bc3038e..d7b2713 100644 --- a/zk/finger.py +++ b/zk/finger.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from struct import pack #, unpack class Finger(object): def __init__(self, size, uid, fid, valid, template): self.size = size @@ -6,6 +7,8 @@ class Finger(object): self.fid = fid self.valid = valid self.template = template + def repack(self): + return pack("HHbb%is" % (self.size-6), self.size, self.uid, self.fid, self.valid, self.template) def __str__(self): return " [u:%i, fid:%i, size:%i ]" % (self.uid, self.fid, self.size) From 3a2d82c83ca2561f6c784fa3f40cab3695451d32 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Tue, 27 Mar 2018 19:32:56 -0400 Subject: [PATCH 11/83] lectura huella individual --- zk/base.py | 126 +++++++++++++++++++++++---------------------------- zk/finger.py | 5 +- zk/user.py | 1 + 3 files changed, 60 insertions(+), 72 deletions(-) diff --git a/zk/base.py b/zk/base.py index cf56ff5..475160f 100644 --- a/zk/base.py +++ b/zk/base.py @@ -492,7 +492,7 @@ class ZK(object): else: raise ZKErrorResponse("Invalid response") - def set_user(self, uid, name, privilege, password='', group_id='', user_id=''): + def set_user(self, uid, name, privilege=0, password='', group_id='', user_id='', card=0): ''' create or update user by uid ''' @@ -531,6 +531,23 @@ class ZK(object): else: raise ZKErrorResponse("Invalid response") + def delete_user_template(self, uid, temp_id): + """ + Delete specific template + """ + command = const.CMD_DELETE_USERTEMP + command_string = pack('hb', uid, temp_id) + checksum = 0 + session_id = self.__sesion_id + reply_id = self.__reply_id + response_size = 1024 + + cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + if cmd_response.get('status'): + return True #refres_data (1013)? + else: + raise ZKErrorResponse("Invalid response") + def delete_user(self, uid): ''' delete specific user by uid @@ -550,10 +567,42 @@ class ZK(object): return True else: raise ZKErrorResponse("Invalid response") - #def get_user_template(self, uid, finger): + + def get_user_template(self, uid, temp_id): + command = 88 # comando secreto!!! + command_string = pack('hb', uid, temp_id) + checksum = 0 + session_id = self.__sesion_id + reply_id = self.__reply_id + response_size = 1024 + cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + data = [] + if not cmd_response.get('status'): + raise ZKErrorResponse("Invalid response") + #else + if cmd_response.get('code') == const.CMD_PREPARE_DATA: + bytes = self.__get_data_size() #TODO: check with size + size = bytes + while True: #limitado por respuesta no por tamaƱo + data_recv = self.__sock.recv(1032) + response = unpack('HHHH', data_recv[:8])[0] + #print "# %s packet response is: %s" % (pac, response) + if response == const.CMD_DATA: + data.append(data_recv[8:]) #header turncated + bytes -= 1024 + elif response == const.CMD_ACK_OK: + break #without problem. + else: + #truncado! continuar? + #print "broken!" + break + #print "still needs %s" % bytes + data = ''.join(data) + #uid 32 fing 03, starts with 4d-9b-53-53-32-31 + return Finger(size + 6, uid, temp_id, 1, data) # TODO: confirm def get_templates(self): - """ return array of finger """ + """ return array of all fingers """ templates = [] templatedata, size = self.read_with_buffer(const.CMD_DB_RRQ, const.FCT_FINGERTMP) if size < 4: @@ -564,7 +613,7 @@ class ZK(object): if self.__firmware == 6: #tested! while total_size: size, uid, fid, valid = unpack('HHbb',templatedata[:6]) - template = unpack("%is" % (size-6), templatedata[6:size]) + template = unpack("%is" % (size-6), templatedata[6:size])[0] finger = Finger(size, uid, fid, valid, template) print finger # test templates.append(finger) @@ -573,73 +622,13 @@ class ZK(object): else: # TODO: test!!! while total_size: size, uid, fid, valid = unpack('HHbb',templatedata[:6]) - template = unpack("%is" % (size-6), templatedata[6:size]) + template = unpack("%is" % (size-6), templatedata[6:size])[0] finger = Finger(size, uid, fid, valid, template) print finger # test templates.append(finger) templatedata = templatedata[(size):] total_size -= size return templates - - def _get_templates(self): - """ return array of fingers""" - command = const.CMD_DB_RRQ - command_string = chr(const.FCT_FINGERTMP) - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id - response_size = 1024 - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) - templates =[] - pac = 0 - if cmd_response.get('status'): - if cmd_response.get('code') == const.CMD_PREPARE_DATA: - bytes = self.__get_data_size() - templatedata = [] - while True: - data_recv = self.__sock.recv(1032) - response = unpack('HHHH', data_recv[:8])[0] - if response == const.CMD_DATA: - pac += 1 - templatedata.append(data_recv[8:]) #header turncated - bytes -= 1024 - elif response == const.CMD_ACK_OK: - break #without problem. - else: - #truncado! continuar? - #print "broken! with %s" % response - #print "user still needs %s" % bytes - break - - if response == const.CMD_ACK_OK: - if templatedata: - # first 4 bytes, total size of template data - templatedata = ''.join(templatedata) - total_size = unpack('i', templatedata[0:4])[0] - print "total size: ", total_size - templatedata = templatedata[4:] - if self.__firmware == 6: #tested! - while total_size: - size, uid, fid, valid = unpack('HHbb',templatedata[:6]) - template = unpack("%is" % (size-6), templatedata[6:size]) - finger = Finger(size,uid,fid,valid,template) - print finger # test - templates.append(finger) - templatedata = templatedata[(size):] - total_size -= size - else: # TODO: test!!! - while total_size: - size, uid, fid, valid = unpack('HHbb',templatedata[:6]) - template = unpack("%is" % (size-6), templatedata[6:size]) - finger = Finger(size,uid,fid,valid,template) - print finger # test - templates.append(finger) - templatedata = templatedata[(size):] - total_size -= size - self.free_data() - else: - raise ZKErrorResponse("Invalid response") - return templates def get_users(self): """ return all user """ @@ -780,15 +769,12 @@ class ZK(object): cmd_response = self.__send_command(command=command) print cmd_response - def enroll_user(self, uid): + def enroll_user(self, uid, temp_id=0): ''' start enroll user ''' command = const.CMD_STARTENROLL - print "enrol uid b", uid - uid = chr(uid % 256) + chr(uid >> 8) - print "enrol uid a", uid - command_string = pack('2s', uid) + command_string = pack('hhb', uid, 0, temp_id) # el 0 es misterio checksum = 0 session_id = self.__sesion_id reply_id = self.__reply_id diff --git a/zk/finger.py b/zk/finger.py index d7b2713..bf8f46d 100644 --- a/zk/finger.py +++ b/zk/finger.py @@ -7,11 +7,12 @@ class Finger(object): self.fid = fid self.valid = valid self.template = template + self.mark = str(template[:6]).encode("hex") def repack(self): return pack("HHbb%is" % (self.size-6), self.size, self.uid, self.fid, self.valid, self.template) def __str__(self): - return " [u:%i, fid:%i, size:%i ]" % (self.uid, self.fid, self.size) + return " [u:%i, fid:%i, size:%i v:%i t:%s...]" % (self.uid, self.fid, self.size, self.valid, self.mark) def __repr__(self): - return " [u:%i, fid:%i, size:%i ]" % (self.uid, self.fid, self.size) + return " [u:%i, fid:%i, size:%i v:%i t:%s...]" % (self.uid, self.fid, self.size, self.valid, self.mark) #.encode('hex') diff --git a/zk/user.py b/zk/user.py index 5da5a97..185559b 100644 --- a/zk/user.py +++ b/zk/user.py @@ -8,6 +8,7 @@ class User(object): self.password = password self.group_id = group_id self.user_id = user_id + self.card = card def __str__(self): return ': {}'.format(self.name) From ec46ef24c581ca81d38557a2b1c188421df18790 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Fri, 6 Apr 2018 20:01:35 -0400 Subject: [PATCH 12/83] pruebas enroll user --- zk/base.py | 127 +++++++++++++++++++++++++++++++++++++++++++++------ zk/finger.py | 9 ++-- zk/user.py | 5 +- 3 files changed, 122 insertions(+), 19 deletions(-) diff --git a/zk/base.py b/zk/base.py index 475160f..9839930 100644 --- a/zk/base.py +++ b/zk/base.py @@ -475,6 +475,23 @@ class ZK(object): else: raise ZKErrorResponse("Invalid response") + def refresh_data(self): + ''' + shutdown the device + ''' + command = const.CMD_REFRESHDATA + command_string = '' + checksum = 0 + session_id = self.__sesion_id + reply_id = self.__reply_id + response_size = 8 + + cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + if cmd_response.get('status'): + return True + else: + raise ZKErrorResponse("Invalid response") + def test_voice(self): ''' play test voice @@ -517,7 +534,7 @@ class ZK(object): except Exception, e: print "s_h Error pack: %s" % e print "Error pack: %s" % sys.exc_info()[0] - raise ZKErrorResponse("Invalid response") + raise ZKErrorResponse("Cant pack user") else: command_string = pack('Hc8s28sc7sx24s', uid, privilege, password, name, chr(0), group_id, user_id) checksum = 0 @@ -529,9 +546,74 @@ class ZK(object): if cmd_response.get('status'): return True else: - raise ZKErrorResponse("Invalid response") + raise ZKErrorResponse("Cant set user") + def save_user_template(self, user, fingers=[]): + """ save user and template """ + # armar paquete de huellas + if isinstance(fingers, Finger): + fingers =[Finger] + fpack = "" + table = "" + fnum = 0x10 # possibly flag + tstart = 0 + for finger in fingers: + tfp = finger.repack_only() + table += pack("> 8) - - command_string = pack('2s', uid) + #uid = chr(uid % 256) + chr(uid >> 8) + #command_string = pack('2s', uid) + command_string = pack('h', uid) checksum = 0 session_id = self.__sesion_id reply_id = self.__reply_id @@ -599,7 +681,7 @@ class ZK(object): #print "still needs %s" % bytes data = ''.join(data) #uid 32 fing 03, starts with 4d-9b-53-53-32-31 - return Finger(size + 6, uid, temp_id, 1, data) # TODO: confirm + return Finger(size, uid, temp_id, 1, data) def get_templates(self): """ return array of all fingers """ @@ -623,7 +705,7 @@ class ZK(object): while total_size: size, uid, fid, valid = unpack('HHbb',templatedata[:6]) template = unpack("%is" % (size-6), templatedata[6:size])[0] - finger = Finger(size, uid, fid, valid, template) + finger = Finger(size - 6, uid, fid, valid, template) print finger # test templates.append(finger) templatedata = templatedata[(size):] @@ -650,7 +732,7 @@ class ZK(object): #TODO: check card value and find in ver8 if not name: name = "NN-%s" % user_id - user = User(uid, name, privilege, password, group_id, user_id) + user = User(uid, name, privilege, password, group_id, user_id, card) users.append(user) print "[6]user:",uid, privilege, password, name, card, group_id, timezone, user_id userdata = userdata[28:] @@ -766,8 +848,18 @@ class ZK(object): ''' command = const.CMD_STARTVERIFY # uid = chr(uid % 256) + chr(uid >> 8) - cmd_response = self.__send_command(command=command) - print cmd_response + command_string = '' + checksum = 0 + session_id = self.__sesion_id + reply_id = self.__reply_id + response_size = 8 + cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + print 'verify', cmd_response + if cmd_response.get('status'): + return True + else: + raise ZKErrorResponse("Cant Verify") + def enroll_user(self, uid, temp_id=0): ''' @@ -781,7 +873,12 @@ class ZK(object): response_size = 8 cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) - print cmd_response + if not cmd_response.get('status'): + raise ZKErrorResponse("Cant Enroll user #%i [%i]" %(uid, temp_id)) + print "enroll", cmd_response + #retorna rapido toca esperar un reg event + data_recv = self.__sock.recv(1032) # timeout? tarda bastante... + print data_recv def clear_data(self): ''' @@ -834,7 +931,7 @@ class ZK(object): def read_with_buffer(self, command, fct=0 ,ext=0): """ Test read info with buffered command (ZK6: 1503) """ - MAX_CHUNK = 16 * 1204 + MAX_CHUNK = 16 * 1024 command_string = pack(' [u:%i, fid:%i, size:%i v:%i t:%s...]" % (self.uid, self.fid, self.size, self.valid, self.mark) diff --git a/zk/user.py b/zk/user.py index 185559b..9c00348 100644 --- a/zk/user.py +++ b/zk/user.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from struct import pack #, unpack class User(object): def __init__(self, uid, name, privilege, password='', group_id='', user_id='', card=0): @@ -8,7 +9,9 @@ class User(object): self.password = password self.group_id = group_id self.user_id = user_id - self.card = card + self.card = card # 64 int to 40 bit int + def repack29(self): # with 02 for zk6 (size 29) + return pack(": {}'.format(self.name) From 1e06eefa3f5f53f73ae769ccf2a907e1de7d3fb7 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Wed, 11 Apr 2018 19:38:37 -0400 Subject: [PATCH 13/83] fix menores --- zk/base.py | 384 ++++++++++++++++++++--------------------------------- 1 file changed, 142 insertions(+), 242 deletions(-) diff --git a/zk/base.py b/zk/base.py index 9839930..7828e82 100644 --- a/zk/base.py +++ b/zk/base.py @@ -46,14 +46,9 @@ def make_commkey(key, session_id, ticks=50): return k class ZK(object): - - is_connect = False - - __data_recv = None - __sesion_id = 0 - __reply_id = 0 - + """ Clase ZK """ def __init__(self, ip, port=4370, timeout=60, password=0, firmware=8): + """ initialize instance """ self.is_connect = False self.__address = (ip, port) self.__sock = socket(AF_INET, SOCK_DGRAM) @@ -71,11 +66,17 @@ class ZK(object): self.fingers_av = 0 self.users_av = 0 self.rec_av = 0 - def __create_header(self, command, command_string, checksum, session_id, reply_id): + self.__session_id = 0 + self.__reply_id = const.USHRT_MAX-1 + self.__data_recv = None + def __create_header(self, command, command_string, session_id, reply_id): ''' Puts a the parts that make up a packet together and packs them into a byte string + + MODIFIED now, without initial checksum ''' - buf = pack('HHHH', command, checksum, session_id, reply_id) + command_string + #checksum = 0 always? for calculating + buf = pack('HHHH', command, 0, session_id, reply_id) + command_string buf = unpack('8B' + '%sB' % len(command_string), buf) checksum = unpack('H', self.__create_checksum(buf))[0] reply_id += 1 @@ -111,11 +112,12 @@ class ZK(object): return pack('H', checksum) - def __send_command(self, command, command_string, checksum, session_id, reply_id, response_size): + def __send_command(self, command, command_string='', response_size=8): ''' send command to the terminal ''' - buf = self.__create_header(command, command_string, checksum, session_id, reply_id) + + buf = self.__create_header(command, command_string, self.__session_id, self.__reply_id) try: self.__sock.sendto(buf, self.__address) self.__data_recv = self.__sock.recv(response_size) @@ -125,16 +127,23 @@ class ZK(object): self.__response = unpack('HHHH', self.__data_recv[:8])[0] self.__reply_id = unpack('HHHH', self.__data_recv[:8])[3] - if self.__response in [const.CMD_ACK_OK, const.CMD_PREPARE_DATA]: + if self.__response in [const.CMD_ACK_OK, const.CMD_PREPARE_DATA, const.CMD_DATA]: return { 'status': True, 'code': self.__response } - else: - return { - 'status': False, - 'code': self.__response - } + return { + 'status': False, + 'code': self.__response + } + def __ack_ok(self): + """ ack ok """ + buf = self.__create_header(const.CMD_ACK_OK, "", self.__session_id, const.USHRT_MAX - 1) + try: + self.__sock.sendto(buf, self.__address) + except Exception, e: + raise ZKNetworkError(str(e)) + def __get_data_size(self): """Checks a returned packet to see if it returned CMD_PREPARE_DATA, @@ -197,96 +206,63 @@ class ZK(object): ''' connect to the device ''' - command = const.CMD_CONNECT - command_string = '' - checksum = 0 - session_id = 0 - reply_id = const.USHRT_MAX - 1 - response_size = 8 - - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) - self.__sesion_id = unpack('HHHH', self.__data_recv[:8])[2] + self.__session_id = 0 + self.__reply_id = const.USHRT_MAX - 1 + cmd_response = self.__send_command(const.CMD_CONNECT) + self.__session_id = unpack('HHHH', self.__data_recv[:8])[2] if cmd_response.get('code')==const.CMD_ACK_UNAUTH: #print "try auth" - command_string = make_commkey(self.__password,self.__sesion_id) - cmd_response = self.__send_command(const.CMD_AUTH, command_string , checksum, self.__sesion_id, self.__reply_id, response_size) + command_string = make_commkey(self.__password, self.__session_id) + cmd_response = self.__send_command(const.CMD_AUTH, command_string) if cmd_response.get('status'): self.is_connect = True # set the session iduid, privilege, password, name, card, group_id, timezone, user_id = unpack('HB5s8s5sBhI',userdata.ljust(28)[:28]) return self else: print "connect err {} ".format(cmd_response["code"]) - raise ZKErrorResponse("Invalid response") + raise ZKErrorResponse("Invalid response: Can't connect") def disconnect(self): ''' diconnect from the connected device ''' - command = const.CMD_EXIT - command_string = '' - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id - response_size = 8 - - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + cmd_response = self.__send_command(const.CMD_EXIT) if cmd_response.get('status'): self.is_connect = False return True else: - raise ZKErrorResponse("Invalid response") + raise ZKErrorResponse("can't disconnect") def disable_device(self): ''' disable (lock) device, ensure no activity when process run ''' - command = const.CMD_DISABLEDEVICE - command_string = '' - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id - response_size = 8 - - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + cmd_response = self.__send_command(const.CMD_DISABLEDEVICE) if cmd_response.get('status'): return True else: - raise ZKErrorResponse("Invalid response") + raise ZKErrorResponse("Can't Disable") def enable_device(self): ''' re-enable the connected device ''' - command = const.CMD_ENABLEDEVICE - command_string = '' - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id - response_size = 8 - - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + cmd_response = self.__send_command(const.CMD_ENABLEDEVICE) if cmd_response.get('status'): return True else: - raise ZKErrorResponse("Invalid response") + raise ZKErrorResponse("Can't enable") def get_firmware_version(self): ''' return the firmware version ''' - command = const.CMD_GET_VERSION - command_string = '' - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id - response_size = 1024 - - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + cmd_response = self.__send_command(const.CMD_GET_VERSION,'', 1024) if cmd_response.get('status'): firmware_version = self.__data_recv[8:].split('\x00')[0] return firmware_version else: - raise ZKErrorResponse("Invalid response") + raise ZKErrorResponse("Can't read frimware version") def get_serialnumber(self): ''' @@ -294,17 +270,13 @@ class ZK(object): ''' command = const.CMD_OPTIONS_RRQ command_string = '~SerialNumber' - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id response_size = 1024 - - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): serialnumber = self.__data_recv[8:].split('=')[-1].split('\x00')[0] return serialnumber else: - raise ZKErrorResponse("Invalid response") + raise ZKErrorResponse("can't read serial number") def get_platform(self): ''' @@ -312,17 +284,14 @@ class ZK(object): ''' command = const.CMD_OPTIONS_RRQ command_string = '~Platform' - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id response_size = 1024 - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): platform = self.__data_recv[8:].split('=')[-1].split('\x00')[0] return platform else: - raise ZKErrorResponse("Invalid response") + raise ZKErrorResponse("can't get platform") def get_device_name(self): ''' @@ -330,17 +299,14 @@ class ZK(object): ''' command = const.CMD_OPTIONS_RRQ command_string = '~DeviceName' - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id response_size = 1024 - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): device = self.__data_recv[8:].split('=')[-1].split('\x00')[0] return device else: - raise ZKErrorResponse("Invalid response") + raise ZKErrorResponse("can't read device name") def get_pin_width(self): ''' @@ -348,43 +314,30 @@ class ZK(object): ''' command = const.CMD_GET_PINWIDTH command_string = ' P' - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id - response_size = 1024 + response_size = 9 - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): width = self.__data_recv[8:].split('\x00')[0] return bytearray(width)[0] else: - raise ZKErrorResponse("Invalid response") + raise ZKErrorResponse("can0t get pin width") def free_data(self): """ clear buffer""" command = const.CMD_FREE_DATA - command_string = '' - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id - response_size = 1024 - - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + cmd_response = self.__send_command(command) if cmd_response.get('status'): return True else: - raise ZKErrorResponse("Invalid response") + raise ZKErrorResponse("can't free data") def read_sizes(self): """ read sizes """ command = const.CMD_GET_FREE_SIZES - command_string = '' - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id response_size = 1024 - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + cmd_response = self.__send_command(command,'', response_size) if cmd_response.get('status'): fields = unpack('iiiiiiiiiiiiiiiiiiii', self.__data_recv[8:]) self.users = fields[4] @@ -402,7 +355,7 @@ class ZK(object): return True else: - raise ZKErrorResponse("Invalid response") + raise ZKErrorResponse("can't read sizes") def __str__(self): """ for debug""" @@ -417,46 +370,31 @@ class ZK(object): restart the device ''' command = const.CMD_RESTART - command_string = '' - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id - response_size = 8 - - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + cmd_response = self.__send_command(command) if cmd_response.get('status'): return True else: - raise ZKErrorResponse("Invalid response") + raise ZKErrorResponse("can't restart device") def get_time(self): """obtener la hora del equipo""" command = const.CMD_GET_TIME - command_string = '' - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id response_size = 1032 - - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + cmd_response = self.__send_command(command, '', response_size) if cmd_response.get('status'): return self.__decode_time(self.__data_recv[8:12]) else: - raise ZKErrorResponse("Invalid response") + raise ZKErrorResponse("can't get time") def set_time(self, timestamp): """ colocar la hora del sistema al zk """ command = const.CMD_SET_TIME command_string = pack(b'I', self.__encode_time(timestamp)) - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id - response_size = 8 - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + cmd_response = self.__send_command(command, command_string) if cmd_response.get('status'): return True else: - raise ZKErrorResponse("Invalid response") + raise ZKErrorResponse("can't set time") def poweroff(self): ''' @@ -464,50 +402,34 @@ class ZK(object): ''' command = const.CMD_POWEROFF command_string = '' - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id response_size = 1032 - - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): return True else: - raise ZKErrorResponse("Invalid response") + raise ZKErrorResponse("can't poweroff") def refresh_data(self): ''' shutdown the device ''' command = const.CMD_REFRESHDATA - command_string = '' - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id - response_size = 8 - - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + cmd_response = self.__send_command(command) if cmd_response.get('status'): return True else: - raise ZKErrorResponse("Invalid response") + raise ZKErrorResponse("can't refresh data") def test_voice(self): ''' play test voice ''' - command = const.CMD_TESTVOICEuid, privilege, password, name, card, group_id, timezone, user_id = unpack('HB5s8s5sBhI',userdata.ljust(28)[:28]) - command_string = '' - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id - response_size = 8 - - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + command = const.CMD_TESTVOICE + cmd_response = self.__send_command(command) if cmd_response.get('status'): return True else: - raise ZKErrorResponse("Invalid response") + raise ZKErrorResponse("can't test voice") def set_user(self, uid, name, privilege=0, password='', group_id='', user_id='', card=0): ''' @@ -537,12 +459,8 @@ class ZK(object): raise ZKErrorResponse("Cant pack user") else: command_string = pack('Hc8s28sc7sx24s', uid, privilege, password, name, chr(0), group_id, user_id) - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id response_size = 1024 - - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): return True else: @@ -568,11 +486,7 @@ class ZK(object): self._send_with_buffer(packet) command = 110 # Unknown command_string = pack('> 8) #command_string = pack('2s', uid) command_string = pack('h', uid) - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id - response_size = 1024 - - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + cmd_response = self.__send_command(command, command_string) if cmd_response.get('status'): return True else: - raise ZKErrorResponse("Invalid response") + raise ZKErrorResponse("can't delete user") def get_user_template(self, uid, temp_id): command = 88 # comando secreto!!! command_string = pack('hb', uid, temp_id) - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id response_size = 1024 - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + cmd_response = self.__send_command(command, command_string, response_size) data = [] if not cmd_response.get('status'): - raise ZKErrorResponse("Invalid response") + raise ZKErrorResponse("can't get user template") #else if cmd_response.get('code') == const.CMD_PREPARE_DATA: bytes = self.__get_data_size() #TODO: check with size @@ -760,12 +652,9 @@ class ZK(object): ''' command = const.CMD_USERTEMP_RRQ command_string = chr(const.FCT_USER) - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id response_size = 1024 - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + cmd_response = self.__send_command(command, command_string, response_size) users = [] pac = 0 if cmd_response.get('status'): @@ -826,7 +715,7 @@ class ZK(object): userdata = userdata[72:] self.free_data() else: - raise ZKErrorResponse("Invalid response") + raise ZKErrorResponse("can't _get user") return users def cancel_capture(self): @@ -834,12 +723,7 @@ class ZK(object): cancel capturing finger ''' command = const.CMD_CANCELCAPTURE - command_string = '' - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id - response_size = 8 - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + cmd_response = self.__send_command(command) return bool(cmd_response.get('status')) def verify_user(self): @@ -848,18 +732,21 @@ class ZK(object): ''' command = const.CMD_STARTVERIFY # uid = chr(uid % 256) + chr(uid >> 8) - command_string = '' - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id - response_size = 8 - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + cmd_response = self.__send_command(command) print 'verify', cmd_response if cmd_response.get('status'): return True else: raise ZKErrorResponse("Cant Verify") + def reg_event(self, flags): + """ reg events, """ + command = const.CMD_REG_EVENT + command_string = pack ("I", flags) + cmd_response = self.__send_command(command, command_string) + if not cmd_response.get('status'): + raise ZKErrorResponse("cant' reg events %i" % flags) + def enroll_user(self, uid, temp_id=0): ''' @@ -867,51 +754,76 @@ class ZK(object): ''' command = const.CMD_STARTENROLL command_string = pack('hhb', uid, 0, temp_id) # el 0 es misterio - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id - response_size = 8 - - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + cmd_response = self.__send_command(command, command_string) if not cmd_response.get('status'): raise ZKErrorResponse("Cant Enroll user #%i [%i]" %(uid, temp_id)) print "enroll", cmd_response #retorna rapido toca esperar un reg event + attempts = 3 + while attempts: + print "A:%i esperando primer regevent" % attempts + data_recv = self.__sock.recv(1032) # timeout? tarda bastante... + self.__ack_ok() + print (data_recv).encode('hex') + if len(data_recv) > 8: #not empty + res = unpack("H", data_recv.ljust(16,"\x00")[8:10])[0] + print "res", res + if res == 6: + print ("posible timeout") + return False + print "A:%i esperando 2do regevent" % attempts + data_recv = self.__sock.recv(1032) # timeout? tarda bastante... + self.__ack_ok() + print (data_recv).encode('hex') + if len(data_recv) > 8: #not empty + res = unpack("H", data_recv.ljust(16,"\x00")[8:10])[0] + print "res", res + if res == 6: + print ("posible timeout") + return False + elif res == 0x64: + print ("ok, continue?") + attempts -= 1 + + print "esperando 3er regevent" data_recv = self.__sock.recv(1032) # timeout? tarda bastante... - print data_recv + self.__ack_ok() + print (data_recv).encode('hex') + res = unpack("H", data_recv.ljust(16,"\x00")[8:10])[0] + if res == 4: + print "registro Fallido" + self.cancel_capture() + return False + if res == 0: + size = unpack("H", data_recv.ljust(16,"\x00")[10:12])[0] + pos = unpack("H", data_recv.ljust(16,"\x00")[12:14])[0] + print "enroll ok", size, pos + return True def clear_data(self): ''' clear all data (include: user, attendance report, finger database ) ''' command = const.CMD_CLEAR_DATA - command_string = '' - checksum = 0 - session_id = self.__sesion_id - reply_id = self.__reply_id - response_size = 1024 - - cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + cmd_response = self.__send_command(command) if cmd_response.get('status'): return True else: - raise ZKErrorResponse("Invalid response") + raise ZKErrorResponse("can't clear data") def __read_chunk(self, start, size): """ read a chunk from buffer """ command = 1504 #CMD_READ_BUFFER command_string = pack(' Date: Thu, 12 Apr 2018 19:10:01 -0400 Subject: [PATCH 14/83] agregado pruebas de voz --- test_voice.py | 26 ++++++++++++++++ zk/base.py | 85 ++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 test_voice.py diff --git a/test_voice.py b/test_voice.py new file mode 100644 index 0000000..fd86fc9 --- /dev/null +++ b/test_voice.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +import sys +from time import sleep +from zk import ZK, const + +sys.path.append("zk") + +conn = None +zk = ZK('192.168.1.201', port=4370, timeout=5) +try: + print 'Connecting to device ...' + conn = zk.connect() + print 'Disabling device ...' + conn.disable_device() + print 'Firmware Version: : {}'.format(conn.get_firmware_version()) + for i in range(0,65): + print "test_voice, %i" % i + zk.test_voice(i) + sleep(3) + print 'Enabling device ...' + conn.enable_device() +except Exception, e: + print "Process terminate : {}".format(e) +finally: + if conn: + conn.disconnect() diff --git a/zk/base.py b/zk/base.py index 7828e82..21da492 100644 --- a/zk/base.py +++ b/zk/base.py @@ -420,12 +420,80 @@ class ZK(object): else: raise ZKErrorResponse("can't refresh data") - def test_voice(self): + def test_voice(self, index=0): ''' play test voice + 0 acceso correcto + 1 password incorrecto + 2 la memoria del terminal estĆ” llena + 3 usuario invalido + 4 intente de nuevo por favor * + 5 reintroduszca codigo de usuario + 6 memoria del terminal llena + 7 memoria de alm fich llena + 8 huella duplicada + 9 acceso denegado + 10 *beep* + 11 el sistema vuelve al modo de verificacion + 12 por favor coloque su dedo o acerque tarjeta + 13 acerca su tarjeta de nuevo + 14 excedido tiempo p esta operacion + 15 coloque su dedo de nuevo + 16 coloque su dedo por ultima vez + 17 ATN numero de tarjeta estĆ” repetida + 18 proceso de registro correcto * + 19 borrado correcto + 20 Numero de usuario + 21 ATN se ha llegado al max num usuarios + 22 verificacion de usuarios + 23 usuario no registrado + 24 ATN se ha llegado al num max de registros + 25 ATN la puerta no esta cerrada + 26 registro de usuarios + 27 borrado de usuarios + 28 coloque su dedo + 29 registre la tarjeta de administrador + 30 0 + 31 1 + 32 2 + 33 3 + 34 4 + 35 5 + 36 6 + 37 7 + 38 8 + 39 9 + 40 PFV seleccione numero de usuario + 41 registrar + 42 operacion correcta + 43 PFV acerque su tarjeta + 43 la tarjeta ha sido registrada + 45 error en operacion + 46 PFV acerque tarjeta de administracion, p confirmacion + 47 descarga de fichajes + 48 descarga de usuarios + 49 carga de usuarios + 50 actualizan de firmware + 51 ejeuctar ficheros de configuracion + 52 confirmaciĆ³n de clave de acceso correcta + 53 error en operacion de tclado + 54 borrar todos los usuarios + 55 restaurar terminal con configuracion por defecto + 56 introduzca numero de usuario + 57 teclado bloqueado + 58 error en la gestiĆ³n de la tarjeta + 59 establezca una clave de acceso + 60 pulse el teclado + 61 zona de accceso invalida + 62 acceso combinado invÄŗlido + 63 verificaciĆ³n multiusuario + 64 modo de verificaciĆ³n invĆ”lido + 65 - + ''' command = const.CMD_TESTVOICE - cmd_response = self.__send_command(command) + command_string = pack("I", index) + cmd_response = self.__send_command(command, command_string) if cmd_response.get('status'): return True else: @@ -552,7 +620,7 @@ class ZK(object): cmd_response = self.__send_command(command, command_string, response_size) data = [] if not cmd_response.get('status'): - raise ZKErrorResponse("can't get user template") + return None #("can't get user template") #else if cmd_response.get('code') == const.CMD_PREPARE_DATA: bytes = self.__get_data_size() #TODO: check with size @@ -728,12 +796,11 @@ class ZK(object): def verify_user(self): ''' - verify finger + start verify finger mode (after capture) ''' command = const.CMD_STARTVERIFY # uid = chr(uid % 256) + chr(uid >> 8) cmd_response = self.__send_command(command) - print 'verify', cmd_response if cmd_response.get('status'): return True else: @@ -877,11 +944,13 @@ class ZK(object): if self.__firmware == 6: while len(attendance_data) >= 8: uid, status, timestamp = unpack('HH4s', attendance_data.ljust(8)[:8]) + attendance_data = attendance_data[8:] + if uid == 0: #(on zk it's 16 bytes, extrauid 0, status 255, timestamp 0x0) + continue # probably user_id = str(uid) #TODO revisar posibles valores cruzar con userdata timestamp = self.__decode_time(timestamp) attendance = Attendance(uid, user_id, timestamp, status) attendances.append(attendance) - attendance_data = attendance_data[8:] else: while len(attendance_data) >= 40: uid, user_id, sparator, timestamp, status, space = unpack('H24sc4sc8s', attendance_data.ljust(40)[:40]) @@ -929,11 +998,13 @@ class ZK(object): if self.__firmware == 6: while len(attendance_data) >= 8: uid, status, timestamp = unpack('HH4s', attendance_data.ljust(8)[:8]) + attendance_data = attendance_data[8:] + if uid == 0: #(on zk it's 16 bytes, extrauid 0, status 255, timestamp 0x0) + continue # probably user_id = str(uid) #TODO revisar posibles valores cruzar con userdata timestamp = self.__decode_time(timestamp) attendance = Attendance(uid, user_id, timestamp, status) attendances.append(attendance) - attendance_data = attendance_data[8:] else: while len(attendance_data) >= 40: uid, user_id, sparator, timestamp, status, space = unpack('H24sc4sc8s', attendance_data.ljust(40)[:40]) From dc142f38efad69c82f1c39f7232434ad14224abb Mon Sep 17 00:00:00 2001 From: Ajay Ramaswamy Date: Wed, 18 Apr 2018 05:45:59 +0530 Subject: [PATCH 15/83] silence an informational message --- zk/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zk/base.py b/zk/base.py index 21da492..d054910 100644 --- a/zk/base.py +++ b/zk/base.py @@ -676,7 +676,7 @@ class ZK(object): """ return all user """ users = [] userdata, size = self.read_with_buffer(const.CMD_USERTEMP_RRQ, const.FCT_USER) - print "user size %i" % size + #print "user size %i" % size if size < 4: print "WRN: no user data" # debug return [] From 4a5e298ee50ee44bc463db213e1bbc3ffc24b8b5 Mon Sep 17 00:00:00 2001 From: Ajay Ramaswamy Date: Wed, 18 Apr 2018 06:01:18 +0530 Subject: [PATCH 16/83] Add support for reading RFID cards From my tests I see that the name is 24 characters and the RFID card is encoded in the next 4 positions as an Integer --- zk/base.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/zk/base.py b/zk/base.py index d054910..113b497 100644 --- a/zk/base.py +++ b/zk/base.py @@ -698,7 +698,7 @@ class ZK(object): userdata = userdata[28:] else: while len(userdata) >= 72: - uid, privilege, password, name, sparator, group_id, user_id = unpack('Hc8s28sc7sx24s', userdata.ljust(72)[:72]) + uid, privilege, password, name, separator, group_id, user_id = unpack('Hc8s24s4sx7sx24s', userdata.ljust(72)[:72]) #u1 = int(uid[0].encode("hex"), 16) #u2 = int(uid[1].encode("hex"), 16) #uid = u1 + (u2 * 256) @@ -707,9 +707,10 @@ class ZK(object): name = unicode(name.split('\x00')[0], errors='ignore').strip() group_id = unicode(group_id.split('\x00')[0], errors='ignore').strip() user_id = unicode(user_id.split('\x00')[0], errors='ignore') + card = int(unpack('I', separator)[0]) if not name: name = "NN-%s" % user_id - user = User(uid, name, privilege, password, group_id, user_id) + user = User(uid, name, privilege, password, group_id, user_id, card) users.append(user) userdata = userdata[72:] return users From e73e20947fbb1661ecbab2302abf42a1ac916bc4 Mon Sep 17 00:00:00 2001 From: Ajay Ramaswamy Date: Wed, 18 Apr 2018 06:03:46 +0530 Subject: [PATCH 17/83] update test to show card detail --- test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test.py b/test.py index 37bec2e..cbac697 100644 --- a/test.py +++ b/test.py @@ -26,6 +26,7 @@ try: print ' Password : {}'.format(user.password) print ' Group ID : {}'.format(user.group_id) print ' User ID : {}'.format(user.user_id) + print ' Card : {}'.format(user.card) print "Voice Test ..." conn.test_voice() From bea82416f15b4a9f14eb3550c1ab8f309a6c0289 Mon Sep 17 00:00:00 2001 From: Ajay Ramaswamy Date: Wed, 18 Apr 2018 10:00:31 +0530 Subject: [PATCH 18/83] fix for reading card info when reading all users --- zk/base.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/zk/base.py b/zk/base.py index 113b497..51838b3 100644 --- a/zk/base.py +++ b/zk/base.py @@ -767,7 +767,7 @@ class ZK(object): userdata = userdata[28:] else: while len(userdata) >= 72: - uid, privilege, password, name, sparator, group_id, user_id = unpack('Hc8s28sc7sx24s', userdata.ljust(72)[:72]) + uid, privilege, password, name, separator, group_id, user_id = unpack('Hc8s24s4sx7sx24s', userdata.ljust(72)[:72]) #u1 = int(uid[0].encode("hex"), 16) #u2 = int(uid[1].encode("hex"), 16) #uid = u1 + (u2 * 256) @@ -776,9 +776,10 @@ class ZK(object): name = unicode(name.split('\x00')[0], errors='ignore').strip() group_id = unicode(group_id.split('\x00')[0], errors='ignore').strip() user_id = unicode(user_id.split('\x00')[0], errors='ignore') + card = int(unpack('I', separator)[0]) if not name: name = "NN-%s" % user_id - user = User(uid, name, privilege, password, group_id, user_id) + user = User(uid, name, privilege, password, group_id, user_id, card) users.append(user) userdata = userdata[72:] From 362c5b96007e6d7e88f546d48e7a82a5a5b3536d Mon Sep 17 00:00:00 2001 From: Ajay Ramaswamy Date: Thu, 19 Apr 2018 05:22:38 +0530 Subject: [PATCH 19/83] Add support for writing RFID cards --- zk/base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zk/base.py b/zk/base.py index 51838b3..ca5d69b 100644 --- a/zk/base.py +++ b/zk/base.py @@ -526,7 +526,9 @@ class ZK(object): print "Error pack: %s" % sys.exc_info()[0] raise ZKErrorResponse("Cant pack user") else: - command_string = pack('Hc8s28sc7sx24s', uid, privilege, password, name, chr(0), group_id, user_id) + name_pad = name.ljust(24, '\x00')[:24] + card_str = pack('i', int(card))[:4] + command_string = pack('Hc8s24s4sc7sx24s', uid, privilege, password, name_pad, card_str, chr(0), group_id, user_id) response_size = 1024 cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): From a4405075f284f70615be2c2672f394b566e85c39 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Wed, 18 Apr 2018 20:22:03 -0400 Subject: [PATCH 20/83] minor fixes --- zk/base.py | 11 +++++++---- zk/user.py | 6 +++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/zk/base.py b/zk/base.py index 113b497..9ab7a01 100644 --- a/zk/base.py +++ b/zk/base.py @@ -533,8 +533,10 @@ class ZK(object): return True else: raise ZKErrorResponse("Cant set user") + def save_user_template(self, user, fingers=[]): """ save user and template """ + #TODO: grabado global # armar paquete de huellas if isinstance(fingers, Finger): fingers =[Finger] @@ -544,9 +546,8 @@ class ZK(object): tstart = 0 for finger in fingers: tfp = finger.repack_only() - table += pack(": {}'.format(self.name) + return ': [uid:{}, name:{} user_id:{}]'.format(self.uid, self.name, self.user_id) def __repr__(self): return ': {}'.format(self.name) From 5a1ff7691b40b1b4aa3a59a5332219271b1d936d Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Thu, 19 Apr 2018 20:23:23 -0400 Subject: [PATCH 21/83] minor fix, added universal tester --- test_machine.py | 73 +++++++++++++++++++++++++++++++++++++++++++++++++ zk/base.py | 29 +++++++++++++++++--- 2 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 test_machine.py diff --git a/test_machine.py b/test_machine.py new file mode 100644 index 0000000..713b0cb --- /dev/null +++ b/test_machine.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +import sys +import argparse + +from zk import ZK, const + +sys.path.append("zk") + +conn = None + + +parser = argparse.ArgumentParser(description='ZK Basic Reading Tests') +parser.add_argument('-a', '--address', + help='ZK device Addres [192.168.1.201]', default='192.168.1.201') +parser.add_argument('-p', '--port', type=int, + help='device port', default=4370) +parser.add_argument('-T', '--timeout', type=int, + help='timeout', default=60) +parser.add_argument('-P', '--password', type=int, + help='Device code/password', default=0) +parser.add_argument('-f', '--firmware', type=int, + help='test firmware', default=8) +parser.add_argument('-t', '--templates', action="store_true", + help='get templates') +parser.add_argument('-r', '--records', action="store_true", + help='get records') + +args = parser.parse_args() + +zk = ZK(args.address, port=args.port, timeout=args.timeout, password=args.password, firmware=args.firmware) +try: + print 'Connecting to device ...' + conn = zk.connect() + print 'Disabling device ...' + conn.disable_device() + conn.read_sizes() + print conn + print 'Firmware Version: : {}'.format(conn.get_firmware_version()) + print 'Platform: %s' % conn.get_platform() + print 'DeviceName: %s' % conn.get_device_name() + print 'Pin Width: %i' % conn.get_pin_width() + print 'Serial Number: %s' % conn.get_serialnumber() + print 'MAC: %s' % conn.get_mac() + print '' + print '--- Get User ---' + users = conn.get_users() + for user in users: + privilege = 'User' + if user.privilege == const.USER_ADMIN: + privilege = 'Admin' + + print '-> UID #{:<8} Name : {:<8} Privilege : {}'.format(user.uid, user.name, privilege) + print ' Group ID : {:<8} User ID : {:<8} Password : {:<8} Card : {}'.format(user.group_id, user.user_id, user.password, user.card) + print '' + print "Voice Test ..." + conn.test_voice(10) + if args.templates: + print "Read Templates..." + templates = conn.get_templates() + for tem in templates: + print tem + if args.records: + print "Read Records..." + attendance = conn.get_attendance() + for att in attendance: + print "ATT: uid:{:>3}, t: {}".format(att.uid, att.timestamp) + print 'Enabling device ...' + conn.enable_device() +except Exception, e: + print "Process terminate : {}".format(e) +finally: + if conn: + conn.disconnect() diff --git a/zk/base.py b/zk/base.py index c9bc189..365fc44 100644 --- a/zk/base.py +++ b/zk/base.py @@ -293,6 +293,21 @@ class ZK(object): else: raise ZKErrorResponse("can't get platform") + def get_mac(self): + ''' + return the serial number + ''' + command = const.CMD_OPTIONS_RRQ + command_string = 'MAC' + response_size = 1024 + + cmd_response = self.__send_command(command, command_string, response_size) + if cmd_response.get('status'): + mac = self.__data_recv[8:].split('=')[-1].split('\x00')[0] + return mac + else: + raise ZKErrorResponse("can't get mac") + def get_device_name(self): ''' return the serial number @@ -660,7 +675,7 @@ class ZK(object): size, uid, fid, valid = unpack('HHbb',templatedata[:6]) template = unpack("%is" % (size-6), templatedata[6:size])[0] finger = Finger(size, uid, fid, valid, template) - print finger # test + #print finger # test templates.append(finger) templatedata = templatedata[size:] total_size -= size @@ -818,8 +833,14 @@ class ZK(object): cmd_response = self.__send_command(command, command_string) if not cmd_response.get('status'): raise ZKErrorResponse("cant' reg events %i" % flags) - - + + def set_sdk_build_1(self): + """ """ + command = const.CMD_OPTIONS_WRQ + command_string = "SDKBuild=1" + cmd_response = self.__send_command(command, command_string) + if not cmd_response.get('status'): + raise ZKErrorResponse("can't set sdk build ") def enroll_user(self, uid, temp_id=0): ''' start enroll user @@ -1033,7 +1054,7 @@ class ZK(object): clear all attendance record ''' command = const.CMD_CLEAR_ATTLOG - cmd_response = self.__send_command(command, command_string) + cmd_response = self.__send_command(command) if cmd_response.get('status'): return True else: From 74cdc267eb2164fe5fecf55850c6fb35c8a38f61 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Thu, 19 Apr 2018 20:29:00 -0400 Subject: [PATCH 22/83] fix format --- test_machine.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test_machine.py b/test_machine.py index 713b0cb..6cf4801 100644 --- a/test_machine.py +++ b/test_machine.py @@ -49,9 +49,9 @@ try: if user.privilege == const.USER_ADMIN: privilege = 'Admin' - print '-> UID #{:<8} Name : {:<8} Privilege : {}'.format(user.uid, user.name, privilege) - print ' Group ID : {:<8} User ID : {:<8} Password : {:<8} Card : {}'.format(user.group_id, user.user_id, user.password, user.card) - print '' + print '-> UID #{:<5} Name : {:<27} Privilege : {}'.format(user.uid, user.name, privilege) + print ' Group ID : {:<8} User ID : {:<8} Password : {:<8} Card : {}'.format(user.group_id, user.user_id, user.password, user.card) + #print '' print "Voice Test ..." conn.test_voice(10) if args.templates: From 7304c41a37dda32de70624856ee58b84bda32152 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Fri, 20 Apr 2018 19:16:31 -0400 Subject: [PATCH 23/83] pruebas con ZK8 (lectura user,finger,reg ok) --- test_machine.py | 31 ++++-- test_voice.py | 18 +++- zk/base.py | 259 ++++++++++++++++++++---------------------------- zk/user.py | 5 +- 4 files changed, 150 insertions(+), 163 deletions(-) diff --git a/test_machine.py b/test_machine.py index 6cf4801..2101d0d 100644 --- a/test_machine.py +++ b/test_machine.py @@ -18,8 +18,8 @@ parser.add_argument('-T', '--timeout', type=int, help='timeout', default=60) parser.add_argument('-P', '--password', type=int, help='Device code/password', default=0) -parser.add_argument('-f', '--firmware', type=int, - help='test firmware', default=8) +#parser.add_argument('-f', '--firmware', type=int, +# help='test firmware', default=8) parser.add_argument('-t', '--templates', action="store_true", help='get templates') parser.add_argument('-r', '--records', action="store_true", @@ -27,20 +27,31 @@ parser.add_argument('-r', '--records', action="store_true", args = parser.parse_args() -zk = ZK(args.address, port=args.port, timeout=args.timeout, password=args.password, firmware=args.firmware) +zk = ZK(args.address, port=args.port, timeout=args.timeout, password=args.password) # , firmware=args.firmware try: print 'Connecting to device ...' conn = zk.connect() print 'Disabling device ...' conn.disable_device() + fmt = conn.get_extend_fmt() + print 'ExtendFmt : {}'.format(fmt) + if fmt == 1: + print "Firmware 6" + conn.firmware = 6 + else: + print "Firmware 8" + conn.firmware = 8 + + print 'Time : {}'.format(conn.get_time()) + print 'Firmware Version : {}'.format(conn.get_firmware_version()) + print 'Platform : %s' % conn.get_platform() + print 'DeviceName : %s' % conn.get_device_name() + print 'Pin Width : %i' % conn.get_pin_width() + print 'Serial Number : %s' % conn.get_serialnumber() + print 'MAC: %s' % conn.get_mac() + print '' conn.read_sizes() print conn - print 'Firmware Version: : {}'.format(conn.get_firmware_version()) - print 'Platform: %s' % conn.get_platform() - print 'DeviceName: %s' % conn.get_device_name() - print 'Pin Width: %i' % conn.get_pin_width() - print 'Serial Number: %s' % conn.get_serialnumber() - print 'MAC: %s' % conn.get_mac() print '' print '--- Get User ---' users = conn.get_users() @@ -51,6 +62,7 @@ try: print '-> UID #{:<5} Name : {:<27} Privilege : {}'.format(user.uid, user.name, privilege) print ' Group ID : {:<8} User ID : {:<8} Password : {:<8} Card : {}'.format(user.group_id, user.user_id, user.password, user.card) + #print len (user.repack73()), user.repack73().encode('hex') #print '' print "Voice Test ..." conn.test_voice(10) @@ -68,6 +80,7 @@ try: conn.enable_device() except Exception, e: print "Process terminate : {}".format(e) + print "Error: %s" % sys.exc_info()[0] finally: if conn: conn.disconnect() diff --git a/test_voice.py b/test_voice.py index fd86fc9..f7a6cc5 100644 --- a/test_voice.py +++ b/test_voice.py @@ -1,12 +1,28 @@ # -*- coding: utf-8 -*- import sys +import argparse + from time import sleep from zk import ZK, const sys.path.append("zk") +parser = argparse.ArgumentParser(description='ZK Basic Reading Tests') +parser.add_argument('-a', '--address', + help='ZK device Addres [192.168.1.201]', default='192.168.1.201') +parser.add_argument('-p', '--port', type=int, + help='device port [4370]', default=4370) +parser.add_argument('-T', '--timeout', type=int, + help='timeout [60]', default=60) +parser.add_argument('-P', '--password', type=int, + help='Device code/password', default=0) +parser.add_argument('-f', '--firmware', type=int, + help='test firmware', default=8) + +args = parser.parse_args() + conn = None -zk = ZK('192.168.1.201', port=4370, timeout=5) +zk = ZK(args.address, port=args.port, timeout=args.timeout, password=args.password, firmware=args.firmware) try: print 'Connecting to device ...' conn = zk.connect() diff --git a/zk/base.py b/zk/base.py index 365fc44..fbd5f51 100644 --- a/zk/base.py +++ b/zk/base.py @@ -54,7 +54,7 @@ class ZK(object): self.__sock = socket(AF_INET, SOCK_DGRAM) self.__sock.settimeout(timeout) self.__password = password # passint - self.__firmware = int(firmware) #TODO check minor version? + self.firmware = int(firmware) #TODO check minor version? self.users = 0 self.fingers = 0 self.records = 0 @@ -63,6 +63,8 @@ class ZK(object): self.fingers_cap = 0 self.users_cap = 0 self.rec_cap = 0 + self.faces = 0 + self.faces_cap = 0 self.fingers_av = 0 self.users_av = 0 self.rec_av = 0 @@ -323,6 +325,22 @@ class ZK(object): else: raise ZKErrorResponse("can't read device name") + def get_extend_fmt(self): + ''' + determine extend fmt + ''' + command = const.CMD_OPTIONS_RRQ + command_string = '~ExtendFmt' + response_size = 1024 + + cmd_response = self.__send_command(command, command_string, response_size) + if cmd_response.get('status'): + fmt = int(self.__data_recv[8:].split('=')[-1].split('\x00')[0]) + #definitivo? seleccionar firmware aqui? + return fmt + else: + raise ZKErrorResponse("can't read extend fmt") + def get_pin_width(self): ''' return the serial number @@ -354,7 +372,11 @@ class ZK(object): cmd_response = self.__send_command(command,'', response_size) if cmd_response.get('status'): - fields = unpack('iiiiiiiiiiiiiiiiiiii', self.__data_recv[8:]) + size = len(self.__data_recv[8:]) + if size == 80: + fields = unpack('iiiiiiiiiiiiiiiiiiii', self.__data_recv[8:]) + else: #92? + fields = unpack('iiiiiiiiiiiiiiiiiiiiiii', self.__data_recv[8:]) self.users = fields[4] self.fingers = fields[6] self.records = fields[8] @@ -366,6 +388,9 @@ class ZK(object): self.fingers_av = fields[17] self.users_av = fields[18] self.rec_av = fields[19] + if len(fields) > 20: + self.faces = fields[20] + self.faces_cap = fields[22] #TODO: get faces size... return True @@ -374,10 +399,10 @@ class ZK(object): def __str__(self): """ for debug""" - return "ZK%i adr:%s:%s users:%i/%i fingers:%i/%i, records:%i/%i" % ( - self.__firmware, self.__address[0], self.__address[1], + return "ZK%i adr:%s:%s users:%i/%i fingers:%i/%i, records:%i/%i faces:%i/%i" % ( + self.firmware, self.__address[0], self.__address[1], self.users, self.users_cap, self.fingers, self.fingers_cap, - self.records, self.rec_cap + self.records, self.rec_cap, self.faces, self.faces_cap ) def restart(self): @@ -439,71 +464,71 @@ class ZK(object): ''' play test voice 0 acceso correcto - 1 password incorrecto - 2 la memoria del terminal estĆ” llena - 3 usuario invalido - 4 intente de nuevo por favor * - 5 reintroduszca codigo de usuario - 6 memoria del terminal llena - 7 memoria de alm fich llena - 8 huella duplicada - 9 acceso denegado - 10 *beep* - 11 el sistema vuelve al modo de verificacion - 12 por favor coloque su dedo o acerque tarjeta - 13 acerca su tarjeta de nuevo - 14 excedido tiempo p esta operacion - 15 coloque su dedo de nuevo - 16 coloque su dedo por ultima vez - 17 ATN numero de tarjeta estĆ” repetida - 18 proceso de registro correcto * - 19 borrado correcto - 20 Numero de usuario - 21 ATN se ha llegado al max num usuarios - 22 verificacion de usuarios - 23 usuario no registrado - 24 ATN se ha llegado al num max de registros - 25 ATN la puerta no esta cerrada - 26 registro de usuarios - 27 borrado de usuarios - 28 coloque su dedo - 29 registre la tarjeta de administrador - 30 0 - 31 1 - 32 2 - 33 3 - 34 4 - 35 5 - 36 6 - 37 7 - 38 8 - 39 9 - 40 PFV seleccione numero de usuario - 41 registrar - 42 operacion correcta - 43 PFV acerque su tarjeta - 43 la tarjeta ha sido registrada - 45 error en operacion - 46 PFV acerque tarjeta de administracion, p confirmacion - 47 descarga de fichajes - 48 descarga de usuarios - 49 carga de usuarios - 50 actualizan de firmware - 51 ejeuctar ficheros de configuracion - 52 confirmaciĆ³n de clave de acceso correcta - 53 error en operacion de tclado - 54 borrar todos los usuarios - 55 restaurar terminal con configuracion por defecto - 56 introduzca numero de usuario - 57 teclado bloqueado - 58 error en la gestiĆ³n de la tarjeta - 59 establezca una clave de acceso - 60 pulse el teclado - 61 zona de accceso invalida - 62 acceso combinado invÄŗlido - 63 verificaciĆ³n multiusuario - 64 modo de verificaciĆ³n invĆ”lido - 65 - + 1 password incorrecto / clave incorrecta + 2 la memoria del terminal estĆ” llena / acceso denegado + 3 usuario invalido /codigo no valido + 4 intente de nuevo por favor / intente de nuevo por favor * + 5 reintroduszca codigo de usuario /reintroduszca codigo + 6 memoria del terminal llena /- + 7 memoria de alm fich llena /- + 8 huella duplicada / huella duplicada + 9 acceso denegado / ya ha sido registrado + 10 *beep* / beep + 11 el sistema vuelve al modo de verificacion / beep + 12 por favor coloque su dedo o acerque tarjeta /- + 13 acerca su tarjeta de nuevo /beep + 14 excedido tiempo p esta operacion /- + 15 coloque su dedo de nuevo /- + 16 coloque su dedo por ultima vez /- + 17 ATN numero de tarjeta estĆ” repetida /- + 18 proceso de registro correcto * /- + 19 borrado correcto /- + 20 Numero de usuario / ponga la caja de ojos + 21 ATN se ha llegado al max num usuarios /- + 22 verificacion de usuarios /- + 23 usuario no registrado /- + 24 ATN se ha llegado al num max de registros /- + 25 ATN la puerta no esta cerrada /- + 26 registro de usuarios /- + 27 borrado de usuarios /- + 28 coloque su dedo /- + 29 registre la tarjeta de administrador /- + 30 0 /- + 31 1 /- + 32 2 /- + 33 3 /- + 34 4 /- + 35 5 /- + 36 6 /- + 37 7 /- + 38 8 /- + 39 9 /- + 40 PFV seleccione numero de usuario /- + 41 registrar /- + 42 operacion correcta /- + 43 PFV acerque su tarjeta /- + 43 la tarjeta ha sido registrada /- + 45 error en operacion /- + 46 PFV acerque tarjeta de administracion, p confirmacion /- + 47 descarga de fichajes /- + 48 descarga de usuarios /- + 49 carga de usuarios /- + 50 actualizan de firmware /- + 51 ejeuctar ficheros de configuracion /- + 52 confirmaciĆ³n de clave de acceso correcta /- + 53 error en operacion de tclado /- + 54 borrar todos los usuarios /- + 55 restaurar terminal con configuracion por defecto /- + 56 introduzca numero de usuario /- + 57 teclado bloqueado /- + 58 error en la gestiĆ³n de la tarjeta /- + 59 establezca una clave de acceso /- + 60 pulse el teclado /- + 61 zona de accceso invalida /- + 62 acceso combinado invÄŗlido /- + 63 verificaciĆ³n multiusuario /- + 64 modo de verificaciĆ³n invĆ”lido /- + 65 - /- ''' command = const.CMD_TESTVOICE @@ -525,7 +550,7 @@ class ZK(object): if privilege not in [const.USER_DEFAULT, const.USER_ADMIN]: privilege = const.USER_DEFAULT privilege = chr(privilege) - if self.__firmware == 6: + if self.firmware == 6: print "uid : %i" % uid print "pri : %c" % privilege print "pass: %s" % str(password) @@ -566,7 +591,10 @@ class ZK(object): table += pack("= 28: uid, privilege, password, name, card, group_id, timezone, user_id = unpack('HB5s8s5sBhI',userdata.ljust(28)[:28]) password = unicode(password.split('\x00')[0], errors='ignore') @@ -712,7 +740,7 @@ class ZK(object): name = "NN-%s" % user_id user = User(uid, name, privilege, password, group_id, user_id, card) users.append(user) - print "[6]user:",uid, privilege, password, name, card, group_id, timezone, user_id + #print "[6]user:",uid, privilege, password, name, card, group_id, timezone, user_id userdata = userdata[28:] else: while len(userdata) >= 72: @@ -732,79 +760,6 @@ class ZK(object): users.append(user) userdata = userdata[72:] return users - - def _get_users(self): - ''' - return all user - ''' - command = const.CMD_USERTEMP_RRQ - command_string = chr(const.FCT_USER) - response_size = 1024 - - cmd_response = self.__send_command(command, command_string, response_size) - users = [] - pac = 0 - if cmd_response.get('status'): - if cmd_response.get('code') == const.CMD_PREPARE_DATA: - bytes = self.__get_data_size() - userdata = [] - while True: - data_recv = self.__sock.recv(1032) - response = unpack('HHHH', data_recv[:8])[0] - if response == const.CMD_DATA: - pac += 1 - userdata.append(data_recv[8:]) #header turncated - bytes -= 1024 - elif response == const.CMD_ACK_OK: - break #without problem. - else: - #truncado! continuar? - #print "broken! with %s" % response - #print "user still needs %s" % bytes - break - - if response == const.CMD_ACK_OK: - if userdata: - # The first 4 bytes don't seem to be related to the user - userdata = ''.join(userdata) - userdata = userdata[4:] - if self.__firmware == 6: - while len(userdata) >= 28: - uid, privilege, password, name, card, group_id, timezone, user_id = unpack('HB5s8s5sBhI',userdata.ljust(28)[:28]) - password = unicode(password.split('\x00')[0], errors='ignore') - name = unicode(name.split('\x00')[0], errors='ignore').strip() - card = unpack('Q', card.ljust(8,'\x00'))[0] #or hex value? - group_id = str(group_id) - user_id = str(user_id) - #TODO: check card value and find in ver8 - if not name: - name = "NN-%s" % user_id - user = User(uid, name, privilege, password, group_id, user_id) - users.append(user) - print "[6]user:",uid, privilege, password, name, card, group_id, timezone, user_id - userdata = userdata[28:] - else: - while len(userdata) >= 72: - uid, privilege, password, name, separator, group_id, user_id = unpack('Hc8s24s4sx7sx24s', userdata.ljust(72)[:72]) - #u1 = int(uid[0].encode("hex"), 16) - #u2 = int(uid[1].encode("hex"), 16) - #uid = u1 + (u2 * 256) - privilege = int(privilege.encode("hex"), 16) - password = unicode(password.split('\x00')[0], errors='ignore') - name = unicode(name.split('\x00')[0], errors='ignore').strip() - group_id = unicode(group_id.split('\x00')[0], errors='ignore').strip() - user_id = unicode(user_id.split('\x00')[0], errors='ignore') - card = int(unpack('I', separator)[0]) - if not name: - name = "NN-%s" % user_id - user = User(uid, name, privilege, password, group_id, user_id, card) - users.append(user) - - userdata = userdata[72:] - self.free_data() - else: - raise ZKErrorResponse("can't _get user") - return users def cancel_capture(self): ''' @@ -969,7 +924,7 @@ class ZK(object): print "WRN: no attendance data" # debug return [] attendance_data = attendance_data[4:] #total size not used - if self.__firmware == 6: + if self.firmware == 6: while len(attendance_data) >= 8: uid, status, timestamp = unpack('HH4s', attendance_data.ljust(8)[:8]) attendance_data = attendance_data[8:] @@ -1023,7 +978,7 @@ class ZK(object): if attendance_data: attendance_data = ''.join(attendance_data) attendance_data = attendance_data[4:] - if self.__firmware == 6: + if self.firmware == 6: while len(attendance_data) >= 8: uid, status, timestamp = unpack('HH4s', attendance_data.ljust(8)[:8]) attendance_data = attendance_data[8:] diff --git a/zk/user.py b/zk/user.py index 50926e9..4cea229 100644 --- a/zk/user.py +++ b/zk/user.py @@ -12,7 +12,10 @@ class User(object): self.card = card # 64 int to 40 bit int def repack29(self): # with 02 for zk6 (size 29) return pack(" 7sx group id, timezone? + return pack(": [uid:{}, name:{} user_id:{}]'.format(self.uid, self.name, self.user_id) From 86826f9f7c00326409c35a226e45234ee329c9a3 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Wed, 25 Apr 2018 20:42:04 -0400 Subject: [PATCH 24/83] MEGA UPDATE! now with TCP support (on test!) mayor fixes but trying to keep functionality --- .gitignore | 1 + test_machine.py | 139 +++++++++++---- test_voice.py | 1 + zk/base.py | 443 ++++++++++++++++++++++++++++++------------------ zk/const.py | 5 + 5 files changed, 397 insertions(+), 192 deletions(-) mode change 100644 => 100755 test_machine.py mode change 100644 => 100755 test_voice.py diff --git a/.gitignore b/.gitignore index 971a4a7..70e6997 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ build/ dist/ *.egg-info/ +*.test \ No newline at end of file diff --git a/test_machine.py b/test_machine.py old mode 100644 new mode 100755 index 2101d0d..34986f6 --- a/test_machine.py +++ b/test_machine.py @@ -1,11 +1,19 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python2 +# # -*- coding: utf-8 -*- import sys +import traceback import argparse - -from zk import ZK, const +import time +import datetime sys.path.append("zk") +from zk import ZK, const +from zk.attendance import Attendance +from zk.exception import ZKErrorResponse, ZKNetworkError +from zk.user import User +from zk.finger import Finger + conn = None @@ -15,19 +23,27 @@ parser.add_argument('-a', '--address', parser.add_argument('-p', '--port', type=int, help='device port', default=4370) parser.add_argument('-T', '--timeout', type=int, - help='timeout', default=60) + help='timeout', default=10) parser.add_argument('-P', '--password', type=int, help='Device code/password', default=0) -#parser.add_argument('-f', '--firmware', type=int, -# help='test firmware', default=8) +parser.add_argument('-f', '--force-udp', action="store_true", + help='Force UDP communication') parser.add_argument('-t', '--templates', action="store_true", help='get templates') parser.add_argument('-r', '--records', action="store_true", help='get records') +parser.add_argument('-u', '--updatetime', action="store_true", + help='Update Date / Time') +parser.add_argument('-D', '--deleteuser', type=int, + help='Delete a User', default=0) +parser.add_argument('-A', '--adduser', type=int, + help='Add a User', default=0) +parser.add_argument('-F', '--finger', type=int, + help='Finger for register', default=0) args = parser.parse_args() -zk = ZK(args.address, port=args.port, timeout=args.timeout, password=args.password) # , firmware=args.firmware +zk = ZK(args.address, port=args.port, timeout=args.timeout, password=args.password, force_udp=args.force_udp) # , firmware=args.firmware try: print 'Connecting to device ...' conn = zk.connect() @@ -35,14 +51,15 @@ try: conn.disable_device() fmt = conn.get_extend_fmt() print 'ExtendFmt : {}'.format(fmt) - if fmt == 1: - print "Firmware 6" - conn.firmware = 6 - else: - print "Firmware 8" - conn.firmware = 8 - - print 'Time : {}'.format(conn.get_time()) + now = datetime.datetime.today().replace(microsecond=0) + if args.updatetime: + print '--- Updating Time---' + conn.set_time(now) + zk_time = conn.get_time() + dif = abs(zk_time - now).total_seconds() + print 'Time : {}'.format(zk_time) + if dif > 120: + print("WRN: TIME IS NOT SYNC!!!!!! (local: %s)" % now) print 'Firmware Version : {}'.format(conn.get_firmware_version()) print 'Platform : %s' % conn.get_platform() print 'DeviceName : %s' % conn.get_device_name() @@ -50,20 +67,73 @@ try: print 'Serial Number : %s' % conn.get_serialnumber() print 'MAC: %s' % conn.get_mac() print '' - conn.read_sizes() - print conn - print '' print '--- Get User ---' users = conn.get_users() - for user in users: - privilege = 'User' - if user.privilege == const.USER_ADMIN: - privilege = 'Admin' + max_uid = 0 + prev = None + if not args.deleteuser: + for user in users: + privilege = 'User' + if user.uid > max_uid: + max_uid = user.uid + privilege = 'User' if user.privilege == const.USER_DEFAULT else 'Admin-%s' % user.privilege + print '-> UID #{:<5} Name : {:<27} Privilege : {}'.format(user.uid, user.name, privilege) + print ' Group ID : {:<8} User ID : {:<8} Password : {:<8} Card : {}'.format(user.group_id, user.user_id, user.password, user.card) + #print len (user.repack73()), user.repack73().encode('hex') + #print '' + if args.adduser and user.uid == args.adduser: + prev = user + if args.deleteuser: + print '' + print '-- Delete User UID#%s ---' % args.deleteuser + #TODO implementar luego + conn.delete_user(args.deleteuser) + users = conn.get_users() #update + for user in users: + if user.uid > max_uid: + max_uid = user.uid + privilege = 'User' if user.privilege == const.USER_DEFAULT else 'Admin-%s' % user.privilege + print '-> UID #{:<5} Name : {:<27} Privilege : {}'.format(user.uid, user.name, privilege) + print ' Group ID : {:<8} User ID : {:<8} Password : {:<8} Card : {}'.format(user.group_id, user.user_id, user.password, user.card) + #print len (user.repack73()), user.repack73().encode('hex') + #print '' + if args.adduser and user.uid == args.adduser: + prev = user - print '-> UID #{:<5} Name : {:<27} Privilege : {}'.format(user.uid, user.name, privilege) - print ' Group ID : {:<8} User ID : {:<8} Password : {:<8} Card : {}'.format(user.group_id, user.user_id, user.password, user.card) - #print len (user.repack73()), user.repack73().encode('hex') - #print '' + if args.adduser: + uid = int(args.adduser) + if prev: + user = prev + privilege = 'User' if user.privilege == const.USER_DEFAULT else 'Admin-%s' % user.privilege + print '' + print '--- Modify User %i ---' % user.uid + print '-> UID #{:<5} Name : {:<27} Privilege : {}'.format(user.uid, user.name, privilege) + print ' Group ID : {:<8} User ID : {:<8} Password : {:<8} Card : {}'.format(user.group_id, user.user_id, user.password, user.card) + #discard prev + else: + print '--- Add new User %i ---' % uid + name = raw_input('Name :') + admin = raw_input('Admin (y/n):') + privilege = 14 if admin == 'y' else 0 + password = raw_input('Password :') + user_id = raw_input('User ID2 :') + card = int(raw_input('Card :')) + if prev: + conn.delete_user(uid) #borrado previo + try: + conn.set_user(uid, name, privilege, password, '', user_id, card) + except ZKErrorResponse, e: + print "error: ", e + #try new format + zk_user = User(uid, name, privilege, password, '', user_id, card) + conn.save_user_template(zk_user) + conn.delete_user_template(uid, args.finger) + conn.reg_event(0xFFFF) # + if conn.enroll_user(uid, args.finger): + conn.test_voice(18) # register ok + else: + conn.test_voice(23) # not registered + conn.refresh_data() print "Voice Test ..." conn.test_voice(10) if args.templates: @@ -74,13 +144,24 @@ try: if args.records: print "Read Records..." attendance = conn.get_attendance() + i = 0 for att in attendance: - print "ATT: uid:{:>3}, t: {}".format(att.uid, att.timestamp) - print 'Enabling device ...' - conn.enable_device() + i +=1 + print "ATT {:>6}: uid:{:>3}, t: {}".format(i, att.uid, att.timestamp) + print '' + print '--- sizes & capacity ---' + conn.read_sizes() + print conn + print '' except Exception, e: print "Process terminate : {}".format(e) print "Error: %s" % sys.exc_info()[0] + print '-'*60 + traceback.print_exc(file=sys.stdout) + print '-'*60 finally: if conn: + print 'Enabling device ...' + conn.enable_device() conn.disconnect() + print '' diff --git a/test_voice.py b/test_voice.py old mode 100644 new mode 100755 index f7a6cc5..106ba73 --- a/test_voice.py +++ b/test_voice.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python2 # -*- coding: utf-8 -*- import sys import argparse diff --git a/zk/base.py b/zk/base.py index fbd5f51..ddd7d02 100644 --- a/zk/base.py +++ b/zk/base.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import sys from datetime import datetime -from socket import AF_INET, SOCK_DGRAM, socket +from socket import AF_INET, SOCK_DGRAM, SOCK_STREAM, socket from struct import pack, unpack from zk import const @@ -44,17 +44,55 @@ def make_commkey(key, session_id, ticks=50): B, k[3] ^ B) return k +class ZK_helper(object): + """ helper class """ + def __init__(self, ip, port=4370): + self.address = (ip, port) + self.ip = ip + self.port = port + #self.timeout = timeout + #self.password = password # passint + #self.firmware = int(firmware) #TODO check minor version? + #self.tcp = tcp + def test_ping(self): + """ + Returns True if host responds to a ping request + """ + import subprocess, platform + # Ping parameters as function of OS + ping_str = "-n 1" if platform.system().lower()=="windows" else "-c 1" + args = "ping " + " " + ping_str + " " + self.ip + need_sh = False if platform.system().lower()=="windows" else True + # Ping + return subprocess.call(args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + shell=need_sh) == 0 + def test_tcp(self): + self.client = socket(AF_INET, SOCK_STREAM) + self.client.settimeout(10) # fixed test + res = self.client.connect_ex(self.address) + self.client.close() + return res + def test_udp(self): + self.client = socket(AF_INET, SOCK_DGRAM) + self.client.settimeout(10) # fixed test class ZK(object): """ Clase ZK """ - def __init__(self, ip, port=4370, timeout=60, password=0, firmware=8): + def __init__(self, ip, port=4370, timeout=60, password=0, force_udp=False, ommit_ping=False): """ initialize instance """ self.is_connect = False + self.helper = ZK_helper(ip, port) self.__address = (ip, port) self.__sock = socket(AF_INET, SOCK_DGRAM) self.__sock.settimeout(timeout) + self.__timeout = timeout self.__password = password # passint - self.firmware = int(firmware) #TODO check minor version? + #self.firmware = int(firmware) #dummy + self.force_udp = force_udp + self.ommit_ping = ommit_ping + self.tcp = False self.users = 0 self.fingers = 0 self.records = 0 @@ -68,9 +106,24 @@ class ZK(object): self.fingers_av = 0 self.users_av = 0 self.rec_av = 0 + self.user_packet_size = 28 # default zk6 self.__session_id = 0 self.__reply_id = const.USHRT_MAX-1 self.__data_recv = None + def __create_socket(self): + """ based on self.tcp""" + if self.tcp: + self.__sock = socket(AF_INET, SOCK_STREAM) + self.__sock.connect_ex(self.__address) + else: + self.__sock = socket(AF_INET, SOCK_DGRAM) + + + def __create_tcp_top(self, packet): + """ witch the complete packet set top header """ + length = len(packet) + top = pack(' 20: self.faces = fields[20] self.faces_cap = fields[22] - #TODO: get faces size... - return True else: raise ZKErrorResponse("can't read sizes") def __str__(self): """ for debug""" - return "ZK%i adr:%s:%s users:%i/%i fingers:%i/%i, records:%i/%i faces:%i/%i" % ( - self.firmware, self.__address[0], self.__address[1], - self.users, self.users_cap, self.fingers, self.fingers_cap, - self.records, self.rec_cap, self.faces, self.faces_cap + return "ZK %s://%s:%s users[%i]:%i/%i fingers:%i/%i, records:%i/%i faces:%i/%i" % ( + "tcp" if self.tcp else "udp", self.__address[0], self.__address[1], + self.user_packet_size, self.users, self.users_cap, + self.fingers, self.fingers_cap, + self.records, self.rec_cap, + self.faces, self.faces_cap ) def restart(self): @@ -549,18 +626,12 @@ class ZK(object): #uid = chr(uid % 256) + chr(uid >> 8) if privilege not in [const.USER_DEFAULT, const.USER_ADMIN]: privilege = const.USER_DEFAULT - privilege = chr(privilege) - if self.firmware == 6: - print "uid : %i" % uid - print "pri : %c" % privilege - print "pass: %s" % str(password) - print "name: %s" % str(name) - print type(name) - print "group %i" % int(group_id) - print "uid2: %i" % int(user_id) + privilege = int(privilege) + if self.user_packet_size == 28: #self.firmware == 6: + if not group_id: + group_id = 0 try: - command_string = pack('Hc5s8s5sBHI', uid, privilege, str(password), str(name), chr(0), int(group_id), 0, int(user_id)) - print "cmd : %s" % command_string + command_string = pack('HB5s8s5sBHI', uid, privilege, str(password), str(name), chr(0), int(group_id), 0, int(user_id)) except Exception, e: print "s_h Error pack: %s" % e print "Error pack: %s" % sys.exc_info()[0] @@ -568,14 +639,13 @@ class ZK(object): else: name_pad = name.ljust(24, '\x00')[:24] card_str = pack('i', int(card))[:4] - command_string = pack('Hc8s24s4sc7sx24s', uid, privilege, password, name_pad, card_str, chr(0), group_id, user_id) - response_size = 1024 + command_string = pack('HB8s24s4sc7sx24s', uid, privilege, password, name_pad, card_str, chr(0), group_id, str(user_id)) + response_size = 1024 #TODO check response? cmd_response = self.__send_command(command, command_string, response_size) - if cmd_response.get('status'): - return True - else: + if not cmd_response.get('status'): raise ZKErrorResponse("Cant set user") - + self.refresh_data() + def save_user_template(self, user, fingers=[]): """ save user and template """ #TODO: grabado global @@ -591,9 +661,9 @@ class ZK(object): table += pack("> 8) - #command_string = pack('2s', uid) command_string = pack('h', uid) cmd_response = self.__send_command(command, command_string) - if cmd_response.get('status'): - return True - else: + if not cmd_response.get('status'): raise ZKErrorResponse("can't delete user") + self.refresh_data() def get_user_template(self, uid, temp_id): + """ ZKFinger VX10.0 """ command = 88 # comando secreto!!! command_string = pack('hb', uid, temp_id) response_size = 1024 @@ -698,36 +778,34 @@ class ZK(object): return [] total_size = unpack('i', templatedata[0:4])[0] templatedata = templatedata[4:] #total size not used - if self.firmware == 6: #tested! - while total_size: - size, uid, fid, valid = unpack('HHbb',templatedata[:6]) - template = unpack("%is" % (size-6), templatedata[6:size])[0] - finger = Finger(size, uid, fid, valid, template) - #print finger # test - templates.append(finger) - templatedata = templatedata[size:] - total_size -= size - else: # tested with ZEM800_TFT - iFace402/ID - while total_size: - size, uid, fid, valid = unpack('HHbb',templatedata[:6]) - template = unpack("%is" % (size-6), templatedata[6:size])[0] - finger = Finger(size - 6, uid, fid, valid, template) - #print finger # test - templates.append(finger) - templatedata = templatedata[(size):] - total_size -= size + # ZKFinger VX10.0 the only finger firmware tested + while total_size: + size, uid, fid, valid = unpack('HHbb',templatedata[:6]) + template = unpack("%is" % (size-6), templatedata[6:size])[0] + finger = Finger(size, uid, fid, valid, template) + #print finger # test + templates.append(finger) + templatedata = templatedata[size:] + total_size -= size return templates - def get_users(self): + def get_users(self): #ALWAYS CALL TO GET correct user_packet_size """ return all user """ + self.read_sizes() # last update + if self.users == 0: #lazy + return [] users = [] userdata, size = self.read_with_buffer(const.CMD_USERTEMP_RRQ, const.FCT_USER) #print "user size %i" % size - if size < 4: + if size <= 4: print "WRN: no user data" # debug return [] - userdata = userdata[4:] #total size not used - if self.firmware == 6: + total_size = unpack("I",userdata[:4])[0] + self.user_packet_size = total_size / self.users + if not self.user_packet_size in [28, 72]: + print "WRN packet size would be ", self.user_packet_size + userdata = userdata[4:] + if self.user_packet_size == 28: while len(userdata) >= 28: uid, privilege, password, name, card, group_id, timezone, user_id = unpack('HB5s8s5sBhI',userdata.ljust(28)[:28]) password = unicode(password.split('\x00')[0], errors='ignore') @@ -796,58 +874,95 @@ class ZK(object): cmd_response = self.__send_command(command, command_string) if not cmd_response.get('status'): raise ZKErrorResponse("can't set sdk build ") - def enroll_user(self, uid, temp_id=0): + def enroll_user(self, uid=0, temp_id=0, user_id=''): ''' start enroll user ''' command = const.CMD_STARTENROLL - command_string = pack('hhb', uid, 0, temp_id) # el 0 es misterio + done = False + if self.tcp: + if not user_id: + #we need user_id (uid2) + users = self.get_users() + users = filter(lambda x: x.uid==uid, users) + if len(users) == 1: + user_id = users[0].user_id + else: #double? posibly empty + return False #can't enrool + command_string = pack('<24sbb',str(user_id), temp_id, 1) # el 1 es misterio + else: + command_string = pack('hhb', int(uid), 0, temp_id) # el 0 es misterio + self.cancel_capture() cmd_response = self.__send_command(command, command_string) if not cmd_response.get('status'): raise ZKErrorResponse("Cant Enroll user #%i [%i]" %(uid, temp_id)) - print "enroll", cmd_response #retorna rapido toca esperar un reg event + self.__sock.settimeout(60)# default 1min for finger attempts = 3 while attempts: print "A:%i esperando primer regevent" % attempts data_recv = self.__sock.recv(1032) # timeout? tarda bastante... self.__ack_ok() print (data_recv).encode('hex') - if len(data_recv) > 8: #not empty - res = unpack("H", data_recv.ljust(16,"\x00")[8:10])[0] - print "res", res - if res == 6: - print ("posible timeout") - return False + if self.tcp: + if len(data_recv) > 16: #not empty + res = unpack("H", data_recv.ljust(24,"\x00")[16:18])[0] + print "res", res + if res == 0 or res == 6 or res == 4: + # 6 timeout, 4 mismatch error, 0 can't start(why?) + print ("posible timeout o reg Fallido") + break + else: + if len(data_recv) > 8: #not empty + res = unpack("H", data_recv.ljust(16,"\x00")[8:10])[0] + print "res", res + if res == 6 or res == 4: + print ("posible timeout o reg Fallido") + break print "A:%i esperando 2do regevent" % attempts data_recv = self.__sock.recv(1032) # timeout? tarda bastante... self.__ack_ok() print (data_recv).encode('hex') - if len(data_recv) > 8: #not empty + if self.tcp: + if len(data_recv) > 8: #not empty + res = unpack("H", data_recv.ljust(24,"\x00")[16:18])[0] + print "res", res + if res == 6 or res == 4: + print ("posible timeout o reg Fallido") + break + elif res == 0x64: + print ("ok, continue?") + attempts -= 1 + else: + if len(data_recv) > 8: #not empty + res = unpack("H", data_recv.ljust(16,"\x00")[8:10])[0] + print "res", res + if res == 6 or res == 4: + print ("posible timeout o reg Fallido") + break + elif res == 0x64: + print ("ok, continue?") + attempts -= 1 + if attempts == 0: + print "esperando 3er regevent" + data_recv = self.__sock.recv(1032) # timeout? tarda bastante... + self.__ack_ok() + print (data_recv).encode('hex') + if self.tcp: + res = unpack("H", data_recv.ljust(24,"\x00")[16:18])[0] + else: res = unpack("H", data_recv.ljust(16,"\x00")[8:10])[0] - print "res", res - if res == 6: - print ("posible timeout") - return False - elif res == 0x64: - print ("ok, continue?") - attempts -= 1 - - print "esperando 3er regevent" - data_recv = self.__sock.recv(1032) # timeout? tarda bastante... - self.__ack_ok() - print (data_recv).encode('hex') - res = unpack("H", data_recv.ljust(16,"\x00")[8:10])[0] - if res == 4: - print "registro Fallido" - self.cancel_capture() - return False - if res == 0: - size = unpack("H", data_recv.ljust(16,"\x00")[10:12])[0] - pos = unpack("H", data_recv.ljust(16,"\x00")[12:14])[0] - print "enroll ok", size, pos - return True - + if res == 6 or res == 4: + print ("posible timeout o reg Fallido") + if res == 0: + size = unpack("H", data_recv.ljust(16,"\x00")[10:12])[0] + pos = unpack("H", data_recv.ljust(16,"\x00")[12:14])[0] + print "enroll ok", size, pos + done = True + self.__sock.settimeout(self.__timeout) + self.cancel_capture() + self.verify_user() + return done def clear_data(self, clear_type=5): # FCT_USER ''' clear all data (include: user, attendance report, finger database ) @@ -871,29 +986,68 @@ class ZK(object): raise ZKErrorResponse("can't read chunk %i:[%i]" % (start, size)) #else if cmd_response.get('code') == const.CMD_DATA: # less than 1024!!! - return self.__data_recv[8:] + if self.tcp: + return self.__data_recv[16:] #TODO: check size? + else: + return self.__data_recv[8:] if cmd_response.get('code') == const.CMD_PREPARE_DATA: data = [] bytes = self.__get_data_size() #TODO: check with size + #print "prepare data size is", bytes + if self.tcp: + data_recv = self.__sock.recv(bytes + 32) + recieved = len(data_recv) + tcp_length = unpack('HHI', data_recv[:8])[2] #bytes+8 + if tcp_length < (bytes + 8): + print "request chunk too big!" + response = unpack('HHHH', data_recv[8:16])[0] + if recieved >= (bytes + 32): #complete + if response == const.CMD_DATA: + resp = data_recv[16:bytes+16] # no ack? + #print "resp len", len(resp) + return resp + else: + print "broken packet!!!" + return '' #broken + else: # incomplete + data.append(data_recv[16:]) # w/o tcp and header + bytes -= recieved-16 + while bytes>0: #jic + data_recv = self.__sock.recv(bytes) #ideal limit? + recieved = len(data_recv) + data.append(data_recv) # w/o tcp and header + bytes -= recieved + data_recv = self.__sock.recv(16) + response = unpack('HHHH', data_recv[8:16])[0] + if response == const.CMD_ACK_OK: + return ''.join(data) + #data_recv[bytes+16:].encode('hex') #included CMD_ACK_OK + print "bad response ", data_recv.encode('hex') + #print data + return '' + #else udp while True: #limitado por respuesta no por tamaƱo - data_recv = self.__sock.recv(1032) + data_recv = self.__sock.recv(response_size) response = unpack('HHHH', data_recv[:8])[0] #print "# %s packet response is: %s" % (pac, response) if response == const.CMD_DATA: data.append(data_recv[8:]) #header turncated - bytes -= 1024 + bytes -= 1024 #UDP elif response == const.CMD_ACK_OK: break #without problem. else: #truncado! continuar? - #print "broken!" + print "broken!" break #print "still needs %s" % bytes return ''.join(data) def read_with_buffer(self, command, fct=0 ,ext=0): """ Test read info with buffered command (ZK6: 1503) """ - MAX_CHUNK = 16 * 1024 + if self.tcp: + MAX_CHUNK = 0xFFc0 #arbitrary, below 0x10008 + else: + MAX_CHUNK = 16 * 1024 command_string = pack('= 8: uid, status, timestamp = unpack('HH4s', attendance_data.ljust(8)[:8]) attendance_data = attendance_data[8:] @@ -934,6 +1098,16 @@ class ZK(object): timestamp = self.__decode_time(timestamp) attendance = Attendance(uid, user_id, timestamp, status) attendances.append(attendance) + elif record_size == 16: # extended + while len(attendance_data) >= 16: + uid, timestamp, status, verified, reserved, workcode = unpack('= 40: uid, user_id, sparator, timestamp, status, space = unpack('H24sc4sc8s', attendance_data.ljust(40)[:40]) @@ -945,64 +1119,7 @@ class ZK(object): attendances.append(attendance) attendance_data = attendance_data[40:] return attendances - def _get_attendance(self): - ''' - return all attendance record - ''' - command = const.CMD_ATTLOG_RRQ - command_string = '' - response_size = 1024 - cmd_response = self.__send_command(command, command_string, response_size) - attendances = [] - if cmd_response.get('status'): - if cmd_response.get('code') == const.CMD_PREPARE_DATA: - bytes = self.__get_data_size() - attendance_data = [] - pac = 1 - while True: #limitado por respuesta no por tamaƱo - data_recv = self.__sock.recv(1032) - response = unpack('HHHH', data_recv[:8])[0] - #print "# %s packet response is: %s" % (pac, response) - if response == const.CMD_DATA: - pac += 1 - attendance_data.append(data_recv[8:]) #header turncated - bytes -= 1024 - elif response == const.CMD_ACK_OK: - break #without problem. - else: - #truncado! continuar? - #print "broken!" - break - #print "still needs %s" % bytes - if response == const.CMD_ACK_OK: - if attendance_data: - attendance_data = ''.join(attendance_data) - attendance_data = attendance_data[4:] - if self.firmware == 6: - while len(attendance_data) >= 8: - uid, status, timestamp = unpack('HH4s', attendance_data.ljust(8)[:8]) - attendance_data = attendance_data[8:] - if uid == 0: #(on zk it's 16 bytes, extrauid 0, status 255, timestamp 0x0) - continue # probably - user_id = str(uid) #TODO revisar posibles valores cruzar con userdata - timestamp = self.__decode_time(timestamp) - attendance = Attendance(uid, user_id, timestamp, status) - attendances.append(attendance) - else: - while len(attendance_data) >= 40: - uid, user_id, sparator, timestamp, status, space = unpack('H24sc4sc8s', attendance_data.ljust(40)[:40]) - user_id = user_id.split('\x00')[0] - timestamp = self.__decode_time(timestamp) - status = int(status.encode("hex"), 16) - - attendance = Attendance(uid, user_id, timestamp, status) - attendances.append(attendance) - - attendance_data = attendance_data[40:] - self.free_data() - else: - raise ZKErrorResponse("can't _get attendance") - return attendances + def clear_attendance(self): ''' diff --git a/zk/const.py b/zk/const.py index ae369f8..8ca5665 100644 --- a/zk/const.py +++ b/zk/const.py @@ -94,6 +94,8 @@ EF_FPFTR = (1<<8)# be real-time capture fingerprint minutia EF_ALARM = (1<<9)# Alarm signal USER_DEFAULT = 0 +USER_ENROLLER = 2 +USER_MANAGER = 6 USER_ADMIN = 14 FCT_ATTLOG = 1 @@ -103,3 +105,6 @@ FCT_OPLOG = 4 FCT_USER = 5 FCT_SMS = 6 FCT_UDATA = 7 + +MACHINE_PREPARE_DATA_1 = 20560 # 0x5050 +MACHINE_PREPARE_DATA_2 = 32130 # 0x7282 \ No newline at end of file From a710842f6e30f2b6728966ad507b6bd03ec4fe4c Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Thu, 26 Apr 2018 15:35:54 -0400 Subject: [PATCH 25/83] fix menores --- test_machine.py | 4 +++ zk/base.py | 95 ++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 95 insertions(+), 4 deletions(-) diff --git a/test_machine.py b/test_machine.py index 34986f6..586589d 100755 --- a/test_machine.py +++ b/test_machine.py @@ -67,6 +67,10 @@ try: print 'Serial Number : %s' % conn.get_serialnumber() print 'MAC: %s' % conn.get_mac() print '' + print '--- sizes & capacity ---' + conn.read_sizes() + print conn + print '' print '--- Get User ---' users = conn.get_users() max_uid = 0 diff --git a/zk/base.py b/zk/base.py index ddd7d02..aab3deb 100644 --- a/zk/base.py +++ b/zk/base.py @@ -402,6 +402,36 @@ class ZK(object): else: raise ZKErrorResponse("can't read device name") + def get_face_version(self): + ''' + return the face version + ''' + command = const.CMD_OPTIONS_RRQ + command_string = 'ZKFaceVersion' + response_size = 1024 + + cmd_response = self.__send_command(command, command_string, response_size) + if cmd_response.get('status'): + response = self.__data_recv[8:].split('=')[-1].split('\x00')[0] + return response + else: + return None + + def get_fp_version(self): + ''' + return the fingerprint version + ''' + command = const.CMD_OPTIONS_RRQ + command_string = '~ZKFPVersion' + response_size = 1024 + + cmd_response = self.__send_command(command, command_string, response_size) + if cmd_response.get('status'): + response = self.__data_recv[8:].split('=')[-1].split('\x00')[0] + return response + else: + return None + def get_extend_fmt(self): ''' determine extend fmt @@ -418,6 +448,67 @@ class ZK(object): else: raise ZKErrorResponse("can't read extend fmt") + def get_user_extend_fmt(self): + ''' + determine extend fmt + ''' + command = const.CMD_OPTIONS_RRQ + command_string = '~UserExtFmt' + response_size = 1024 + + cmd_response = self.__send_command(command, command_string, response_size) + if cmd_response.get('status'): + fmt = int(self.__data_recv[8:].split('=')[-1].split('\x00')[0]) + #definitivo? seleccionar firmware aqui? + return fmt + else: + return None + + def get_face_fun_on(self): + ''' + determine extend fmt + ''' + command = const.CMD_OPTIONS_RRQ + command_string = 'FaceFunOn' + response_size = 1024 + + cmd_response = self.__send_command(command, command_string, response_size) + if cmd_response.get('status'): + response = int(self.__data_recv[8:].split('=')[-1].split('\x00')[0]) + #definitivo? seleccionar firmware aqui? + return response + else: + return None + + def get_compat_old_firmware(self): + ''' + determine extend fmt + ''' + command = const.CMD_OPTIONS_RRQ + command_string = 'CompatOldFirmware' + response_size = 1024 + + cmd_response = self.__send_command(command, command_string, response_size) + if cmd_response.get('status'): + response = int(self.__data_recv[8:].split('=')[-1].split('\x00')[0]) + #definitivo? seleccionar firmware aqui? + return response + else: + return None + def get_network_params(self): + ip = self.__address[0] + mask = '' + gate = '' + cmd_response = self.__send_command(const.CMD_OPTIONS_RRQ, 'IPAddress', 1024) + if cmd_response.get('status'): + ip = int(self.__data_recv[8:].split('=')[-1].split('\x00')[0]) + cmd_response = self.__send_command(const.CMD_OPTIONS_RRQ, 'NetMask', 1024) + if cmd_response.get('status'): + mask = int(self.__data_recv[8:].split('=')[-1].split('\x00')[0]) + cmd_response = self.__send_command(const.CMD_OPTIONS_RRQ, 'GATEIPAddress', 1024) + if cmd_response.get('status'): + gate = int(self.__data_recv[8:].split('=')[-1].split('\x00')[0]) + return {'ip': ip, 'mask': mask, 'gateway': gate} def get_pin_width(self): ''' return the serial number @@ -1092,8 +1183,6 @@ class ZK(object): while len(attendance_data) >= 8: uid, status, timestamp = unpack('HH4s', attendance_data.ljust(8)[:8]) attendance_data = attendance_data[8:] - if uid == 0: #(on zk it's 16 bytes, extrauid 0, status 255, timestamp 0x0) - continue # probably user_id = str(uid) #TODO revisar posibles valores cruzar con userdata timestamp = self.__decode_time(timestamp) attendance = Attendance(uid, user_id, timestamp, status) @@ -1102,8 +1191,6 @@ class ZK(object): while len(attendance_data) >= 16: uid, timestamp, status, verified, reserved, workcode = unpack(' Date: Thu, 26 Apr 2018 16:40:57 -0400 Subject: [PATCH 26/83] update LUA dissector ( for tcp and udp dump) still not complete, but helps a lot --- zk6.lua | 206 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 192 insertions(+), 14 deletions(-) diff --git a/zk6.lua b/zk6.lua index f8db47b..1997d0b 100644 --- a/zk6.lua +++ b/zk6.lua @@ -123,6 +123,17 @@ assert(ProtoExpert.new, "Wireshark does not have the ProtoExpert class, so it's ---------------------------------------- -- creates a Proto object, but doesn't register it yet local zk = Proto("zk6","ZK600 UDP Protocol") +local zk_tcp = Proto("zk8","ZK800 TCP Protocol") + +local rfct = { + [1] = "FCT_ATTLOG", + [8] = "FCT_WORKCODE", + [2] = "FCT_FINGERTMP", + [4] = "FCT_OPLOG", + [5] = "FCT_USER", + [6] = "FCT_SMS", + [7] = "FCT_UDATA" +} local rcomands = { [7] = "CMD_DB_RRQ", @@ -191,6 +202,8 @@ local rcomands = { [1500] = "CMD_PREPARE_DATA", [1501] = "CMD_DATA", [1502] = "CMD_FREE_DATA", + [1503] = "CMD_PREPARE_BUFFER", + [1504] = "CMD_READ_BUFFER", [2000] = "CMD_ACK_OK", [2001] = "CMD_ACK_ERROR", [2002] = "CMD_ACK_DATA", @@ -202,20 +215,67 @@ local rcomands = { [65532] = "CMD_ACK_ERROR_INIT", [65531] = "CMD_ACK_ERROR_DATA" } +local rmachines = { + [20560] = "MACHINE_PREPARE_DATA_1", + [32130] = "MACHINE_PREPARE_DATA_2" +} ---------------------------------------- +local pf_machine1 = ProtoField.new ("Machine Data 1", "zk8.machine1", ftypes.UINT16, rmachines, base.DEC) +local pf_machine2 = ProtoField.new ("Machine Data 2", "zk8.machine2", ftypes.UINT16, rmachines, base.DEC) +local pf_length = ProtoField.new ("Length", "zk8.length", ftypes.UINT32, nil, base.DEC) + local pf_command = ProtoField.new ("Command", "zk6.command", ftypes.UINT16, rcomands, base.DEC) local pf_checksum = ProtoField.new ("CheckSum", "zk6.checksum", ftypes.UINT16, nil, base.HEX) local pf_sesion_id = ProtoField.uint16("zk6.session_id", "ID session", base.HEX) local pf_reply_id = ProtoField.uint16("zk6.reply_id", "ID Reply") +local pf_commkey = ProtoField.new ("Communication key", "zk6.commkey", ftypes.UINT32, nil, base.HEX) local pf_data = ProtoField.new ("Data", "zk6.data", ftypes.BYTES, nil, base.DOT) +local pf_string = ProtoField.new ("Data", "zk6.string", ftypes.STRING) local pf_time = ProtoField.new ("Time", "zk6.time", ftypes.UINT32, nil) - +local pf_start = ProtoField.new ("Data offset", "zk6.start", ftypes.UINT32, nil) +local pf_size = ProtoField.new ("Data Size", "zk6.size", ftypes.UINT32, nil) +local pf_psize = ProtoField.new ("Packet Size", "zk6.psize", ftypes.UINT32, nil) +local pf_fsize0 = ProtoField.new ("null #1", "zk6.fsize0", ftypes.UINT32, nil) +local pf_fsize1 = ProtoField.new ("null #2", "zk6.fsize1", ftypes.UINT32, nil) +local pf_fsize2 = ProtoField.new ("null #3", "zk6.fsize2", ftypes.UINT32, nil) +local pf_fsize3 = ProtoField.new ("null #4", "zk6.fsize3", ftypes.UINT32, nil) +local pf_fsizeu = ProtoField.new ("users", "zk6.fsizeu", ftypes.UINT32, nil) +local pf_fsize4 = ProtoField.new ("null #5", "zk6.fsize4", ftypes.UINT32, nil) +local pf_fsizef = ProtoField.new ("fingers", "zk6.fsizef", ftypes.UINT32, nil) +local pf_fsize5 = ProtoField.new ("null #6", "zk6.fsize5", ftypes.UINT32, nil) +local pf_fsizer = ProtoField.new ("records", "zk6.fsizer", ftypes.UINT32, nil) +local pf_fsize6 = ProtoField.new ("null #7", "zk6.fsize6", ftypes.UINT32, nil) +local pf_fsize7 = ProtoField.new ("null 4096", "zk6.fsize7", ftypes.UINT32, nil) +local pf_fsize8 = ProtoField.new ("null #8", "zk6.fsize8", ftypes.UINT32, nil) +local pf_fsizec = ProtoField.new ("cards", "zk6.fsizec", ftypes.UINT32, nil) +local pf_fsize9 = ProtoField.new ("null #9", "zk6.fsize9", ftypes.UINT32, nil) +local pf_fsizefc = ProtoField.new ("finger capacity", "zk6.fsizefc", ftypes.UINT32, nil) +local pf_fsizeuc = ProtoField.new ("user capacity", "zk6.fsizeuc", ftypes.UINT32, nil) +local pf_fsizerc = ProtoField.new ("record capacity", "zk6.fsizerc", ftypes.UINT32, nil) +local pf_fsizefa = ProtoField.new ("finger available", "zk6.fsizefa", ftypes.UINT32, nil) +local pf_fsizeua = ProtoField.new ("user available", "zk6.fsizeua", ftypes.UINT32, nil) +local pf_fsizera = ProtoField.new ("record available", "zk6.fsizera", ftypes.UINT32, nil) +local pf_fsizeff = ProtoField.new ("face", "zk6.fsizerff", ftypes.UINT32, nil) +local pf_fsize10 = ProtoField.new ("nul #10", "zk6.fsize10", ftypes.UINT32, nil) +local pf_fsizeffc = ProtoField.new ("face capacity", "zk6.fsizeffc", ftypes.UINT32, nil) +local pf_pbfill = ProtoField.new ("null 01", "zk6.pbfill", ftypes.UINT8, nil) +local pf_pbcmd = ProtoField.new ("command", "zk6.pbcmd", ftypes.UINT16, rcomands) +local pf_pbarg = ProtoField.new ("argument", "zk6.pbarg", ftypes.UINT64, rfct) +local pf_pbfill0 = ProtoField.new ("null 0", "zk6.pbfill0", ftypes.UINT8, nil) +local pf_pbfree = ProtoField.new ("free space", "zk6.pbfree", ftypes.UINT32, nil) +local pf_uid = ProtoField.new ("User ID", "zk6.uid", ftypes.UINT16, nil) ---------------------------------------- -- this actually registers the ProtoFields above, into our new Protocol -- in a real script I wouldn't do it this way; I'd build a table of fields programmatically -- and then set dns.fields to it, so as to avoid forgetting a field -zk.fields = { pf_command, pf_checksum, pf_sesion_id, pf_reply_id, pf_data, pf_time} +zk.fields = { pf_command, pf_checksum, pf_sesion_id, pf_reply_id, pf_commkey, pf_data, pf_string, + pf_time, pf_start, pf_size, pf_psize, pf_fsize0, pf_fsize1, pf_fsize2, pf_fsize3, + pf_fsizeu, pf_fsize4, pf_fsizef, pf_fsize5,pf_fsizer,pf_fsize6,pf_fsize7, + pf_fsize8,pf_fsizec,pf_fsize9,pf_fsizefc,pf_fsizeuc,pf_fsizerc, pf_uid, + pf_fsizefa,pf_fsizeua,pf_fsizera, pf_fsizeff, pf_fsize10, pf_fsizeffc, + pf_pbfill, pf_pbcmd, pf_pbarg, pf_pbfill0, pf_pbfree} +zk_tcp.fields = { pf_machine1, pf_machine2, pf_length } ---------------------------------------- -- we don't just want to display our protocol's fields, we want to access the value of some of them too! -- There are several ways to do that. One is to just parse the buffer contents in Lua code to find @@ -226,12 +286,41 @@ zk.fields = { pf_command, pf_checksum, pf_sesion_id, pf_reply_id, pf_data, pf_ti -- referencing fields we're creating, and they're not "created" until that line above. -- Furthermore, you cannot put these 'Field.new()' lines inside the dissector function. -- Before Wireshark version 1.11, you couldn't even do this concept (of using fields you just created). -local command_field = Field.new("zk6.command") -local checksum_field = Field.new("zk6.checksum") -local session_id_field = Field.new("zk6.session_id") -local reply_id_field = Field.new("zk6.reply_id") -local data_field = Field.new("zk6.data") -local time_field =Field.new("zk6.time") +local machine1_field = Field.new("zk8.machine1") +local machine2_field = Field.new("zk8.machine2") +local length_field = Field.new("zk8.length") +local command_field = Field.new("zk6.command") +local checksum_field = Field.new("zk6.checksum") +local session_id_field = Field.new("zk6.session_id") +local reply_id_field = Field.new("zk6.reply_id") +local commkey_field = Field.new("zk6.commkey") +local data_field = Field.new("zk6.data") +local string_field = Field.new("zk6.string") +local time_field = Field.new("zk6.time") +local size_field = Field.new("zk6.size") +local start_field = Field.new("zk6.start") +local psize_field = Field.new("zk6.psize") +local fsize0_field = Field.new("zk6.fsize0") +local fsize1_field = Field.new("zk6.fsize1") +local fsize2_field = Field.new("zk6.fsize2") +local fsize3_field = Field.new("zk6.fsize3") +local fsize4_field = Field.new("zk6.fsize4") +local fsize5_field = Field.new("zk6.fsize5") +local fsize6_field = Field.new("zk6.fsize6") +local fsize7_field = Field.new("zk6.fsize7") +local fsize8_field = Field.new("zk6.fsize8") +local fsize9_field = Field.new("zk6.fsize9") +local fsizef_field = Field.new("zk6.fsizef") +local fsizeu_field = Field.new("zk6.fsizeu") +local fsizer_field = Field.new("zk6.fsizer") +local fsizec_field = Field.new("zk6.fsizec") +local pbfill_field = Field.new("zk6.pbfill") +local pbcmd_field = Field.new("zk6.pbcmd") +local pbarg_field = Field.new("zk6.pbarg") +local pbfill0_field = Field.new("zk6.pbfill0") +local pbfree_field = Field.new("zk6.pbfree") +local uid_field = Field.new("zk6.uid") + -- here's a little helper function to access the response_field value later. -- Like any Field retrieval, you can't retrieve a field's value until its value has been -- set, which won't happen until we actually use our ProtoFields in TreeItem:add() calls. @@ -325,7 +414,7 @@ local prevCommand = 0 -- The 'tvbuf' is a Tvb object, 'pktinfo' is a Pinfo object, and 'root' is a TreeItem object. -- Whenever Wireshark dissects a packet that our Proto is hooked into, it will call -- this function and pass it these arguments for the packet it's dissecting. -function zk.dissector(tvbuf,pktinfo,root) +function zk.dissector(tvbuf, pktinfo, root) dprint2("zk.dissector called") -- set the protocol column to show our protocol name @@ -360,27 +449,116 @@ function zk.dissector(tvbuf,pktinfo,root) tree:add_le(pf_checksum, tvbuf:range(2,2)) tree:add_le(pf_sesion_id, tvbuf:range(4,2)) tree:add_le(pf_reply_id, tvbuf:range(6,2)) + local command = tvbuf:range(0,2):le_uint() if pktlen > ZK_HDR_LEN then remain = pktlen - ZK_HDR_LEN -- TODO: no funciona el prevCommand, - if (prevCommand == 201) or (prevCommand == 202) then + if (command == 1102) then + tree:add_le(pf_commkey, tvbuf:range(8,4)) + elseif (command == 1500) then + tree:add_le(pf_size, tvbuf:range(8,4)) + if remain > 8 then + tree:add_le(pf_psize, tvbuf:range(12,4)) + end + elseif (command == 12) or (command == 11) then + tree:add(pf_string, tvbuf:range(8,remain)) + elseif (command == 18) then + tree:add_le(pf_uid, tvbuf(8,2)) + elseif (command == 1503) then + tree:add(pf_pbfill, tvbuf:range(8,1)) + tree:add_le(pf_pbcmd, tvbuf:range(9,2)) + tree:add_le(pf_pbarg, tvbuf:range(11,8)) + elseif (command == 1504) then + tree:add_le(pf_start, tvbuf:range(8,4)) + tree:add_le(pf_size, tvbuf:range(12,4)) + elseif (prevCommand == 1503) then + tree:add_le(pf_pbfill0, tvbuf:range(8,1)) + tree:add_le(pf_size, tvbuf:range(9,4)) + tree:add_le(pf_psize, tvbuf:range(13,4)) + tree:add_le(pf_pbfree, tvbuf:range(17,4)) + elseif (prevCommand == 12) or (prevCommand == 11) or (prevCommand == 1100) then + tree:add(pf_string, tvbuf:range(8,remain)) + elseif (prevCommand == 201) or (prevCommand == 202) then local ts = tvbuf:range(8,4):le_uint() - tree:add_le(pf_time, tvbuf:range(8,4)) + tree:add_le(pf_time, tvbuf:range(8,4)) + elseif (prevCommand == 50) then + tree:add_le(pf_fsize0, tvbuf:range(8,4)) + tree:add_le(pf_fsize1, tvbuf:range(12,4)) + tree:add_le(pf_fsize2, tvbuf:range(16,4)) + tree:add_le(pf_fsize3, tvbuf:range(20,4)) + tree:add_le(pf_fsizeu, tvbuf:range(24,4)) + tree:add_le(pf_fsize4, tvbuf:range(28,4)) + tree:add_le(pf_fsizef, tvbuf:range(32,4)) + tree:add_le(pf_fsize5, tvbuf:range(36,4)) + tree:add_le(pf_fsizer, tvbuf:range(40,4)) + tree:add_le(pf_fsize6, tvbuf:range(44,4)) + tree:add_le(pf_fsize7, tvbuf:range(48,4)) + tree:add_le(pf_fsize8, tvbuf:range(52,4)) + tree:add_le(pf_fsizec, tvbuf:range(56,4)) + tree:add_le(pf_fsize9, tvbuf:range(60,4)) + tree:add_le(pf_fsizefc, tvbuf:range(64,4)) + tree:add_le(pf_fsizeuc, tvbuf:range(68,4)) + tree:add_le(pf_fsizerc, tvbuf:range(72,4)) + tree:add_le(pf_fsizefa, tvbuf:range(76,4)) + tree:add_le(pf_fsizeua, tvbuf:range(80,4)) + tree:add_le(pf_fsizera, tvbuf:range(84,4)) + if remain > 80 then + tree:add_le(pf_fsizeff, tvbuf:range(88,4)) + tree:add_le(pf_fsize10, tvbuf:range(92,4)) + tree:add_le(pf_fsizeffc, tvbuf:range(96,4)) + end else - tree:add_le(pf_data, tvbuf:range(8,remain)) + -- tree:add_le(pf_data, tvbuf:range(8,remain)) most time we need strings + tree:add(pf_string, tvbuf:range(8,remain)) end end dprint2("zk.dissector returning",pktlen) - prevCommand = tvbuf:range(0,2):le_uint() + prevCommand = command -- tell wireshark how much of tvbuff we dissected return pktlen end - ---------------------------------------- -- we want to have our protocol dissection invoked for a specific UDP port, -- so get the udp dissector table and add our protocol to it DissectorTable.get("udp.port"):add(default_settings.port, zk) +function zk_tcp.dissector(tvbuf, pktinfo, root) + dprint2("zk_tcp.dissector called") + local pktlen = tvbuf:reported_length_remaining() + + -- We start by adding our protocol to the dissection display tree. + -- A call to tree:add() returns the child created, so we can add more "under" it using that return value. + -- The second argument is how much of the buffer/packet this added tree item covers/represents - in this + -- case (DNS protocol) that's the remainder of the packet. + local tree = root:add(zk_tcp, tvbuf:range(0,pktlen)) + + -- now let's check it's not too short + if pktlen < ZK_HDR_LEN then + -- since we're going to add this protocol to a specific UDP port, we're going to + -- assume packets in this port are our protocol, so the packet being too short is an error + -- the old way: tree:add_expert_info(PI_MALFORMED, PI_ERROR, "packet too short") + -- the correct way now: + tree:add_proto_expert_info(ef_too_short) + dprint("packet length",pktlen,"too short") + return + end + dprint2("zk_tcp.dissector returning", pktlen) + tree:add_le(pf_machine1, tvbuf:range(0,2)) + tree:add_le(pf_machine2, tvbuf:range(2,2)) + tree:add_le(pf_length, tvbuf:range(4,4)) + -- tell wireshark how much of tvbuff we dissected + if pktlen > ZK_HDR_LEN then + remain = pktlen - ZK_HDR_LEN + -- zk_tree = tree:add(zk, tvbuf:range(8, remain)) + zk.dissector(tvbuf:range(8,remain):tvb(), pktinfo, tree) + end + -- set the protocol column to show our protocol name + pktinfo.cols.protocol:set("ZK8") + return pktlen +end + + +DissectorTable.get("tcp.port"):add(default_settings.port, zk_tcp) -- We're done! -- our protocol (Proto) gets automatically registered after this script finishes loading ---------------------------------------- From cb1177f61ac4a1554a4b70093abf0d93db4a25a5 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Thu, 26 Apr 2018 20:17:48 -0400 Subject: [PATCH 27/83] agregado funciones adicionales de prueba y fix diesctor --- test_machine.py | 11 ++++++++++- zk/base.py | 25 ++++++++++++++----------- zk6.lua | 5 +++++ 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/test_machine.py b/test_machine.py index 586589d..c9846e3 100755 --- a/test_machine.py +++ b/test_machine.py @@ -47,10 +47,19 @@ zk = ZK(args.address, port=args.port, timeout=args.timeout, password=args.passwo try: print 'Connecting to device ...' conn = zk.connect() + print 'SDK build=1 :', conn.set_sdk_build_1() # why? print 'Disabling device ...' conn.disable_device() fmt = conn.get_extend_fmt() print 'ExtendFmt : {}'.format(fmt) + fmt = conn.get_user_extend_fmt() + print 'UsrExtFmt : {}'.format(fmt) + print 'Face FunOn : {}'.format(conn.get_face_fun_on()) + print 'Face Version : {}'.format(conn.get_face_version()) + print 'Finger Version : {}'.format(conn.get_fp_version()) + print 'Old Firm compat : {}'.format(conn.get_compat_old_firmware()) + net = conn.get_network_params() + print 'IP:{} mask:{} gateway:{}'.format(net['ip'],net['mask'], net['gateway']) now = datetime.datetime.today().replace(microsecond=0) if args.updatetime: print '--- Updating Time---' @@ -59,7 +68,7 @@ try: dif = abs(zk_time - now).total_seconds() print 'Time : {}'.format(zk_time) if dif > 120: - print("WRN: TIME IS NOT SYNC!!!!!! (local: %s)" % now) + print("WRN: TIME IS NOT SYNC!!!!!! (local: %s) use command -u to update" % now) print 'Firmware Version : {}'.format(conn.get_firmware_version()) print 'Platform : %s' % conn.get_platform() print 'DeviceName : %s' % conn.get_device_name() diff --git a/zk/base.py b/zk/base.py index aab3deb..69e8ee0 100644 --- a/zk/base.py +++ b/zk/base.py @@ -442,9 +442,9 @@ class ZK(object): cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): - fmt = int(self.__data_recv[8:].split('=')[-1].split('\x00')[0]) + fmt = (self.__data_recv[8:].split('=')[-1].split('\x00')[0]) #definitivo? seleccionar firmware aqui? - return fmt + return int(fmt) else: raise ZKErrorResponse("can't read extend fmt") @@ -458,9 +458,9 @@ class ZK(object): cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): - fmt = int(self.__data_recv[8:].split('=')[-1].split('\x00')[0]) + fmt = (self.__data_recv[8:].split('=')[-1].split('\x00')[0]) #definitivo? seleccionar firmware aqui? - return fmt + return (fmt) else: return None @@ -474,9 +474,9 @@ class ZK(object): cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): - response = int(self.__data_recv[8:].split('=')[-1].split('\x00')[0]) + response = (self.__data_recv[8:].split('=')[-1].split('\x00')[0]) #definitivo? seleccionar firmware aqui? - return response + return (response) else: return None @@ -490,25 +490,27 @@ class ZK(object): cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): - response = int(self.__data_recv[8:].split('=')[-1].split('\x00')[0]) + response = (self.__data_recv[8:].split('=')[-1].split('\x00')[0]) #definitivo? seleccionar firmware aqui? return response else: return None + def get_network_params(self): ip = self.__address[0] mask = '' gate = '' cmd_response = self.__send_command(const.CMD_OPTIONS_RRQ, 'IPAddress', 1024) if cmd_response.get('status'): - ip = int(self.__data_recv[8:].split('=')[-1].split('\x00')[0]) + ip = (self.__data_recv[8:].split('=')[-1].split('\x00')[0]) cmd_response = self.__send_command(const.CMD_OPTIONS_RRQ, 'NetMask', 1024) if cmd_response.get('status'): - mask = int(self.__data_recv[8:].split('=')[-1].split('\x00')[0]) + mask = (self.__data_recv[8:].split('=')[-1].split('\x00')[0]) cmd_response = self.__send_command(const.CMD_OPTIONS_RRQ, 'GATEIPAddress', 1024) if cmd_response.get('status'): - gate = int(self.__data_recv[8:].split('=')[-1].split('\x00')[0]) + gate = (self.__data_recv[8:].split('=')[-1].split('\x00')[0]) return {'ip': ip, 'mask': mask, 'gateway': gate} + def get_pin_width(self): ''' return the serial number @@ -964,7 +966,8 @@ class ZK(object): command_string = "SDKBuild=1" cmd_response = self.__send_command(command, command_string) if not cmd_response.get('status'): - raise ZKErrorResponse("can't set sdk build ") + return False #raise ZKErrorResponse("can't set sdk build ") + return True def enroll_user(self, uid=0, temp_id=0, user_id=''): ''' start enroll user diff --git a/zk6.lua b/zk6.lua index 1997d0b..f947df0 100644 --- a/zk6.lua +++ b/zk6.lua @@ -512,6 +512,11 @@ function zk.dissector(tvbuf, pktinfo, root) end end dprint2("zk.dissector returning",pktlen) + if rcomands[command] ~= nil then + pktinfo.cols.info:set(rcomands[command]) + else + pktinfo.cols.info:set("CMD:" .. tostring(command)) + end prevCommand = command -- tell wireshark how much of tvbuff we dissected return pktlen From 402ba438d1739ebe95e35c6fbd93b9250af90512 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Fri, 27 Apr 2018 20:00:13 -0400 Subject: [PATCH 28/83] Added support for python3 (WIP) possible broken a lot of things still needs more safeguards --- test_machine.py | 120 ++++++++++---------- zk/__init__.py | 4 +- zk/base.py | 287 ++++++++++++++++++++++++------------------------ zk/finger.py | 4 +- zk/user.py | 2 +- 5 files changed, 213 insertions(+), 204 deletions(-) diff --git a/test_machine.py b/test_machine.py index c9846e3..4d3aa67 100755 --- a/test_machine.py +++ b/test_machine.py @@ -5,6 +5,7 @@ import traceback import argparse import time import datetime +from builtins import input sys.path.append("zk") @@ -45,42 +46,42 @@ args = parser.parse_args() zk = ZK(args.address, port=args.port, timeout=args.timeout, password=args.password, force_udp=args.force_udp) # , firmware=args.firmware try: - print 'Connecting to device ...' + print('Connecting to device ...') conn = zk.connect() - print 'SDK build=1 :', conn.set_sdk_build_1() # why? - print 'Disabling device ...' + print('SDK build=1 : %s' % conn.set_sdk_build_1()) # why? + print ('Disabling device ...') conn.disable_device() fmt = conn.get_extend_fmt() - print 'ExtendFmt : {}'.format(fmt) + print ('ExtendFmt : {}'.format(fmt)) fmt = conn.get_user_extend_fmt() - print 'UsrExtFmt : {}'.format(fmt) - print 'Face FunOn : {}'.format(conn.get_face_fun_on()) - print 'Face Version : {}'.format(conn.get_face_version()) - print 'Finger Version : {}'.format(conn.get_fp_version()) - print 'Old Firm compat : {}'.format(conn.get_compat_old_firmware()) + print ('UsrExtFmt : {}'.format(fmt)) + print ('Face FunOn : {}'.format(conn.get_face_fun_on())) + print ('Face Version : {}'.format(conn.get_face_version())) + print ('Finger Version : {}'.format(conn.get_fp_version())) + print ('Old Firm compat : {}'.format(conn.get_compat_old_firmware())) net = conn.get_network_params() - print 'IP:{} mask:{} gateway:{}'.format(net['ip'],net['mask'], net['gateway']) + print ('IP:{} mask:{} gateway:{}'.format(net['ip'],net['mask'], net['gateway'])) now = datetime.datetime.today().replace(microsecond=0) if args.updatetime: - print '--- Updating Time---' + print ('--- Updating Time---') conn.set_time(now) zk_time = conn.get_time() dif = abs(zk_time - now).total_seconds() - print 'Time : {}'.format(zk_time) + print ('Time : {}'.format(zk_time)) if dif > 120: print("WRN: TIME IS NOT SYNC!!!!!! (local: %s) use command -u to update" % now) - print 'Firmware Version : {}'.format(conn.get_firmware_version()) - print 'Platform : %s' % conn.get_platform() - print 'DeviceName : %s' % conn.get_device_name() - print 'Pin Width : %i' % conn.get_pin_width() - print 'Serial Number : %s' % conn.get_serialnumber() - print 'MAC: %s' % conn.get_mac() - print '' - print '--- sizes & capacity ---' + print ('Firmware Version : {}'.format(conn.get_firmware_version())) + print ('Platform : %s' % conn.get_platform()) + print ('DeviceName : %s' % conn.get_device_name()) + print ('Pin Width : %i' % conn.get_pin_width()) + print ('Serial Number : %s' % conn.get_serialnumber()) + print ('MAC: %s' % conn.get_mac()) + print ('') + print ('--- sizes & capacity ---') conn.read_sizes() - print conn - print '' - print '--- Get User ---' + print (conn) + print ('') + print ('--- Get User ---') users = conn.get_users() max_uid = 0 prev = None @@ -90,15 +91,15 @@ try: if user.uid > max_uid: max_uid = user.uid privilege = 'User' if user.privilege == const.USER_DEFAULT else 'Admin-%s' % user.privilege - print '-> UID #{:<5} Name : {:<27} Privilege : {}'.format(user.uid, user.name, privilege) - print ' Group ID : {:<8} User ID : {:<8} Password : {:<8} Card : {}'.format(user.group_id, user.user_id, user.password, user.card) - #print len (user.repack73()), user.repack73().encode('hex') - #print '' + print ('-> UID #{:<5} Name : {:<27} Privilege : {}'.format(user.uid, user.name, privilege)) + print (' Group ID : {:<8} User ID : {:<8} Password : {:<8} Card : {}'.format(user.group_id, user.user_id, user.password, user.card)) + #print (len (user.repack73()), user.repack73().encode('hex')) + #print ('') if args.adduser and user.uid == args.adduser: prev = user if args.deleteuser: - print '' - print '-- Delete User UID#%s ---' % args.deleteuser + print ('') + print ('-- Delete User UID#%s ---' % args.deleteuser) #TODO implementar luego conn.delete_user(args.deleteuser) users = conn.get_users() #update @@ -106,8 +107,8 @@ try: if user.uid > max_uid: max_uid = user.uid privilege = 'User' if user.privilege == const.USER_DEFAULT else 'Admin-%s' % user.privilege - print '-> UID #{:<5} Name : {:<27} Privilege : {}'.format(user.uid, user.name, privilege) - print ' Group ID : {:<8} User ID : {:<8} Password : {:<8} Card : {}'.format(user.group_id, user.user_id, user.password, user.card) + print ('-> UID #{:<5} Name : {:<27} Privilege : {}'.format(user.uid, user.name, privilege)) + print (' Group ID : {:<8} User ID : {:<8} Password : {:<8} Card : {}'.format(user.group_id, user.user_id, user.password, user.card)) #print len (user.repack73()), user.repack73().encode('hex') #print '' if args.adduser and user.uid == args.adduser: @@ -118,25 +119,26 @@ try: if prev: user = prev privilege = 'User' if user.privilege == const.USER_DEFAULT else 'Admin-%s' % user.privilege - print '' - print '--- Modify User %i ---' % user.uid - print '-> UID #{:<5} Name : {:<27} Privilege : {}'.format(user.uid, user.name, privilege) - print ' Group ID : {:<8} User ID : {:<8} Password : {:<8} Card : {}'.format(user.group_id, user.user_id, user.password, user.card) + print ('') + print ('--- Modify User %i ---' % user.uid) + print ('-> UID #{:<5} Name : {:<27} Privilege : {}'.format(user.uid, user.name, privilege)) + print (' Group ID : {:<8} User ID : {:<8} Password : {:<8} Card : {}'.format(user.group_id, user.user_id, user.password, user.card)) #discard prev else: - print '--- Add new User %i ---' % uid - name = raw_input('Name :') - admin = raw_input('Admin (y/n):') + print ('--- Add new User %i ---' % uid) + name = input('Name :') + admin = input('Admin (y/N):') privilege = 14 if admin == 'y' else 0 - password = raw_input('Password :') - user_id = raw_input('User ID2 :') - card = int(raw_input('Card :')) + password = input('Password :') + user_id = input('User ID2 :') + card = input('Card :') + card = int(card) if card else 0 if prev: conn.delete_user(uid) #borrado previo try: conn.set_user(uid, name, privilege, password, '', user_id, card) - except ZKErrorResponse, e: - print "error: ", e + except ZKErrorResponse as e: + print ("error: %s" % e) #try new format zk_user = User(uid, name, privilege, password, '', user_id, card) conn.save_user_template(zk_user) @@ -147,34 +149,34 @@ try: else: conn.test_voice(23) # not registered conn.refresh_data() - print "Voice Test ..." + print ("Voice Test ...") conn.test_voice(10) if args.templates: - print "Read Templates..." + print ("Read Templates...") templates = conn.get_templates() for tem in templates: - print tem + print (tem) if args.records: - print "Read Records..." + print ("Read Records...") attendance = conn.get_attendance() i = 0 for att in attendance: i +=1 - print "ATT {:>6}: uid:{:>3}, t: {}".format(i, att.uid, att.timestamp) - print '' - print '--- sizes & capacity ---' + print ("ATT {:>6}: uid:{:>3}, t: {}".format(i, att.uid, att.timestamp)) + print ('') + print ('--- sizes & capacity ---') conn.read_sizes() - print conn - print '' -except Exception, e: - print "Process terminate : {}".format(e) - print "Error: %s" % sys.exc_info()[0] - print '-'*60 + print (conn) + print ('') +except Exception as e: + print ("Process terminate : {}".format(e)) + print ("Error: %s" % sys.exc_info()[0]) + print ('-'*60) traceback.print_exc(file=sys.stdout) - print '-'*60 + print ('-'*60) finally: if conn: - print 'Enabling device ...' + print ('Enabling device ...') conn.enable_device() conn.disconnect() - print '' + print ('') diff --git a/zk/__init__.py b/zk/__init__.py index 94900e7..8f49c65 100644 --- a/zk/__init__.py +++ b/zk/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from base import ZK +from .base import ZK -VERSION = (0, 3) +VERSION = (0, 9) __all__ = ['ZK'] diff --git a/zk/base.py b/zk/base.py index 69e8ee0..d72c2a2 100644 --- a/zk/base.py +++ b/zk/base.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import sys +#from builtins import str from datetime import datetime from socket import AF_INET, SOCK_DGRAM, SOCK_STREAM, socket from struct import pack, unpack @@ -110,6 +111,7 @@ class ZK(object): self.__session_id = 0 self.__reply_id = const.USHRT_MAX-1 self.__data_recv = None + self.__data = None def __create_socket(self): """ based on self.tcp""" if self.tcp: @@ -167,7 +169,7 @@ class ZK(object): return pack('H', checksum) - def __send_command(self, command, command_string='', response_size=8): + def __send_command(self, command, command_string=b'', response_size=8): ''' send command to the terminal ''' @@ -186,12 +188,12 @@ class ZK(object): self.__sock.sendto(buf, self.__address) self.__data_recv = self.__sock.recv(response_size) self.__header = unpack('HHHH', self.__data_recv[:8]) - except Exception, e: + except Exception as e: raise ZKNetworkError(str(e)) self.__response = self.__header[0] self.__reply_id = self.__header[3] - + self.__data = self.__data_recv[8:] #could be empty if self.__response in [const.CMD_ACK_OK, const.CMD_PREPARE_DATA, const.CMD_DATA]: return { 'status': True, @@ -203,14 +205,14 @@ class ZK(object): } def __ack_ok(self): """ event ack ok """ - buf = self.__create_header(const.CMD_ACK_OK, "", self.__session_id, const.USHRT_MAX - 1) + buf = self.__create_header(const.CMD_ACK_OK, b'', self.__session_id, const.USHRT_MAX - 1) try: if self.tcp: top = self.__create_tcp_top(buf) self.__sock.send(top) else: self.__sock.sendto(buf, self.__address) - except Exception, e: + except Exception as e: raise ZKNetworkError(str(e)) @@ -221,7 +223,7 @@ class ZK(object): Returns the amount of bytes that are going to be sent""" response = self.__response if response == const.CMD_PREPARE_DATA: - size = unpack('I', self.__data_recv[8:12])[0] + size = unpack('I', self.__data[:4])[0] return size else: return 0 @@ -236,23 +238,26 @@ class ZK(object): """Decode a timestamp retrieved from the timeclock copied from zkemsdk.c - DecodeTime""" + """ t = t.encode('hex') t = int(self.__reverse_hex(t), 16) - #print "decode from %s "% format(t, '04x') + #print ("decode from %s "% format(t, '04x')) + """ + t = unpack("= 28: - uid, privilege, password, name, card, group_id, timezone, user_id = unpack('HB5s8s5sBhI',userdata.ljust(28)[:28]) - password = unicode(password.split('\x00')[0], errors='ignore') - name = unicode(name.split('\x00')[0], errors='ignore').strip() - card = unpack('Q', card.ljust(8,'\x00'))[0] #or hex value? + uid, privilege, password, name, card, group_id, timezone, user_id = unpack('= 72: - uid, privilege, password, name, separator, group_id, user_id = unpack('Hc8s24s4sx7sx24s', userdata.ljust(72)[:72]) + uid, privilege, password, name, card, group_id, user_id = unpack(' 16: #not empty - res = unpack("H", data_recv.ljust(24,"\x00")[16:18])[0] - print "res", res + res = unpack("H", data_recv.ljust(24,b"\x00")[16:18])[0] + print("res %i" % res) if res == 0 or res == 6 or res == 4: # 6 timeout, 4 mismatch error, 0 can't start(why?) print ("posible timeout o reg Fallido") break else: if len(data_recv) > 8: #not empty - res = unpack("H", data_recv.ljust(16,"\x00")[8:10])[0] - print "res", res + res = unpack("H", data_recv.ljust(16,b"\x00")[8:10])[0] + print("res %i" % res) if res == 6 or res == 4: print ("posible timeout o reg Fallido") break - print "A:%i esperando 2do regevent" % attempts + print ("A:%i esperando 2do regevent" % attempts) data_recv = self.__sock.recv(1032) # timeout? tarda bastante... self.__ack_ok() - print (data_recv).encode('hex') + #print ((data_recv).encode('hex')) if self.tcp: if len(data_recv) > 8: #not empty - res = unpack("H", data_recv.ljust(24,"\x00")[16:18])[0] - print "res", res + res = unpack("H", data_recv.ljust(24,b"\x00")[16:18])[0] + print("res %i" % res) if res == 6 or res == 4: print ("posible timeout o reg Fallido") break @@ -1029,8 +1034,8 @@ class ZK(object): attempts -= 1 else: if len(data_recv) > 8: #not empty - res = unpack("H", data_recv.ljust(16,"\x00")[8:10])[0] - print "res", res + res = unpack("H", data_recv.ljust(16,b"\x00")[8:10])[0] + print("res %i" % res) if res == 6 or res == 4: print ("posible timeout o reg Fallido") break @@ -1038,20 +1043,20 @@ class ZK(object): print ("ok, continue?") attempts -= 1 if attempts == 0: - print "esperando 3er regevent" + print ("esperando 3er regevent") data_recv = self.__sock.recv(1032) # timeout? tarda bastante... self.__ack_ok() - print (data_recv).encode('hex') + #print ((data_recv).encode('hex')) if self.tcp: - res = unpack("H", data_recv.ljust(24,"\x00")[16:18])[0] + res = unpack("H", data_recv.ljust(24,b"\x00")[16:18])[0] else: - res = unpack("H", data_recv.ljust(16,"\x00")[8:10])[0] + res = unpack("H", data_recv.ljust(16,b"\x00")[8:10])[0] if res == 6 or res == 4: print ("posible timeout o reg Fallido") if res == 0: - size = unpack("H", data_recv.ljust(16,"\x00")[10:12])[0] - pos = unpack("H", data_recv.ljust(16,"\x00")[12:14])[0] - print "enroll ok", size, pos + size = unpack("H", data_recv.ljust(16,b"\x00")[10:12])[0] + pos = unpack("H", data_recv.ljust(16,b"\x00")[12:14])[0] + print("enroll ok", size, pos) done = True self.__sock.settimeout(self.__timeout) self.cancel_capture() @@ -1081,27 +1086,27 @@ class ZK(object): #else if cmd_response.get('code') == const.CMD_DATA: # less than 1024!!! if self.tcp: - return self.__data_recv[16:] #TODO: check size? + return self.__data[8:] #TODO: check size? else: - return self.__data_recv[8:] + return self.__data if cmd_response.get('code') == const.CMD_PREPARE_DATA: data = [] bytes = self.__get_data_size() #TODO: check with size - #print "prepare data size is", bytes + #print ("prepare data size is", bytes) if self.tcp: data_recv = self.__sock.recv(bytes + 32) recieved = len(data_recv) tcp_length = unpack('HHI', data_recv[:8])[2] #bytes+8 if tcp_length < (bytes + 8): - print "request chunk too big!" + print ("request chunk too big!") response = unpack('HHHH', data_recv[8:16])[0] if recieved >= (bytes + 32): #complete if response == const.CMD_DATA: resp = data_recv[16:bytes+16] # no ack? - #print "resp len", len(resp) + #print ("resp len", len(resp)) return resp else: - print "broken packet!!!" + print("broken packet!!!") return '' #broken else: # incomplete data.append(data_recv[16:]) # w/o tcp and header @@ -1114,16 +1119,16 @@ class ZK(object): data_recv = self.__sock.recv(16) response = unpack('HHHH', data_recv[8:16])[0] if response == const.CMD_ACK_OK: - return ''.join(data) + return b''.join(data) #data_recv[bytes+16:].encode('hex') #included CMD_ACK_OK - print "bad response ", data_recv.encode('hex') + print("bad response %s" % data_recv) #print data return '' #else udp while True: #limitado por respuesta no por tamaƱo data_recv = self.__sock.recv(response_size) response = unpack('HHHH', data_recv[:8])[0] - #print "# %s packet response is: %s" % (pac, response) + #print ("# %s packet response is: %s" % (pac, response)) if response == const.CMD_DATA: data.append(data_recv[8:]) #header turncated bytes -= 1024 #UDP @@ -1131,10 +1136,10 @@ class ZK(object): break #without problem. else: #truncado! continuar? - print "broken!" + print ("broken!") break - #print "still needs %s" % bytes - return ''.join(data) + #print ("still needs %s" % bytes) + return b''.join(data) def read_with_buffer(self, command, fct=0 ,ext=0): """ Test read info with buffered command (ZK6: 1503) """ @@ -1143,7 +1148,7 @@ class ZK(object): else: MAX_CHUNK = 16 * 1024 command_string = pack('= 8: - uid, status, timestamp = unpack('HH4s', attendance_data.ljust(8)[:8]) + uid, status, timestamp = unpack('HH4s', attendance_data.ljust(8, b'\x00')[:8]) attendance_data = attendance_data[8:] user_id = str(uid) #TODO revisar posibles valores cruzar con userdata timestamp = self.__decode_time(timestamp) @@ -1192,7 +1197,7 @@ class ZK(object): attendances.append(attendance) elif record_size == 16: # extended while len(attendance_data) >= 16: - uid, timestamp, status, verified, reserved, workcode = unpack('= 40: - uid, user_id, sparator, timestamp, status, space = unpack('H24sc4sc8s', attendance_data.ljust(40)[:40]) - user_id = user_id.split('\x00')[0] + uid, user_id, sparator, timestamp, status, space = unpack(' 7sx group id, timezone? From dc4629a1bc3ed641048a5657be10c9cfd82a604e Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Mon, 30 Apr 2018 09:33:17 -0400 Subject: [PATCH 29/83] added future to install --- CHANGELOG.md | 17 +++++++++++++++++ setup.py | 1 + test.py | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6e9562..6af9311 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ Changelog ========= +Version 0.9 +----------- +* Initial Python 3 Support +* major changes + +Version 0.8 +----------- +* test suite tool (test_machine.py) +* Initial TCP support + +Version 0.7 +----------- +* internal major changes + +Version 0.6 +----------- +* device password support Version 0.5 ----------- diff --git a/setup.py b/setup.py index ae455e6..13ebd9b 100644 --- a/setup.py +++ b/setup.py @@ -19,5 +19,6 @@ setup( 'biometrics', 'security' ], + install_requires=['future'], zip_safe=False ) diff --git a/test.py b/test.py index cbac697..ab19bd7 100644 --- a/test.py +++ b/test.py @@ -6,7 +6,7 @@ from zk import ZK, const sys.path.append("zk") conn = None -zk = ZK('192.168.1.10', port=4370, timeout=5) +zk = ZK('192.168.1.201', port=4370, timeout=5) try: print 'Connecting to device ...' conn = zk.connect() From cdffacbfe202d6df528a9b4122bef5e5a8bf23bb Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Mon, 30 Apr 2018 10:25:43 -0400 Subject: [PATCH 30/83] complete docs and minor patches --- README.md | 64 +++++++++++++++++++++++++++++++++++++++++++++--- zk/attendance.py | 4 +-- zk/base.py | 34 ++++++++++++++++++++----- zk/finger.py | 4 +-- zk/user.py | 2 +- 5 files changed, 94 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index a2736c5..83772aa 100644 --- a/README.md +++ b/README.md @@ -67,22 +67,78 @@ conn.disable_device() conn.enable_device() ``` -* Ger Firmware Version +* Get and Set Time + +``` +from datetime import datetime + +zktime = conn.get_time() +print zktime + +newtime = datetime.today() +conn.set_time(newtime) +``` + + +* Ger Firmware Version and extra information ``` conn.get_firmware_version() +conn.get_serialnumber() +conn.get_platform() +conn.get_device_name() +conn.get_face_version() +conn.get_fp_version() +conn.get_extend_fmt() +conn.get_user_extend_fmt() +conn.get_face_fun_on() +conn.get_compat_old_firmware() +conn.get_network_params() +conn.get_mac() +conn.get_pin_width() +``` + +* Get Device use and free Space + +``` +conn.read_sizes() +print(conn) +#also: +conn.users +conn.fingers +conn.records +conn.users_cap +conn.fingers_cap +conn.records_cap ``` * User Operation ``` # Create user -conn.set_user(uid=1, name='Fanani M. Ihsan', privilege=const.USER_ADMIN, password='12345678', group_id='', user_id='123') +conn.set_user(uid=1, name='Fanani M. Ihsan', privilege=const.USER_ADMIN, password='12345678', group_id='', user_id='123', card=0) # Get all users (will return list of User object) users = conn.get_users() # Delete User conn.delete_user(uid=1) ``` +there is also an enroll_user() (but it doesn't work with some tcp ZK8 devices) + + +* Fingerprints + +``` +# Get a single Fingerprint (will return a Finger object) +template = conn.get_user_template(uid=1, temp_id=0) #temp_id is the finger to read 0~9 +# Get all fingers from DB (will return a list of Finger objects) +fingers = conn.get_templates() + +# to restore a finger, we need to assemble with the corresponding user +# pass a User object and a list of finger (max 10) to save + +conn.save_user_template(user, [fing1 ,fing2]) +``` + * Attendance Record ``` @@ -95,7 +151,7 @@ conn.clear_attendance() * Test voice ``` -conn.test_voice() +conn.test_voice(index=10) # beep or chirp ``` * Device Maintenance @@ -105,6 +161,8 @@ conn.test_voice() conn.power_off() # restart connected device conn.restart() +# clear buffer +conn.free_data() ``` # Related Project diff --git a/zk/attendance.py b/zk/attendance.py index 790f721..1fbb9d1 100644 --- a/zk/attendance.py +++ b/zk/attendance.py @@ -7,7 +7,7 @@ class Attendance(object): self.status = status def __str__(self): - return ': {}'.format(self.user_id) + return ': {}:{}'.format(self.user_id, self.timestamp) def __repr__(self): - return ': {}'.format(self.user_id) + return ': {}:{}'.format(self.user_id, self.timestamp) diff --git a/zk/base.py b/zk/base.py index d72c2a2..c40b3e1 100644 --- a/zk/base.py +++ b/zk/base.py @@ -45,6 +45,7 @@ def make_commkey(key, session_id, ticks=50): B, k[3] ^ B) return k + class ZK_helper(object): """ helper class """ def __init__(self, ip, port=4370): @@ -55,6 +56,7 @@ class ZK_helper(object): #self.password = password # passint #self.firmware = int(firmware) #TODO check minor version? #self.tcp = tcp + def test_ping(self): """ Returns True if host responds to a ping request @@ -70,15 +72,18 @@ class ZK_helper(object): stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=need_sh) == 0 + def test_tcp(self): self.client = socket(AF_INET, SOCK_STREAM) self.client.settimeout(10) # fixed test res = self.client.connect_ex(self.address) self.client.close() return res + def test_udp(self): self.client = socket(AF_INET, SOCK_DGRAM) self.client.settimeout(10) # fixed test + class ZK(object): """ Clase ZK """ def __init__(self, ip, port=4370, timeout=60, password=0, force_udp=False, ommit_ping=False): @@ -112,6 +117,7 @@ class ZK(object): self.__reply_id = const.USHRT_MAX-1 self.__data_recv = None self.__data = None + def __create_socket(self): """ based on self.tcp""" if self.tcp: @@ -119,13 +125,13 @@ class ZK(object): self.__sock.connect_ex(self.__address) else: self.__sock = socket(AF_INET, SOCK_DGRAM) - - + def __create_tcp_top(self, packet): """ witch the complete packet set top header """ length = len(packet) top = pack('= 1: + uid = users[0].uid #only one + else: # posibly empty + return False #can't enroll command_string = pack('hhb', int(uid), 0, temp_id) # el 0 es misterio self.cancel_capture() cmd_response = self.__send_command(command, command_string) diff --git a/zk/finger.py b/zk/finger.py index 4749576..1cfa264 100644 --- a/zk/finger.py +++ b/zk/finger.py @@ -17,7 +17,7 @@ class Finger(object): return pack("H%is" % (self.size), self.size+2, self.template) def __str__(self): - return " [u:%i, fid:%i, size:%i v:%i t:%s...]" % (self.uid, self.fid, self.size, self.valid, self.mark) + return " [uid:%i, fid:%i, size:%i v:%i t:%s...]" % (self.uid, self.fid, self.size, self.valid, self.mark) def __repr__(self): - return " [u:%i, fid:%i, size:%i v:%i t:%s...]" % (self.uid, self.fid, self.size, self.valid, self.mark) #.encode('hex') + return " [uid:%i, fid:%i, size:%i v:%i t:%s...]" % (self.uid, self.fid, self.size, self.valid, self.mark) #.encode('hex') diff --git a/zk/user.py b/zk/user.py index aecef39..265953f 100644 --- a/zk/user.py +++ b/zk/user.py @@ -20,4 +20,4 @@ class User(object): return ': [uid:{}, name:{} user_id:{}]'.format(self.uid, self.name, self.user_id) def __repr__(self): - return ': {}'.format(self.name) + return ': [uid:{}, name:{} user_id:{}]'.format(self.uid, self.name, self.user_id) From 65aa73d7e5647b3e82cfed662b32dcfea35c7bf4 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Mon, 30 Apr 2018 10:32:10 -0400 Subject: [PATCH 31/83] fix setup.py version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 13ebd9b..38209cb 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import setup setup( name='pyzk', - version='0.5', + version='0.9', description='an unofficial library of zksoftware fingerprint device', url='https://github.com/fananimi/pyzk', author='Fanani M. Ihsan', From 9f0267b107dc4dabbff459af7401b4473ce917a1 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Wed, 2 May 2018 15:12:08 -0400 Subject: [PATCH 32/83] fix enroll_user (keeps using uid, but now it searchs for user_id first) testing live capture! --- test_machine.py | 35 ++++++++++-- zk/base.py | 140 +++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 147 insertions(+), 28 deletions(-) diff --git a/test_machine.py b/test_machine.py index 4d3aa67..31a325d 100755 --- a/test_machine.py +++ b/test_machine.py @@ -35,10 +35,14 @@ parser.add_argument('-r', '--records', action="store_true", help='get records') parser.add_argument('-u', '--updatetime', action="store_true", help='Update Date / Time') +parser.add_argument('-l', '--live-capture', action="store_true", + help='Live Event Capture') parser.add_argument('-D', '--deleteuser', type=int, - help='Delete a User', default=0) + help='Delete a User (uid)', default=0) parser.add_argument('-A', '--adduser', type=int, - help='Add a User', default=0) + help='Add a User (uid)', default=0) +parser.add_argument('-E', '--enrolluser', type=int, + help='Enroll a User (uid)', default=0) parser.add_argument('-F', '--finger', type=int, help='Finger for register', default=0) @@ -137,15 +141,23 @@ try: conn.delete_user(uid) #borrado previo try: conn.set_user(uid, name, privilege, password, '', user_id, card) + args.enrolluser = uid except ZKErrorResponse as e: print ("error: %s" % e) #try new format zk_user = User(uid, name, privilege, password, '', user_id, card) - conn.save_user_template(zk_user) + conn.save_user_template(zk_user)# forced creation + args.enrolluser = uid + conn.refresh_data() + if args.enrolluser: + uid = int(args.enrolluser) + print ('--- Enrolling User #{} ---'.format(uid)) conn.delete_user_template(uid, args.finger) conn.reg_event(0xFFFF) # if conn.enroll_user(uid, args.finger): conn.test_voice(18) # register ok + tem = conn.get_user_template(uid, args.finger) + print (tem) else: conn.test_voice(23) # not registered conn.refresh_data() @@ -162,11 +174,25 @@ try: i = 0 for att in attendance: i +=1 - print ("ATT {:>6}: uid:{:>3}, t: {}".format(i, att.uid, att.timestamp)) + print ("ATT {:>6}: uid:{:>3}, user_id:{:>8} t: {}, s:{}".format(i, att.uid, att.user_id, att.timestamp, att.status)) print ('') print ('--- sizes & capacity ---') conn.read_sizes() print (conn) + if args.live_capture: + print ('') + print ('--- Live Capture! (press ctrl+C to break)---') #TODO how? + counter = 0 + for att in conn.live_capture(): + if att is None: + #counter += 1 + print ("timeout {}".format(counter)) + else: + print ("ATT {:>6}: uid:{:>3}, user_id:{:>8} t: {}, s:{}".format(counter, att.uid, att.user_id, att.timestamp, att.status)) + if counter >= 10: + conn.end_live_capture = True + print('') + print('--- capture End!---') print ('') except Exception as e: print ("Process terminate : {}".format(e)) @@ -179,4 +205,5 @@ finally: print ('Enabling device ...') conn.enable_device() conn.disconnect() + print ('ok bye!') print ('') diff --git a/zk/base.py b/zk/base.py index c40b3e1..f7badf0 100644 --- a/zk/base.py +++ b/zk/base.py @@ -2,7 +2,7 @@ import sys #from builtins import str from datetime import datetime -from socket import AF_INET, SOCK_DGRAM, SOCK_STREAM, socket +from socket import AF_INET, SOCK_DGRAM, SOCK_STREAM, socket, timeout from struct import pack, unpack from zk import const @@ -89,6 +89,7 @@ class ZK(object): def __init__(self, ip, port=4370, timeout=60, password=0, force_udp=False, ommit_ping=False): """ initialize instance """ self.is_connect = False + self.is_enabled = True #let's asume self.helper = ZK_helper(ip, port) self.__address = (ip, port) self.__sock = socket(AF_INET, SOCK_DGRAM) @@ -186,7 +187,7 @@ class ZK(object): top = self.__create_tcp_top(buf) self.__sock.send(top) self.__tcp_data_recv = self.__sock.recv(response_size + 8) - self.__tcp_header = unpack('HHI', self.__tcp_data_recv[:8]) + self.__tcp_header = unpack('= 1: + user_id = users[0].user_id + else: #double? posibly empty + return False #can't enroll if self.tcp: - if not user_id: - #we need user_id (uid2) - users = self.get_users() - users = filter(lambda x: x.uid==uid, users) - if len(users) == 1: - user_id = users[0].user_id - else: #double? posibly empty - return False #can't enroll command_string = pack('<24sbb',str(user_id), temp_id, 1) # el 1 es misterio else: - if not uid: - #we need uid - users = self.get_users() - users = filter(lambda x: x.user_id==user_id, users) - if len(users) >= 1: - uid = users[0].uid #only one - else: # posibly empty - return False #can't enroll - command_string = pack('hhb', int(uid), 0, temp_id) # el 0 es misterio + command_string = pack('= 8: uid, status, timestamp = unpack('HH4s', attendance_data.ljust(8, b'\x00')[:8]) + #print attendance_data[:8].encode('hex') attendance_data = attendance_data[8:] - user_id = str(uid) #TODO revisar posibles valores cruzar con userdata + tuser = filter(lambda x: x.uid == uid, users) + if not tuser: + user_id = str(uid) #TODO revisar pq + else: + user_id = tuser[0].user_id timestamp = self.__decode_time(timestamp) attendance = Attendance(uid, user_id, timestamp, status) attendances.append(attendance) elif record_size == 16: # extended while len(attendance_data) >= 16: - uid, timestamp, status, verified, reserved, workcode = unpack('= 40: uid, user_id, sparator, timestamp, status, space = unpack(' Date: Wed, 2 May 2018 17:09:33 -0400 Subject: [PATCH 33/83] added verbose mode (and fixed print statements for python3) better tcp and python3 support (still todo test --- zk/base.py | 205 ++++++++++++++++++++++++++++++++------------------- zk/finger.py | 4 +- 2 files changed, 131 insertions(+), 78 deletions(-) diff --git a/zk/base.py b/zk/base.py index f7badf0..89a54e2 100644 --- a/zk/base.py +++ b/zk/base.py @@ -86,7 +86,7 @@ class ZK_helper(object): class ZK(object): """ Clase ZK """ - def __init__(self, ip, port=4370, timeout=60, password=0, force_udp=False, ommit_ping=False): + def __init__(self, ip, port=4370, timeout=60, password=0, force_udp=False, ommit_ping=False, verbose=False): """ initialize instance """ self.is_connect = False self.is_enabled = True #let's asume @@ -99,6 +99,7 @@ class ZK(object): #self.firmware = int(firmware) #dummy self.force_udp = force_udp self.ommit_ping = ommit_ping + self.verbose = verbose self.tcp = False self.users = 0 self.fingers = 0 @@ -248,7 +249,7 @@ class ZK(object): """ t = t.encode('hex') t = int(self.__reverse_hex(t), 16) - #print ("decode from %s "% format(t, '04x')) + if self.verbose: print ("decode from %s "% format(t, '04x')) """ t = unpack("= (bytes + 32): #complete + if response == const.CMD_DATA: + resp = data_recv[16:bytes+16] # no ack? + if self.verbose: print ("resp len", len(resp)) + return Finger(uid, temp_id, 1, resp) + else: + if self.verbose: print("broken packet!!!") + return None #broken + else: # incomplete + data.append(data_recv[16:]) # w/o tcp and header + bytes -= recieved-16 + while bytes>0: #jic + data_recv = self.__sock.recv(bytes) #ideal limit? + recieved = len(data_recv) + data.append(data_recv) # w/o tcp and header + bytes -= recieved + data_recv = self.__sock.recv(16) + response = unpack('HHHH', data_recv[8:16])[0] + if response == const.CMD_ACK_OK: + resp = b''.join(data) + return Finger(uid, temp_id, 1, resp) + #data_recv[bytes+16:].encode('hex') #included CMD_ACK_OK + if self.verbose: print("bad response %s" % data_recv) + if self.verbose: print(data) + return None + #else udp size = bytes while True: #limitado por respuesta no por tamaƱo - data_recv = self.__sock.recv(1032) + data_recv = self.__sock.recv(response_size) response = unpack('HHHH', data_recv[:8])[0] - #print("# %s packet response is: %s" % (pac, response)) + if self.verbose: print("# %s packet response is: %s" % (pac, response)) if response == const.CMD_DATA: data.append(data_recv[8:]) #header turncated bytes -= 1024 @@ -882,19 +932,20 @@ class ZK(object): break #without problem. else: #truncado! continuar? - #print("broken!") + if self.verbose: print("broken!") break - #print("still needs %s" % bytes) + if self.verbose: print("still needs %s" % bytes) data = b''.join(data) + self.free_data() #uid 32 fing 03, starts with 4d-9b-53-53-32-31 - return Finger(size, uid, temp_id, 1, data) + return Finger(uid, temp_id, 1, data) def get_templates(self): """ return array of all fingers """ templates = [] templatedata, size = self.read_with_buffer(const.CMD_DB_RRQ, const.FCT_FINGERTMP) if size < 4: - print("WRN: no user data") # debug + if self.verbose: print("WRN: no user data") # debug return [] total_size = unpack('i', templatedata[0:4])[0] templatedata = templatedata[4:] #total size not used @@ -902,8 +953,8 @@ class ZK(object): while total_size: size, uid, fid, valid = unpack('HHbb',templatedata[:6]) template = unpack("%is" % (size-6), templatedata[6:size])[0] - finger = Finger(size, uid, fid, valid, template) - #print(finger) # test + finger = Finger(uid, fid, valid, template) + if self.verbose: print(finger) # test templates.append(finger) templatedata = templatedata[size:] total_size -= size @@ -916,14 +967,14 @@ class ZK(object): return [] users = [] userdata, size = self.read_with_buffer(const.CMD_USERTEMP_RRQ, const.FCT_USER) - #print("user size %i" % size) + if self.verbose: print("user size %i" % size) if size <= 4: - print("WRN: no user data")# debug + if self.verbose: print("WRN: no user data")# debug return [] total_size = unpack("I",userdata[:4])[0] self.user_packet_size = total_size / self.users if not self.user_packet_size in [28, 72]: - print("WRN packet size would be %i" % self.user_packet_size) + if self.verbose: print("WRN packet size would be %i" % self.user_packet_size) userdata = userdata[4:] if self.user_packet_size == 28: while len(userdata) >= 28: @@ -938,7 +989,7 @@ class ZK(object): name = "NN-%s" % user_id user = User(uid, name, privilege, password, group_id, user_id, card) users.append(user) - #print("[6]user:",uid, privilege, password, name, card, group_id, timezone, user_id) + if self.verbose: print("[6]user:",uid, privilege, password, name, card, group_id, timezone, user_id) userdata = userdata[28:] else: while len(userdata) >= 72: @@ -1006,13 +1057,13 @@ class ZK(object): if not user_id: #we need user_id (uid2) users = self.get_users() - users = filter(lambda x: x.uid==uid, users) + users = list(filter(lambda x: x.uid==uid, users)) if len(users) >= 1: user_id = users[0].user_id else: #double? posibly empty return False #can't enroll if self.tcp: - command_string = pack('<24sbb',str(user_id), temp_id, 1) # el 1 es misterio + command_string = pack('<24sbb',str(user_id).encode(), temp_id, 1) # el 1 es misterio else: command_string = pack(' 16: #not empty res = unpack("H", data_recv.ljust(24,b"\x00")[16:18])[0] - print("res %i" % res) + if self.verbose: print("res %i" % res) if res == 0 or res == 6 or res == 4: # 6 timeout, 4 mismatch error, 0 can't start(why?) - print ("posible timeout o reg Fallido") + if self.verbose: print ("posible timeout o reg Fallido") break else: if len(data_recv) > 8: #not empty res = unpack("H", data_recv.ljust(16,b"\x00")[8:10])[0] - print("res %i" % res) + if self.verbose: print("res %i" % res) if res == 6 or res == 4: - print ("posible timeout o reg Fallido") + if self.verbose: print ("posible timeout o reg Fallido") break - print ("A:%i esperando 2do regevent" % attempts) + if self.verbose: print ("A:%i esperando 2do regevent" % attempts) data_recv = self.__sock.recv(1032) # timeout? tarda bastante... self.__ack_ok() - #print ((data_recv).encode('hex')) + if self.verbose: print ((data_recv).encode('hex')) if self.tcp: if len(data_recv) > 8: #not empty res = unpack("H", data_recv.ljust(24,b"\x00")[16:18])[0] - print("res %i" % res) + if self.verbose: print("res %i" % res) if res == 6 or res == 4: - print ("posible timeout o reg Fallido") + if self.verbose: print ("posible timeout o reg Fallido") break elif res == 0x64: - print ("ok, continue?") + if self.verbose: print ("ok, continue?") attempts -= 1 else: if len(data_recv) > 8: #not empty res = unpack("H", data_recv.ljust(16,b"\x00")[8:10])[0] - print("res %i" % res) + if self.verbose: print("res %i" % res) if res == 6 or res == 4: - print ("posible timeout o reg Fallido") + if self.verbose: print ("posible timeout o reg Fallido") break elif res == 0x64: - print ("ok, continue?") + if self.verbose: print ("ok, continue?") attempts -= 1 if attempts == 0: - print ("esperando 3er regevent") + if self.verbose: print ("esperando 3er regevent") data_recv = self.__sock.recv(1032) # timeout? tarda bastante... self.__ack_ok() - #print ((data_recv).encode('hex')) + if self.verbose: print ((data_recv).encode('hex')) if self.tcp: res = unpack("H", data_recv.ljust(24,b"\x00")[16:18])[0] else: res = unpack("H", data_recv.ljust(16,b"\x00")[8:10])[0] - print("res %i" % res) + if self.verbose: print("res %i" % res) if res == 5: - print ("huella duplicada") + if self.verbose: print ("huella duplicada") if res == 6 or res == 4: - print ("posible timeout o reg Fallido") + if self.verbose: print ("posible timeout o reg Fallido") if res == 0: size = unpack("H", data_recv.ljust(16,b"\x00")[10:12])[0] pos = unpack("H", data_recv.ljust(16,b"\x00")[12:14])[0] - print("enroll ok", size, pos) + if self.verbose: print("enroll ok", size, pos) done = True self.__sock.settimeout(self.__timeout) + self.reg_event(0) # TODO: test self.cancel_capture() self.verify_user() return done @@ -1103,7 +1155,7 @@ class ZK(object): self.end_live_capture = False while not self.end_live_capture: try: - print ("esperando event") + if self.verbose: print ("esperando event") data_recv = self.__sock.recv(1032) self.__ack_ok() if self.tcp: @@ -1115,10 +1167,10 @@ class ZK(object): header = unpack('HHHH', data_recv[:8]) data = data_recv[8:] if not header[0] == const.CMD_REG_EVENT: - print("not event!") + if self.verbose: print("not event!") continue # or raise error? if not len(data): - print ("empty") + if self.verbose: print ("empty") continue """if len (data) == 5: @@ -1126,7 +1178,7 @@ class ZK(object): if len(data) == 12: #class 1 attendance user_id, status, match, timehex = unpack('= (bytes + 32): #complete if response == const.CMD_DATA: resp = data_recv[16:bytes+16] # no ack? - #print ("resp len", len(resp)) + if self.verbose: print ("resp len", len(resp)) return resp else: - print("broken packet!!!") + if self.verbose: print("broken packet!!!") return '' #broken else: # incomplete data.append(data_recv[16:]) # w/o tcp and header @@ -1215,14 +1268,14 @@ class ZK(object): if response == const.CMD_ACK_OK: return b''.join(data) #data_recv[bytes+16:].encode('hex') #included CMD_ACK_OK - print("bad response %s" % data_recv) - #print data + if self.verbose: print("bad response %s" % data_recv) + if self.verbose: print (data) return '' #else udp while True: #limitado por respuesta no por tamaƱo data_recv = self.__sock.recv(response_size) response = unpack('HHHH', data_recv[:8])[0] - #print ("# %s packet response is: %s" % (pac, response)) + if self.verbose: print ("# %s packet response is: %s" % (pac, response)) if response == const.CMD_DATA: data.append(data_recv[8:]) #header turncated bytes -= 1024 #UDP @@ -1230,9 +1283,9 @@ class ZK(object): break #without problem. else: #truncado! continuar? - print ("broken!") + if self.verbose: print ("broken!") break - #print ("still needs %s" % bytes) + if self.verbose: print ("still needs %s" % bytes) return b''.join(data) def read_with_buffer(self, command, fct=0 ,ext=0): @@ -1242,7 +1295,7 @@ class ZK(object): else: MAX_CHUNK = 16 * 1024 command_string = pack('= 8: uid, status, timestamp = unpack('HH4s', attendance_data.ljust(8, b'\x00')[:8]) - #print attendance_data[:8].encode('hex') + if self.verbose: print (codecs.encode(attendance_data[:8], 'hex')) attendance_data = attendance_data[8:] - tuser = filter(lambda x: x.uid == uid, users) + tuser = list(filter(lambda x: x.uid == uid, users)) if not tuser: user_id = str(uid) #TODO revisar pq else: @@ -1299,13 +1352,13 @@ class ZK(object): elif record_size == 16: # extended while len(attendance_data) >= 16: user_id, timestamp, status, verified, reserved, workcode = unpack('= 40: uid, user_id, sparator, timestamp, status, space = unpack(' Date: Wed, 2 May 2018 18:18:05 -0400 Subject: [PATCH 34/83] fixed test_voice for python3 --- test_voice.py | 20 ++++++++++---------- zk/base.py | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/test_voice.py b/test_voice.py index 106ba73..293c3c8 100755 --- a/test_voice.py +++ b/test_voice.py @@ -16,28 +16,28 @@ parser.add_argument('-T', '--timeout', type=int, help='timeout [60]', default=60) parser.add_argument('-P', '--password', type=int, help='Device code/password', default=0) -parser.add_argument('-f', '--firmware', type=int, - help='test firmware', default=8) +parser.add_argument('-f', '--force-udp', action="store_true", + help='Force UDP communication') args = parser.parse_args() conn = None -zk = ZK(args.address, port=args.port, timeout=args.timeout, password=args.password, firmware=args.firmware) +zk = ZK(args.address, port=args.port, timeout=args.timeout, password=args.password, force_udp=args.force_udp) try: - print 'Connecting to device ...' + print ('Connecting to device ...') conn = zk.connect() - print 'Disabling device ...' + print ('Disabling device ...') conn.disable_device() - print 'Firmware Version: : {}'.format(conn.get_firmware_version()) + print ('Firmware Version: : {}'.format(conn.get_firmware_version())) for i in range(0,65): - print "test_voice, %i" % i + print ("test_voice, %i" % i) zk.test_voice(i) sleep(3) - print 'Enabling device ...' + print ('Enabling device ...') conn.enable_device() -except Exception, e: - print "Process terminate : {}".format(e) +except Exception as e: + print ("Process terminate : {}".format(e)) finally: if conn: conn.disconnect() diff --git a/zk/base.py b/zk/base.py index 89a54e2..c0814e3 100644 --- a/zk/base.py +++ b/zk/base.py @@ -663,10 +663,10 @@ class ZK(object): 7 memoria de alm fich llena /- 8 huella duplicada / huella duplicada 9 acceso denegado / ya ha sido registrado - 10 *beep* / beep - 11 el sistema vuelve al modo de verificacion / beep + 10 *beep* / beep kuko + 11 el sistema vuelve al modo de verificacion / beep siren 12 por favor coloque su dedo o acerque tarjeta /- - 13 acerca su tarjeta de nuevo /beep + 13 acerca su tarjeta de nuevo /beep bell 14 excedido tiempo p esta operacion /- 15 coloque su dedo de nuevo /- 16 coloque su dedo por ultima vez /- From 66448cef1811376fa510f6126fb8a4c958f794ca Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Thu, 3 May 2018 15:58:52 -0400 Subject: [PATCH 35/83] fixed reading tcp bug --- README.md | 81 +++++++++++++++++++++++++++++++++++-------------- test.py | 31 +++++++++---------- test_machine.py | 33 ++++++++++++-------- test_voice.py | 8 +++-- zk/base.py | 64 ++++++++++++++++++++------------------ zk/finger.py | 12 +++++--- 6 files changed, 140 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index 83772aa..934447e 100644 --- a/README.md +++ b/README.md @@ -17,17 +17,17 @@ Complete documentation can be found at [Readthedocs](http://pyzk.readthedocs.io/ Just create a ZK object and you will ready to call api. * Basic Usage -``` +```python from zk import ZK, const conn = None -zk = ZK('192.168.1.10', port=4370, timeout=5) +zk = ZK('192.168.1.201', port=4370, timeout=5) try: - print 'Connecting to device ...' + print ('Connecting to device ...') conn = zk.connect() - print 'Disabling device ...' + print ('Disabling device ...') conn.disable_device() - print 'Firmware Version: : {}'.format(conn.get_firmware_version()) + print ('Firmware Version: : {}'.format(conn.get_firmware_version())) # print '--- Get User ---' users = conn.get_users() for user in users: @@ -35,19 +35,19 @@ try: if user.privilege == const.USER_ADMIN: privilege = 'Admin' - print '- UID #{}'.format(user.uid) - print ' Name : {}'.format(user.name) - print ' Privilege : {}'.format(privilege) - print ' Password : {}'.format(user.password) - print ' Group ID : {}'.format(user.group_id) - print ' User ID : {}'.format(user.user_id) + print ('- UID #{}'.format(user.uid)) + print (' Name : {}'.format(user.name)) + print (' Privilege : {}'.format(privilege)) + print (' Password : {}'.format(user.password)) + print (' Group ID : {}'.format(user.group_id)) + print (' User ID : {}'.format(user.user_id)) - print "Voice Test ..." + print ("Voice Test ...") conn.test_voice() - print 'Enabling device ...' + print ('Enabling device ...') conn.enable_device() -except Exception, e: - print "Process terminate : {}".format(e) +except Exception as e: + print ("Process terminate : {}".format(e)) finally: if conn: conn.disconnect() @@ -100,7 +100,7 @@ conn.get_pin_width() * Get Device use and free Space -``` +```python conn.read_sizes() print(conn) #also: @@ -114,7 +114,7 @@ conn.records_cap * User Operation -``` +```python # Create user conn.set_user(uid=1, name='Fanani M. Ihsan', privilege=const.USER_ADMIN, password='12345678', group_id='', user_id='123', card=0) # Get all users (will return list of User object) @@ -122,12 +122,12 @@ users = conn.get_users() # Delete User conn.delete_user(uid=1) ``` -there is also an enroll_user() (but it doesn't work with some tcp ZK8 devices) +there is also an `enroll_user()` (but it doesn't work with some tcp ZK8 devices) * Fingerprints -``` +```python # Get a single Fingerprint (will return a Finger object) template = conn.get_user_template(uid=1, temp_id=0) #temp_id is the finger to read 0~9 # Get all fingers from DB (will return a list of Finger objects) @@ -141,7 +141,7 @@ conn.save_user_template(user, [fing1 ,fing2]) * Attendance Record -``` +```python # Get attendances (will return list of Attendance object) attendances = conn.get_attendance() # Clear attendances record @@ -150,13 +150,13 @@ conn.clear_attendance() * Test voice -``` +```python conn.test_voice(index=10) # beep or chirp ``` * Device Maintenance -``` +```python # shutdown connected device conn.power_off() # restart connected device @@ -165,6 +165,43 @@ conn.restart() conn.free_data() ``` +Test Machine + +```sh +usage: ./test_machine.py [-h] [-a ADDRESS] [-p PORT] [-T TIMEOUT] [-P PASSWORD] + [-f] [-t] [-r] [-u] [-l] [-D DELETEUSER] [-A ADDUSER] + [-E ENROLLUSER] [-F FINGER] + +ZK Basic Reading Tests + +optional arguments: + -h, --help show this help message and exit + -a ADDRESS, --address ADDRESS + ZK device Address [192.168.1.201] + -p PORT, --port PORT ZK device port [4370] + -T TIMEOUT, --timeout TIMEOUT + Default [10] seconds (0: disable timeout) + -P PASSWORD, --password PASSWORD + Device code/password + -f, --force-udp Force UDP communication + -t, --templates Get templates / fingers + -r, --records Get attendance records + -u, --updatetime Update Date/Time + -l, --live-capture Live Event Capture + -D DELETEUSER, --deleteuser DELETEUSER + Delete a User (uid) + -A ADDUSER, --adduser ADDUSER + Add a User (uid) (and enroll) + -E ENROLLUSER, --enrolluser ENROLLUSER + Enroll a User (uid) + -F FINGER, --finger FINGER + Finger for enroll (fid=0) + + +``` + + + # Related Project * [zkcluster](https://github.com/fananimi/zkcluster/ "zkcluster project") is a django apps to manage multiple fingerprint devices. diff --git a/test.py b/test.py index ab19bd7..d5f1f75 100644 --- a/test.py +++ b/test.py @@ -1,39 +1,36 @@ # -*- coding: utf-8 -*- import sys +sys.path.append("zk") from zk import ZK, const -sys.path.append("zk") - conn = None zk = ZK('192.168.1.201', port=4370, timeout=5) try: - print 'Connecting to device ...' + print ('Connecting to device ...') conn = zk.connect() - print 'Disabling device ...' + print ('Disabling device ...') conn.disable_device() - print 'Firmware Version: : {}'.format(conn.get_firmware_version()) + print ('Firmware Version: : {}'.format(conn.get_firmware_version())) # print '--- Get User ---' users = conn.get_users() for user in users: privilege = 'User' if user.privilege == const.USER_ADMIN: privilege = 'Admin' + print ('- UID #{}'.format(user.uid)) + print (' Name : {}'.format(user.name)) + print (' Privilege : {}'.format(privilege)) + print (' Password : {}'.format(user.password)) + print (' Group ID : {}'.format(user.group_id)) + print (' User ID : {}'.format(user.user_id)) - print '- UID #{}'.format(user.uid) - print ' Name : {}'.format(user.name) - print ' Privilege : {}'.format(privilege) - print ' Password : {}'.format(user.password) - print ' Group ID : {}'.format(user.group_id) - print ' User ID : {}'.format(user.user_id) - print ' Card : {}'.format(user.card) - - print "Voice Test ..." + print ("Voice Test ...") conn.test_voice() - print 'Enabling device ...' + print ('Enabling device ...') conn.enable_device() -except Exception, e: - print "Process terminate : {}".format(e) +except Exception as e: + print ("Process terminate : {}".format(e)) finally: if conn: conn.disconnect() diff --git a/test_machine.py b/test_machine.py index 31a325d..d260a84 100755 --- a/test_machine.py +++ b/test_machine.py @@ -20,35 +20,37 @@ conn = None parser = argparse.ArgumentParser(description='ZK Basic Reading Tests') parser.add_argument('-a', '--address', - help='ZK device Addres [192.168.1.201]', default='192.168.1.201') + help='ZK device Address [192.168.1.201]', default='192.168.1.201') parser.add_argument('-p', '--port', type=int, - help='device port', default=4370) + help='ZK device port [4370]', default=4370) parser.add_argument('-T', '--timeout', type=int, - help='timeout', default=10) + help='Default [10] seconds (0: disable timeout)', default=10) parser.add_argument('-P', '--password', type=int, help='Device code/password', default=0) parser.add_argument('-f', '--force-udp', action="store_true", help='Force UDP communication') +parser.add_argument('-v', '--verbose', action="store_true", + help='Print debug information') parser.add_argument('-t', '--templates', action="store_true", - help='get templates') + help='Get templates / fingers') parser.add_argument('-r', '--records', action="store_true", - help='get records') + help='Get attendance records') parser.add_argument('-u', '--updatetime', action="store_true", - help='Update Date / Time') + help='Update Date/Time') parser.add_argument('-l', '--live-capture', action="store_true", help='Live Event Capture') parser.add_argument('-D', '--deleteuser', type=int, help='Delete a User (uid)', default=0) parser.add_argument('-A', '--adduser', type=int, - help='Add a User (uid)', default=0) + help='Add a User (uid) (and enroll)', default=0) parser.add_argument('-E', '--enrolluser', type=int, help='Enroll a User (uid)', default=0) parser.add_argument('-F', '--finger', type=int, - help='Finger for register', default=0) + help='Finger for enroll (fid=0)', default=0) args = parser.parse_args() -zk = ZK(args.address, port=args.port, timeout=args.timeout, password=args.password, force_udp=args.force_udp) # , firmware=args.firmware +zk = ZK(args.address, port=args.port, timeout=args.timeout, password=args.password, force_udp=args.force_udp, verbose=args.verbose) try: print('Connecting to device ...') conn = zk.connect() @@ -167,7 +169,12 @@ try: print ("Read Templates...") templates = conn.get_templates() for tem in templates: - print (tem) + tem2 =conn.get_user_template(tem.uid,tem.fid) + if tem == tem2: # compare with alternative method + print ("OK! %s" % tem) + else: + print ("dif-1 %s" % tem) + print ("dif-2 %s" % tem2) if args.records: print ("Read Records...") attendance = conn.get_attendance() @@ -181,11 +188,11 @@ try: print (conn) if args.live_capture: print ('') - print ('--- Live Capture! (press ctrl+C to break)---') #TODO how? + print ('--- Live Capture! (press ctrl+C to break) ---') counter = 0 - for att in conn.live_capture(): + for att in conn.live_capture():# using a generator! if att is None: - #counter += 1 + #counter += 1 #enable to implemet a poorman timeout print ("timeout {}".format(counter)) else: print ("ATT {:>6}: uid:{:>3}, user_id:{:>8} t: {}, s:{}".format(counter, att.uid, att.user_id, att.timestamp, att.status)) diff --git a/test_voice.py b/test_voice.py index 293c3c8..352e64e 100755 --- a/test_voice.py +++ b/test_voice.py @@ -3,10 +3,11 @@ import sys import argparse +sys.path.append("zk") + from time import sleep from zk import ZK, const -sys.path.append("zk") parser = argparse.ArgumentParser(description='ZK Basic Reading Tests') parser.add_argument('-a', '--address', help='ZK device Addres [192.168.1.201]', default='192.168.1.201') @@ -18,12 +19,13 @@ parser.add_argument('-P', '--password', type=int, help='Device code/password', default=0) parser.add_argument('-f', '--force-udp', action="store_true", help='Force UDP communication') - +parser.add_argument('-v', '--verbose', action="store_true", + help='Print debug information') args = parser.parse_args() conn = None -zk = ZK(args.address, port=args.port, timeout=args.timeout, password=args.password, force_udp=args.force_udp) +zk = ZK(args.address, port=args.port, timeout=args.timeout, password=args.password, force_udp=args.force_udp, verbose=args.verbose) try: print ('Connecting to device ...') conn = zk.connect() diff --git a/zk/base.py b/zk/base.py index c0814e3..b949c2b 100644 --- a/zk/base.py +++ b/zk/base.py @@ -80,7 +80,7 @@ class ZK_helper(object): self.client.close() return res - def test_udp(self): + def test_udp(self): # WIP: self.client = socket(AF_INET, SOCK_DGRAM) self.client.settimeout(10) # fixed test @@ -470,7 +470,7 @@ class ZK(object): def get_user_extend_fmt(self): ''' - determine extend fmt + determine user extend fmt ''' command = const.CMD_OPTIONS_RRQ command_string = b'~UserExtFmt' @@ -502,7 +502,7 @@ class ZK(object): def get_compat_old_firmware(self): ''' - determine extend fmt + determine old firmware ''' command = const.CMD_OPTIONS_RRQ command_string = b'CompatOldFirmware' @@ -538,7 +538,6 @@ class ZK(object): command = const.CMD_GET_PINWIDTH command_string = b' P' response_size = 9 - cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): width = self.__data.split(b'\x00')[0] @@ -559,7 +558,6 @@ class ZK(object): """ read sizes """ command = const.CMD_GET_FREE_SIZES response_size = 1024 - cmd_response = self.__send_command(command,b'', response_size) if cmd_response.get('status'): size = len(self.__data) @@ -607,7 +605,7 @@ class ZK(object): raise ZKErrorResponse("can't restart device") def get_time(self): - """obtener la hora del equipo""" + """get Device Time""" command = const.CMD_GET_TIME response_size = 1032 cmd_response = self.__send_command(command, b'', response_size) @@ -617,7 +615,7 @@ class ZK(object): raise ZKErrorResponse("can't get time") def set_time(self, timestamp): - """ colocar la hora del sistema al zk """ + """ set Device time (pass datetime object)""" command = const.CMD_SET_TIME command_string = pack(b'I', self.__encode_time(timestamp)) cmd_response = self.__send_command(command, command_string) @@ -653,7 +651,7 @@ class ZK(object): def test_voice(self, index=0): ''' play test voice - 0 acceso correcto + 0 acceso correcto / acceso correcto 1 password incorrecto / clave incorrecta 2 la memoria del terminal estĆ” llena / acceso denegado 3 usuario invalido /codigo no valido @@ -865,8 +863,8 @@ class ZK(object): def get_user_template(self, uid, temp_id=0): """ ZKFinger VX10.0 for tcp: - command = const.CMD_USERTEMP_RRQ - command_string = pack('hb', uid, temp_id) + command = const.CMD_USERTEMP_RRQ (doesn't work always) + command_string = pack('hb', uid, temp_id) """ command = 88 # comando secreto!!! @@ -878,15 +876,16 @@ class ZK(object): return None #("can't get user template") #else if cmd_response.get('code') == const.CMD_DATA: # less than 1024!!! + resp = self.__data[:-1] if self.tcp: - size = len(self.__data[8:]) - return Finger(uid, temp_id, 1, self.__data[8:]) - else: - size = len(self.__data) - return Finger(uid, temp_id, 1, self.__data) + if resp[-6:] == b'\x00\x00\x00\x00\x00\x00': # padding? bug? + resp = resp[:-6] + if self.verbose: print("tcp too small!") + return Finger(uid, temp_id, 1, resp) if cmd_response.get('code') == const.CMD_PREPARE_DATA: data = [] bytes = self.__get_data_size() #TODO: check with size + size = bytes if self.tcp: data_recv = self.__sock.recv(bytes + 32) recieved = len(data_recv) @@ -896,9 +895,11 @@ class ZK(object): response = unpack('HHHH', data_recv[8:16])[0] if recieved >= (bytes + 32): #complete if response == const.CMD_DATA: - resp = data_recv[16:bytes+16] # no ack? - if self.verbose: print ("resp len", len(resp)) - return Finger(uid, temp_id, 1, resp) + resp = data_recv[16:bytes+16][:size-1] # no ack? + if resp[-6:] == b'\x00\x00\x00\x00\x00\x00': # padding? bug? + resp = resp[:-6] + if self.verbose: print ("resp len1", len(resp)) + return Finger(uid, temp_id, 1, resp) #mistery else: if self.verbose: print("broken packet!!!") return None #broken @@ -913,7 +914,10 @@ class ZK(object): data_recv = self.__sock.recv(16) response = unpack('HHHH', data_recv[8:16])[0] if response == const.CMD_ACK_OK: - resp = b''.join(data) + resp = b''.join(data)[:size-1] # testing + if resp[-6:] == b'\x00\x00\x00\x00\x00\x00': + resp = resp[:-6] + if self.verbose: print ("resp len2", len(resp)) return Finger(uid, temp_id, 1, resp) #data_recv[bytes+16:].encode('hex') #included CMD_ACK_OK if self.verbose: print("bad response %s" % data_recv) @@ -936,9 +940,9 @@ class ZK(object): break if self.verbose: print("still needs %s" % bytes) data = b''.join(data) - self.free_data() #uid 32 fing 03, starts with 4d-9b-53-53-32-31 - return Finger(uid, temp_id, 1, data) + #CMD_USERTEMP_RRQ desn't need [:-1] + return Finger(uid, temp_id, 1, data[:-1]) #udp def get_templates(self): """ return array of all fingers """ @@ -948,9 +952,11 @@ class ZK(object): if self.verbose: print("WRN: no user data") # debug return [] total_size = unpack('i', templatedata[0:4])[0] + print ("get template total size {}, size {} len {}".format(total_size, size, len(templatedata))) templatedata = templatedata[4:] #total size not used # ZKFinger VX10.0 the only finger firmware tested while total_size: + #print ("total_size {}".format(total_size)) size, uid, fid, valid = unpack('HHbb',templatedata[:6]) template = unpack("%is" % (size-6), templatedata[6:size])[0] finger = Finger(uid, fid, valid, template) @@ -1077,7 +1083,7 @@ class ZK(object): if self.verbose: print("A:%i esperando primer regevent" % attempts) data_recv = self.__sock.recv(1032) # timeout? tarda bastante... self.__ack_ok() - if self.verbose: print((data_recv).encode('hex')) + if self.verbose: print(codecs.encode(data_recv,'hex')) if self.tcp: if len(data_recv) > 16: #not empty res = unpack("H", data_recv.ljust(24,b"\x00")[16:18])[0] @@ -1096,7 +1102,7 @@ class ZK(object): if self.verbose: print ("A:%i esperando 2do regevent" % attempts) data_recv = self.__sock.recv(1032) # timeout? tarda bastante... self.__ack_ok() - if self.verbose: print ((data_recv).encode('hex')) + if self.verbose: print (codecs.encode(data_recv, 'hex')) if self.tcp: if len(data_recv) > 8: #not empty res = unpack("H", data_recv.ljust(24,b"\x00")[16:18])[0] @@ -1121,7 +1127,7 @@ class ZK(object): if self.verbose: print ("esperando 3er regevent") data_recv = self.__sock.recv(1032) # timeout? tarda bastante... self.__ack_ok() - if self.verbose: print ((data_recv).encode('hex')) + if self.verbose: print (codecs.encode(data_recv, 'hex')) if self.tcp: res = unpack("H", data_recv.ljust(24,b"\x00")[16:18])[0] else: @@ -1195,8 +1201,8 @@ class ZK(object): uid = tuser[0].uid yield Attendance(uid, user_id, timestamp, status) else: - if self.verbose: print ((data).encode('hex')), len(data) - yield data + if self.verbose: print (codecs.encode(data, 'hex')), len(data) + yield codecs.encode(data, 'hex') except timeout: if self.verbose: print ("time out") yield None # return to keep watching @@ -1232,10 +1238,8 @@ class ZK(object): raise ZKErrorResponse("can't read chunk %i:[%i]" % (start, size)) #else if cmd_response.get('code') == const.CMD_DATA: # less than 1024!!! - if self.tcp: - return self.__data[8:] #TODO: check size? - else: - return self.__data + if self.verbose: print ("size was {} len is {}".format(size, len(self.__data))) + return self.__data if cmd_response.get('code') == const.CMD_PREPARE_DATA: data = [] bytes = self.__get_data_size() #TODO: check with size diff --git a/zk/finger.py b/zk/finger.py index 1359af2..2ab0ab0 100644 --- a/zk/finger.py +++ b/zk/finger.py @@ -9,15 +9,19 @@ class Finger(object): self.valid = valid self.template = template #self.mark = str().encode("hex") - self.mark = codecs.encode(template[:6], 'hex') + self.mark = codecs.encode(template[:8], 'hex') + b'...' + codecs.encode(template[-8:], 'hex') + def repack(self): #full return pack("HHbb%is" % (self.size), self.size+6, self.uid, self.fid, self.valid, self.template) def repack_only(self): #only template return pack("H%is" % (self.size), self.size+2, self.template) - + + def __eq__(self, other): + return self.__dict__ == other.__dict__ + def __str__(self): - return " [uid:%i, fid:%i, size:%i v:%i t:%s...]" % (self.uid, self.fid, self.size, self.valid, self.mark) + return " [uid:{:>3}, fid:{}, size:{:>4} v:{} t:{}]".format(self.uid, self.fid, self.size, self.valid, self.mark) def __repr__(self): - return " [uid:%i, fid:%i, size:%i v:%i t:%s...]" % (self.uid, self.fid, self.size, self.valid, self.mark) #.encode('hex') + return " [uid:{:>3}, fid:{}, size:{:>4} v:{} t:{}]".format(self.uid, self.fid, self.size, self.valid, self.mark) From 10f3ed5a78e925da01ed5d8917aa711734803530 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Fri, 4 May 2018 15:26:46 -0400 Subject: [PATCH 36/83] test_voice doesn't raise error anymore --- test_machine.py | 6 +++--- zk/base.py | 38 ++++++++++++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/test_machine.py b/test_machine.py index d260a84..fd9480a 100755 --- a/test_machine.py +++ b/test_machine.py @@ -24,7 +24,7 @@ parser.add_argument('-a', '--address', parser.add_argument('-p', '--port', type=int, help='ZK device port [4370]', default=4370) parser.add_argument('-T', '--timeout', type=int, - help='Default [10] seconds (0: disable timeout)', default=10) + help='Default [10] seconds (0: disable timeout)', default=1) parser.add_argument('-P', '--password', type=int, help='Device code/password', default=0) parser.add_argument('-f', '--force-udp', action="store_true", @@ -163,8 +163,8 @@ try: else: conn.test_voice(23) # not registered conn.refresh_data() - print ("Voice Test ...") - conn.test_voice(10) + #print ("Voice Test ...") + #conn.test_voice(10) if args.templates: print ("Read Templates...") templates = conn.get_templates() diff --git a/zk/base.py b/zk/base.py index b949c2b..77474af 100644 --- a/zk/base.py +++ b/zk/base.py @@ -4,6 +4,7 @@ import sys from datetime import datetime from socket import AF_INET, SOCK_DGRAM, SOCK_STREAM, socket, timeout from struct import pack, unpack +import codecs from zk import const from zk.attendance import Attendance @@ -177,6 +178,15 @@ class ZK(object): return pack('H', checksum) + def __test_tcp_top(self, packet): + """ return size!""" + if len(packet)<=8: + return 0 # invalid packet + tcp_header = unpack('0: #jic data_recv = self.__sock.recv(bytes) #ideal limit? recieved = len(data_recv) + if self.verbose: print ("partial recv {}".format(recieved)) data.append(data_recv) # w/o tcp and header bytes -= recieved data_recv = self.__sock.recv(16) @@ -928,7 +948,7 @@ class ZK(object): while True: #limitado por respuesta no por tamaƱo data_recv = self.__sock.recv(response_size) response = unpack('HHHH', data_recv[:8])[0] - if self.verbose: print("# %s packet response is: %s" % (pac, response)) + if self.verbose: print("# packet response is: {}".format(response)) if response == const.CMD_DATA: data.append(data_recv[8:]) #header turncated bytes -= 1024 @@ -939,10 +959,12 @@ class ZK(object): if self.verbose: print("broken!") break if self.verbose: print("still needs %s" % bytes) - data = b''.join(data) + data = b''.join(data)[:-1] + if data[-6:] == b'\x00\x00\x00\x00\x00\x00': # padding? bug? + data = data[:-6] #uid 32 fing 03, starts with 4d-9b-53-53-32-31 #CMD_USERTEMP_RRQ desn't need [:-1] - return Finger(uid, temp_id, 1, data[:-1]) #udp + return Finger(uid, temp_id, 1, data) def get_templates(self): """ return array of all fingers """ @@ -1279,7 +1301,7 @@ class ZK(object): while True: #limitado por respuesta no por tamaƱo data_recv = self.__sock.recv(response_size) response = unpack('HHHH', data_recv[:8])[0] - if self.verbose: print ("# %s packet response is: %s" % (pac, response)) + if self.verbose: print ("# packet response is: {}".format(response)) if response == const.CMD_DATA: data.append(data_recv[8:]) #header turncated bytes -= 1024 #UDP From 0af8a0f18f427bfc4007a1ab913b2b6aa3f3adf0 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Fri, 4 May 2018 19:18:23 -0400 Subject: [PATCH 37/83] added user_id support --- zk/base.py | 157 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 136 insertions(+), 21 deletions(-) diff --git a/zk/base.py b/zk/base.py index 77474af..18396df 100644 --- a/zk/base.py +++ b/zk/base.py @@ -776,11 +776,15 @@ class ZK(object): if not isinstance(user, User): #try uid users = self.get_users() - users = list(filter(lambda x: x.uid==user, users)) - if len(users) == 1: - user = users[0] + tusers = list(filter(lambda x: x.uid==user, users)) + if len(tusers) == 1: + user = tusers[0] else: - raise ZKErrorResponse("Cant find user") + tusers = list(filter(lambda x: x.user_id==str(user), users)) + if len(tusers) == 1: + user = tusers[0] + else: + raise ZKErrorResponse("Cant find user") if isinstance(fingers, Finger): fingers = [fingers] fpack = "" @@ -834,13 +838,19 @@ class ZK(object): else: raise ZKErrorResponse("Cant send chunk") - def delete_user_template(self, uid, temp_id=0): + def delete_user_template(self, uid=0, temp_id=0, user_id=''): """ Delete specific template for tcp via user_id: command = 134 # unknown? command_string = pack('<24sB', user_id, temp_id) """ + if not uid: + users = self.get_users() + users = list(filter(lambda x: x.user_id==str(user_id), users)) + if not users: + return False + uid = users[0].uid command = const.CMD_DELETE_USERTEMP command_string = pack('hb', uid, temp_id) cmd_response = self.__send_command(command, command_string) @@ -865,6 +875,12 @@ class ZK(object): command = 133 #const.CMD_DELETE_USER_2 command_string = pack('24s',str(user_id)) else:""" + if not uid: + users = self.get_users() + users = list(filter(lambda x: x.user_id==str(user_id), users)) + if not users: + return False + uid = users[0].uid command = const.CMD_DELETE_USER command_string = pack('h', uid) cmd_response = self.__send_command(command, command_string) @@ -872,18 +888,34 @@ class ZK(object): raise ZKErrorResponse("can't delete user") self.refresh_data() - def get_user_template(self, uid, temp_id=0): + def get_user_template(self, uid, temp_id=0, user_id=''): """ ZKFinger VX10.0 for tcp: command = const.CMD_USERTEMP_RRQ (doesn't work always) command_string = pack('hb', uid, temp_id) """ - - command = 88 # comando secreto!!! - command_string = pack('hb', uid, temp_id) - response_size = 1024 + 8 - cmd_response = self.__send_command(command, command_string, response_size) - data = [] + if not uid: + users = self.get_users() + users = list(filter(lambda x: x.user_id==str(user_id), users)) + if not users: + return False + uid = users[0].uid + for _retries in range(3): + command = 88 # comando secreto!!! + command_string = pack('hb', uid, temp_id) + response_size = 1024 + 8 + cmd_response = self.__send_command(command, command_string, response_size) + data = self.__recieve_chunk() + if data is not None: + resp = data[:-1] # 01, valid byte? + if resp[-6:] == b'\x00\x00\x00\x00\x00\x00': # padding? bug? + resp = resp[:-6] + return Finger(uid, temp_id, 1, resp) + if self.verbose: print ("retry get_user_template") + else: + if self.verbose: print ("can't read/find finger") + return None + #----------------------------------------------------------------- if not cmd_response.get('status'): return None #("can't get user template") #else @@ -902,7 +934,7 @@ class ZK(object): data_recv = self.__sock.recv(bytes + 32) tcp_length = self.__test_tcp_top(data_recv) if tcp_length == 0: - print "Incorrect tcp packet" + print ("Incorrect tcp packet") return None recieved = len(data_recv) if self.verbose: print ("recieved {}, size {} rec {}".format(recieved, size, data_recv.encode('hex'))) @@ -1205,10 +1237,11 @@ class ZK(object): continue""" if len(data) == 12: #class 1 attendance user_id, status, match, timehex = unpack('= (size + 32): #complete + if response == const.CMD_DATA: + resp = data_recv[16 : size + 16] # no ack? + if self.verbose: print ("resp complete len", len(resp)) + return resp + else: + if self.verbose: print("broken packet!!! {}".format(response)) + return None #broken + else: # incomplete + if self.verbose: print ("try incomplete") + data.append(data_recv[16:]) # w/o tcp and header + size -= recieved-16 + while size>0: #jic + data_recv = self.__sock.recv(size) #ideal limit? + recieved = len(data_recv) + if self.verbose: print ("partial recv {}".format(recieved)) + data.append(data_recv) # w/o tcp and header + size -= recieved + #get cmd_ack_ok + data_recv = self.__sock.recv(16) + #could be broken + if len(data_recv) < 16: + print ("trying to complete broken ACK") + data_recv += self.__sock.recv(16 - len(data_recv)) + if not self.__test_tcp_top(data_recv): + if self.verbose: print ("invalid tcp ACK OK") + return None #b''.join(data) # incomplete? + response = unpack('HHHH', data_recv[8:16])[0] + if response == const.CMD_ACK_OK: + return b''.join(data) + #data_recv[bytes+16:].encode('hex') #included CMD_ACK_OK + if self.verbose: print("bad response %s" % data_recv) + if self.verbose: print (data) + return None + #else udp + while True: #limitado por respuesta no por tamaƱo + data_recv = self.__sock.recv(response_size) + response = unpack('HHHH', data_recv[:8])[0] + if self.verbose: print ("# packet response is: {}".format(response)) + if response == const.CMD_DATA: + data.append(data_recv[8:]) #header turncated + size -= 1024 #UDP + elif response == const.CMD_ACK_OK: + break #without problem. + else: + #truncado! continuar? + if self.verbose: print ("broken!") + break + if self.verbose: print ("still needs %s" % size) + return b''.join(data) + else: + if self.verbose: print ("invalid response %s" % self.__response) + return None #("can't get user template") + def __read_chunk(self, start, size): """ read a chunk from buffer """ - command = 1504 #CMD_READ_BUFFER - command_string = pack('= 16: user_id, timestamp, status, verified, reserved, workcode = unpack(' Date: Mon, 7 May 2018 08:52:22 -0400 Subject: [PATCH 38/83] fix end_capture --- zk/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/zk/base.py b/zk/base.py index 18396df..b0b603d 100644 --- a/zk/base.py +++ b/zk/base.py @@ -116,6 +116,7 @@ class ZK(object): self.users_av = 0 self.rec_av = 0 self.user_packet_size = 28 # default zk6 + self.end_live_capture = False self.__session_id = 0 self.__reply_id = const.USHRT_MAX-1 self.__data_recv = None From 50003aa9cf420a5907fca7b0d84ae6811f9d9b9f Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Mon, 7 May 2018 15:26:31 -0400 Subject: [PATCH 39/83] added timeout for ping in linux --- zk/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zk/base.py b/zk/base.py index b0b603d..ff29a61 100644 --- a/zk/base.py +++ b/zk/base.py @@ -65,7 +65,7 @@ class ZK_helper(object): import subprocess, platform # Ping parameters as function of OS - ping_str = "-n 1" if platform.system().lower()=="windows" else "-c 1" + ping_str = "-n 1" if platform.system().lower()=="windows" else "-c 1 -W 5" args = "ping " + " " + ping_str + " " + self.ip need_sh = False if platform.system().lower()=="windows" else True # Ping From 32da02f6fe19cd7a86493e1fb3f832d2322eac6a Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Mon, 7 May 2018 19:48:19 -0400 Subject: [PATCH 40/83] fix udp bulk mode --- zk/base.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/zk/base.py b/zk/base.py index ff29a61..a1c8612 100644 --- a/zk/base.py +++ b/zk/base.py @@ -432,7 +432,8 @@ class ZK(object): device = self.__data.split(b'=')[-1].split(b'\x00')[0] return device.decode() else: - raise ZKErrorResponse("can't read device name") + return "" #no name + #raise ZKErrorResponse("can't read device name") def get_face_version(self): ''' @@ -1327,7 +1328,7 @@ class ZK(object): #could be broken if len(data_recv) < 16: print ("trying to complete broken ACK") - data_recv += self.__sock.recv(16 - len(data_recv)) + data_recv += self.__sock.recv(16 - len(data_recv)) #TODO: CHECK HERE_! if not self.__test_tcp_top(data_recv): if self.verbose: print ("invalid tcp ACK OK") return None #b''.join(data) # incomplete? @@ -1340,7 +1341,7 @@ class ZK(object): return None #else udp while True: #limitado por respuesta no por tamaƱo - data_recv = self.__sock.recv(response_size) + data_recv = self.__sock.recv(1024+8) response = unpack('HHHH', data_recv[:8])[0] if self.verbose: print ("# packet response is: {}".format(response)) if response == const.CMD_DATA: From a330e2fd379238160e5baeb5d6b09cad741202ae Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Fri, 11 May 2018 21:12:22 -0400 Subject: [PATCH 41/83] calculate next userid and uid --- test_machine.py | 4 +++- zk/base.py | 22 ++++++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/test_machine.py b/test_machine.py index fd9480a..cc3b572 100755 --- a/test_machine.py +++ b/test_machine.py @@ -170,7 +170,9 @@ try: templates = conn.get_templates() for tem in templates: tem2 =conn.get_user_template(tem.uid,tem.fid) - if tem == tem2: # compare with alternative method + if temp2 is None: + print ("bulk! %s" % tem) + elif tem == tem2: # compare with alternative method print ("OK! %s" % tem) else: print ("dif-1 %s" % tem) diff --git a/zk/base.py b/zk/base.py index a1c8612..8a7623e 100644 --- a/zk/base.py +++ b/zk/base.py @@ -115,6 +115,8 @@ class ZK(object): self.fingers_av = 0 self.users_av = 0 self.rec_av = 0 + self.next_uid = 1 + self.next_user_id='1' self.user_packet_size = 28 # default zk6 self.end_live_capture = False self.__session_id = 0 @@ -1025,13 +1027,16 @@ class ZK(object): def get_users(self): #ALWAYS CALL TO GET correct user_packet_size """ return all user """ self.read_sizes() # last update - if self.users == 0: #lazy + if self.users == 0: #lazy + self.next_uid = 1 + self.next_user_id='1' return [] users = [] + max_uid = 0 userdata, size = self.read_with_buffer(const.CMD_USERTEMP_RRQ, const.FCT_USER) if self.verbose: print("user size %i" % size) if size <= 4: - if self.verbose: print("WRN: no user data")# debug + print("WRN: missing user data")# debug return [] total_size = unpack("I",userdata[:4])[0] self.user_packet_size = total_size / self.users @@ -1041,6 +1046,7 @@ class ZK(object): if self.user_packet_size == 28: while len(userdata) >= 28: uid, privilege, password, name, card, group_id, timezone, user_id = unpack(' max_uid: max_uid = uid password = (password.split(b'\x00')[0]).decode(errors='ignore') name = (name.split(b'\x00')[0]).decode(errors='ignore').strip() #card = unpack('I', card)[0] #or hex value? @@ -1064,12 +1070,24 @@ class ZK(object): name = (name.split(b'\x00')[0]).decode(errors='ignore').strip() group_id = (group_id.split(b'\x00')[0]).decode(errors='ignore').strip() user_id = (user_id.split(b'\x00')[0]).decode(errors='ignore') + if uid > max_uid: max_uid = uid #card = int(unpack('I', separator)[0]) if not name: name = "NN-%s" % user_id user = User(uid, name, privilege, password, group_id, user_id, card) users.append(user) userdata = userdata[72:] + #get limits! + max_uid += 1 + self.next_uid = max_uid + self.next_user_id = str(max_uid) + #re check + while True: + if any(u for u in users if u.user_id == self.next_user_id): + max_uid += 1 + self.next_user_id = str(max_uid) + else: + break return users def cancel_capture(self): From 0f9739e025dd9351a3015ebd6419ff68cbf5da76 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Wed, 23 May 2018 16:33:03 -0400 Subject: [PATCH 42/83] fix menor --- test_machine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_machine.py b/test_machine.py index cc3b572..35da3b8 100755 --- a/test_machine.py +++ b/test_machine.py @@ -170,7 +170,7 @@ try: templates = conn.get_templates() for tem in templates: tem2 =conn.get_user_template(tem.uid,tem.fid) - if temp2 is None: + if tem2 is None: print ("bulk! %s" % tem) elif tem == tem2: # compare with alternative method print ("OK! %s" % tem) From 4db70840377fc5cceb201639b8e464fe12d25455 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Thu, 24 May 2018 08:48:50 -0400 Subject: [PATCH 43/83] disable erase user --- test_machine.py | 4 ++-- zk/base.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/test_machine.py b/test_machine.py index cc3b572..1e5fb03 100755 --- a/test_machine.py +++ b/test_machine.py @@ -139,8 +139,8 @@ try: user_id = input('User ID2 :') card = input('Card :') card = int(card) if card else 0 - if prev: - conn.delete_user(uid) #borrado previo + #if prev: + # conn.delete_user(uid) #borrado previo try: conn.set_user(uid, name, privilege, password, '', user_id, card) args.enrolluser = uid diff --git a/zk/base.py b/zk/base.py index 8a7623e..c13032f 100644 --- a/zk/base.py +++ b/zk/base.py @@ -307,6 +307,7 @@ class ZK(object): ''' connect to the device ''' + self.end_live_capture = False # jic if not self.ommit_ping and not self.helper.test_ping(): raise ZKNetworkError("can't reach device (ping %s)" % self.__address[0]) if not self.force_udp and self.helper.test_tcp() == 0: #ok @@ -772,6 +773,10 @@ class ZK(object): if not cmd_response.get('status'): raise ZKErrorResponse("Cant set user") self.refresh_data() + if self.next_uid == uid: + self.next_uid += 1 # better recalculate again + if self.next_user_id == user_id: + self.next_user_id = str(self.next_uid) def save_user_template(self, user, fingers=[]): """ save user and template """ From f1b9e0d6565ec32fe7b1a7d0671eb31898d08a8d Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Fri, 25 May 2018 16:44:38 -0400 Subject: [PATCH 44/83] fixed tcp read_chunk nad improved dissector now it can read broken ACK responses --- test_machine.py | 13 ++ zk/base.py | 24 ++-- zk6.lua | 345 ++++++++++++++++++++++++++---------------------- 3 files changed, 212 insertions(+), 170 deletions(-) diff --git a/test_machine.py b/test_machine.py index 373c0c2..d33e9c2 100755 --- a/test_machine.py +++ b/test_machine.py @@ -88,7 +88,10 @@ try: print (conn) print ('') print ('--- Get User ---') + inicio = time.time() users = conn.get_users() + final = time.time() + print (' took {:.3f}[s]'.format(final - inicio)) max_uid = 0 prev = None if not args.deleteuser: @@ -119,6 +122,7 @@ try: #print '' if args.adduser and user.uid == args.adduser: prev = user + print (' took {:.3f}[s]'.format(final - inicio)) if args.adduser: uid = int(args.adduser) @@ -167,7 +171,11 @@ try: #conn.test_voice(10) if args.templates: print ("Read Templates...") + inicio = time.time() templates = conn.get_templates() + final = time.time() + print (' took {:.3f}[s]'.format(final - inicio)) + print ('now checking individually...') for tem in templates: tem2 =conn.get_user_template(tem.uid,tem.fid) if tem2 is None: @@ -177,13 +185,18 @@ try: else: print ("dif-1 %s" % tem) print ("dif-2 %s" % tem2) + print (' took {:.3f}[s]'.format(final - inicio)) if args.records: print ("Read Records...") + inicio = time.time() attendance = conn.get_attendance() + final = time.time() + print (' took {:.3f}[s]'.format(final - inicio)) i = 0 for att in attendance: i +=1 print ("ATT {:>6}: uid:{:>3}, user_id:{:>8} t: {}, s:{}".format(i, att.uid, att.user_id, att.timestamp, att.status)) + print (' took {:.3f}[s]'.format(final - inicio)) print ('') print ('--- sizes & capacity ---') conn.read_sizes() diff --git a/zk/base.py b/zk/base.py index c13032f..734c3ed 100644 --- a/zk/base.py +++ b/zk/base.py @@ -910,7 +910,7 @@ class ZK(object): return False uid = users[0].uid for _retries in range(3): - command = 88 # comando secreto!!! + command = 88 # comando secreto!!! GET_USER_TEMPLATE command_string = pack('hb', uid, temp_id) response_size = 1024 + 8 cmd_response = self.__send_command(command, command_string, response_size) @@ -946,7 +946,7 @@ class ZK(object): print ("Incorrect tcp packet") return None recieved = len(data_recv) - if self.verbose: print ("recieved {}, size {} rec {}".format(recieved, size, data_recv.encode('hex'))) + if self.verbose: print ("recieved {}, size {} rec {}".format(recieved, size, data_recv.encode('hex'))) #todo python3 tcp_length = unpack('HHI', data_recv[:8])[2] #bytes+8 if tcp_length < (bytes + 8): if self.verbose: print ("request chunk too big!") @@ -1313,7 +1313,7 @@ class ZK(object): if self.__response == const.CMD_DATA: # less than 1024!!! if self.verbose: print ("size was {} len is {}".format(size, len(self.__data))) return self.__data #without headers - elif self.__response== const.CMD_PREPARE_DATA: + elif self.__response == const.CMD_PREPARE_DATA: data = [] size = self.__get_data_size() if self.verbose: print ("recieve chunk:data size is", size) @@ -1328,7 +1328,7 @@ class ZK(object): if tcp_length < (size + 8): if self.verbose: print ("request chunk too big!") response = unpack('HHHH', data_recv[8:16])[0] - if recieved >= (size + 32): #complete + if recieved >= (size + 32): #complete with ACK_OK included if response == const.CMD_DATA: resp = data_recv[16 : size + 16] # no ack? if self.verbose: print ("resp complete len", len(resp)) @@ -1337,20 +1337,26 @@ class ZK(object): if self.verbose: print("broken packet!!! {}".format(response)) return None #broken else: # incomplete - if self.verbose: print ("try incomplete") - data.append(data_recv[16:]) # w/o tcp and header - size -= recieved-16 + if self.verbose: print ("try incomplete (actual valid {})".format(recieved-16)) + data.append(data_recv[16 : size+ 16 ]) # w/o DATA tcp and header + size -= recieved-16 # w/o DATA tcp and header + broken_header = b"" + if size < 0: #broken ack header? + broken_header = data_recv[size:] + if self.verbose: print ("broken", (broken_header).encode('hex')) #TODO python3 while size>0: #jic + if self.verbose: print ("still need {}".format(size)) data_recv = self.__sock.recv(size) #ideal limit? recieved = len(data_recv) if self.verbose: print ("partial recv {}".format(recieved)) data.append(data_recv) # w/o tcp and header size -= recieved #get cmd_ack_ok - data_recv = self.__sock.recv(16) + data_recv = broken_header + self.__sock.recv(16) #could be broken if len(data_recv) < 16: - print ("trying to complete broken ACK") + print ("trying to complete broken ACK %s /16" % len(data_recv)) + if self.verbose: print (data_recv.encode('hex')) #todo python3 data_recv += self.__sock.recv(16 - len(data_recv)) #TODO: CHECK HERE_! if not self.__test_tcp_top(data_recv): if self.verbose: print ("invalid tcp ACK OK") diff --git a/zk6.lua b/zk6.lua index f947df0..dd2d2f5 100644 --- a/zk6.lua +++ b/zk6.lua @@ -13,10 +13,6 @@ -- -- OVERVIEW: -- This script creates an dissector for the UDP protocol on ZK products. --- to the DNS protocol. That's OK. The goal isn't to fully dissect DNS properly - Wireshark already has a good --- DNS dissector built-in. We don't need another one. We also have other example Lua scripts, but I don't think --- they do a good job of explaining things, and the nice thing about this one is getting capture files to --- run it against is trivial. (plus I uploaded one) -- -- HOW TO RUN THIS SCRIPT: -- Wireshark and Tshark support multiple ways of loading Lua scripts: through a dofile() call in init.lua, @@ -126,98 +122,99 @@ local zk = Proto("zk6","ZK600 UDP Protocol") local zk_tcp = Proto("zk8","ZK800 TCP Protocol") local rfct = { - [1] = "FCT_ATTLOG", - [8] = "FCT_WORKCODE", - [2] = "FCT_FINGERTMP", - [4] = "FCT_OPLOG", - [5] = "FCT_USER", - [6] = "FCT_SMS", - [7] = "FCT_UDATA" + [1] = "FCT_ATTLOG", + [8] = "FCT_WORKCODE", + [2] = "FCT_FINGERTMP", + [4] = "FCT_OPLOG", + [5] = "FCT_USER", + [6] = "FCT_SMS", + [7] = "FCT_UDATA" } local rcomands = { - [7] = "CMD_DB_RRQ", - [8] = "CMD_USER_WRQ", - [9] = "CMD_USERTEMP_RRQ", - [10] = "CMD_USERTEMP_WRQ", - [11] = "CMD_OPTIONS_RRQ", - [12] = "CMD_OPTIONS_WRQ", - [13] = "CMD_ATTLOG_RRQ", - [14] = "CMD_CLEAR_DATA", - [15] = "CMD_CLEAR_ATTLOG", - [18] = "CMD_DELETE_USER", - [19] = "CMD_DELETE_USERTEMP", - [20] = "CMD_CLEAR_ADMIN", - [21] = "CMD_USERGRP_RRQ", - [22] = "CMD_USERGRP_WRQ", - [23] = "CMD_USERTZ_RRQ", - [24] = "CMD_USERTZ_WRQ", - [25] = "CMD_GRPTZ_RRQ", - [26] = "CMD_GRPTZ_WRQ", - [27] = "CMD_TZ_RRQ", - [28] = "CMD_TZ_WRQ", - [29] = "CMD_ULG_RRQ", - [30] = "CMD_ULG_WRQ", - [31] = "CMD_UNLOCK", - [32] = "CMD_CLEAR_ACC", - [33] = "CMD_CLEAR_OPLOG", - [34] = "CMD_OPLOG_RRQ", - [50] = "CMD_GET_FREE_SIZES", - [57] = "CMD_ENABLE_CLOCK", - [60] = "CMD_STARTVERIFY", - [61] = "CMD_STARTENROLL", - [62] = "CMD_CANCELCAPTURE", - [64] = "CMD_STATE_RRQ", - [66] = "CMD_WRITE_LCD", - [67] = "CMD_CLEAR_LCD", - [69] = "CMD_GET_PINWIDTH", - [70] = "CMD_SMS_WRQ", - [71] = "CMD_SMS_RRQ", - [72] = "CMD_DELETE_SMS", - [73] = "CMD_UDATA_WRQ", - [74] = "CMD_DELETE_UDATA", - [75] = "CMD_DOORSTATE_RRQ", - [76] = "CMD_WRITE_MIFARE", - [78] = "CMD_EMPTY_MIFARE", - [201] = "CMD_GET_TIME", - [202] = "CMD_SET_TIME", - [500] = "CMD_REG_EVENT", - [1000] = "CMD_CONNECT", - [1001] = "CMD_EXIT", - [1002] = "CMD_ENABLEDEVICE", - [1003] = "CMD_DISABLEDEVICE", - [1004] = "CMD_RESTART", - [1005] = "CMD_POWEROFF", - [1006] = "CMD_SLEEP", - [1007] = "CMD_RESUME", - [1009] = "CMD_CAPTUREFINGER", - [1011] = "CMD_TEST_TEMP", - [1012] = "CMD_CAPTUREIMAGE", - [1013] = "CMD_REFRESHDATA", - [1014] = "CMD_REFRESHOPTION", - [1017] = "CMD_TESTVOICE", - [1100] = "CMD_GET_VERSION", - [1101] = "CMD_CHANGE_SPEED", - [1102] = "CMD_AUTH", - [1500] = "CMD_PREPARE_DATA", - [1501] = "CMD_DATA", - [1502] = "CMD_FREE_DATA", - [1503] = "CMD_PREPARE_BUFFER", - [1504] = "CMD_READ_BUFFER", - [2000] = "CMD_ACK_OK", - [2001] = "CMD_ACK_ERROR", - [2002] = "CMD_ACK_DATA", - [2003] = "CMD_ACK_RETRY", - [2004] = "CMD_ACK_REPEAT", - [2005] = "CMD_ACK_UNAUTH", - [65535] = "CMD_ACK_UNKNOWN", - [65533] = "CMD_ACK_ERROR_CMD", - [65532] = "CMD_ACK_ERROR_INIT", - [65531] = "CMD_ACK_ERROR_DATA" + [7] = "CMD_DB_RRQ", + [8] = "CMD_USER_WRQ", + [9] = "CMD_USERTEMP_RRQ", + [10] = "CMD_USERTEMP_WRQ", + [11] = "CMD_OPTIONS_RRQ", + [12] = "CMD_OPTIONS_WRQ", + [13] = "CMD_ATTLOG_RRQ", + [14] = "CMD_CLEAR_DATA", + [15] = "CMD_CLEAR_ATTLOG", + [18] = "CMD_DELETE_USER", + [19] = "CMD_DELETE_USERTEMP", + [20] = "CMD_CLEAR_ADMIN", + [21] = "CMD_USERGRP_RRQ", + [22] = "CMD_USERGRP_WRQ", + [23] = "CMD_USERTZ_RRQ", + [24] = "CMD_USERTZ_WRQ", + [25] = "CMD_GRPTZ_RRQ", + [26] = "CMD_GRPTZ_WRQ", + [27] = "CMD_TZ_RRQ", + [28] = "CMD_TZ_WRQ", + [29] = "CMD_ULG_RRQ", + [30] = "CMD_ULG_WRQ", + [31] = "CMD_UNLOCK", + [32] = "CMD_CLEAR_ACC", + [33] = "CMD_CLEAR_OPLOG", + [34] = "CMD_OPLOG_RRQ", + [50] = "CMD_GET_FREE_SIZES", + [57] = "CMD_ENABLE_CLOCK", + [60] = "CMD_STARTVERIFY", + [61] = "CMD_STARTENROLL", + [62] = "CMD_CANCELCAPTURE", + [64] = "CMD_STATE_RRQ", + [66] = "CMD_WRITE_LCD", + [67] = "CMD_CLEAR_LCD", + [69] = "CMD_GET_PINWIDTH", + [70] = "CMD_SMS_WRQ", + [71] = "CMD_SMS_RRQ", + [72] = "CMD_DELETE_SMS", + [73] = "CMD_UDATA_WRQ", + [74] = "CMD_DELETE_UDATA", + [75] = "CMD_DOORSTATE_RRQ", + [76] = "CMD_WRITE_MIFARE", + [78] = "CMD_EMPTY_MIFARE", + [88] = "_CMD_GET_USER_TEMPLATE", + [201] = "CMD_GET_TIME", + [202] = "CMD_SET_TIME", + [500] = "CMD_REG_EVENT", + [1000] = "CMD_CONNECT", + [1001] = "CMD_EXIT", + [1002] = "CMD_ENABLEDEVICE", + [1003] = "CMD_DISABLEDEVICE", + [1004] = "CMD_RESTART", + [1005] = "CMD_POWEROFF", + [1006] = "CMD_SLEEP", + [1007] = "CMD_RESUME", + [1009] = "CMD_CAPTUREFINGER", + [1011] = "CMD_TEST_TEMP", + [1012] = "CMD_CAPTUREIMAGE", + [1013] = "CMD_REFRESHDATA", + [1014] = "CMD_REFRESHOPTION", + [1017] = "CMD_TESTVOICE", + [1100] = "CMD_GET_VERSION", + [1101] = "CMD_CHANGE_SPEED", + [1102] = "CMD_AUTH", + [1500] = "CMD_PREPARE_DATA", + [1501] = "CMD_DATA", + [1502] = "CMD_FREE_DATA", + [1503] = "CMD_PREPARE_BUFFER", + [1504] = "CMD_READ_BUFFER", + [2000] = "CMD_ACK_OK", + [2001] = "CMD_ACK_ERROR", + [2002] = "CMD_ACK_DATA", + [2003] = "CMD_ACK_RETRY", + [2004] = "CMD_ACK_REPEAT", + [2005] = "CMD_ACK_UNAUTH", + [65535] = "CMD_ACK_UNKNOWN", + [65533] = "CMD_ACK_ERROR_CMD", + [65532] = "CMD_ACK_ERROR_INIT", + [65531] = "CMD_ACK_ERROR_DATA" } local rmachines = { - [20560] = "MACHINE_PREPARE_DATA_1", - [32130] = "MACHINE_PREPARE_DATA_2" + [20560] = "MACHINE_PREPARE_DATA_1", + [32130] = "MACHINE_PREPARE_DATA_2" } ---------------------------------------- local pf_machine1 = ProtoField.new ("Machine Data 1", "zk8.machine1", ftypes.UINT16, rmachines, base.DEC) @@ -269,11 +266,11 @@ local pf_uid = ProtoField.new ("User ID", "zk6.uid", ftypes.UINT16, nil) -- in a real script I wouldn't do it this way; I'd build a table of fields programmatically -- and then set dns.fields to it, so as to avoid forgetting a field zk.fields = { pf_command, pf_checksum, pf_sesion_id, pf_reply_id, pf_commkey, pf_data, pf_string, - pf_time, pf_start, pf_size, pf_psize, pf_fsize0, pf_fsize1, pf_fsize2, pf_fsize3, - pf_fsizeu, pf_fsize4, pf_fsizef, pf_fsize5,pf_fsizer,pf_fsize6,pf_fsize7, - pf_fsize8,pf_fsizec,pf_fsize9,pf_fsizefc,pf_fsizeuc,pf_fsizerc, pf_uid, - pf_fsizefa,pf_fsizeua,pf_fsizera, pf_fsizeff, pf_fsize10, pf_fsizeffc, - pf_pbfill, pf_pbcmd, pf_pbarg, pf_pbfill0, pf_pbfree} + pf_time, pf_start, pf_size, pf_psize, pf_fsize0, pf_fsize1, pf_fsize2, pf_fsize3, + pf_fsizeu, pf_fsize4, pf_fsizef, pf_fsize5,pf_fsizer,pf_fsize6,pf_fsize7, + pf_fsize8,pf_fsizec,pf_fsize9,pf_fsizefc,pf_fsizeuc,pf_fsizerc, pf_uid, + pf_fsizefa,pf_fsizeua,pf_fsizera, pf_fsizeff, pf_fsize10, pf_fsizeffc, + pf_pbfill, pf_pbcmd, pf_pbarg, pf_pbfill0, pf_pbfree} zk_tcp.fields = { pf_machine1, pf_machine2, pf_length } ---------------------------------------- @@ -450,73 +447,90 @@ function zk.dissector(tvbuf, pktinfo, root) tree:add_le(pf_sesion_id, tvbuf:range(4,2)) tree:add_le(pf_reply_id, tvbuf:range(6,2)) local command = tvbuf:range(0,2):le_uint() + if rcomands[command] ~= nil then + --pktinfo.cols.info:set(rcomands[command]) + pktinfo.cols.info = string.sub(rcomands[command], 5) + else + --pktinfo.cols.info:set("CMD:" .. tostring(command)) + pktinfo.cols.info = "CMD:" .. tostring(command) + end if pktlen > ZK_HDR_LEN then remain = pktlen - ZK_HDR_LEN -- TODO: no funciona el prevCommand, - if (command == 1102) then - tree:add_le(pf_commkey, tvbuf:range(8,4)) - elseif (command == 1500) then + if (command == 1102) then --CMD_AUTH + tree:add_le(pf_commkey, tvbuf:range(8,4)) + elseif (command == 1500) then --CMD_PREPARE_DATA tree:add_le(pf_size, tvbuf:range(8,4)) - if remain > 8 then - tree:add_le(pf_psize, tvbuf:range(12,4)) - end - elseif (command == 12) or (command == 11) then - tree:add(pf_string, tvbuf:range(8,remain)) - elseif (command == 18) then - tree:add_le(pf_uid, tvbuf(8,2)) - elseif (command == 1503) then - tree:add(pf_pbfill, tvbuf:range(8,1)) - tree:add_le(pf_pbcmd, tvbuf:range(9,2)) - tree:add_le(pf_pbarg, tvbuf:range(11,8)) - elseif (command == 1504) then - tree:add_le(pf_start, tvbuf:range(8,4)) + pktinfo.cols.info = tostring(pktinfo.cols.info) .. " - " .. tvbuf:range(8,4):le_uint() .. " Bytes" + if remain > 8 then + tree:add_le(pf_psize, tvbuf:range(12,4)) + end + elseif (command == 12) or (command == 11) then --CMD_OPTIONS_RRQ CMD_OPTIONS_WRQ + tree:add(pf_string, tvbuf:range(8,remain)) + pktinfo.cols.info = tostring(pktinfo.cols.info) .. " - " .. tvbuf:range(8,remain):string() + elseif (command == 18) then -- CMD_DELETE_USER + tree:add_le(pf_uid, tvbuf(8,2)) + pktinfo.cols.info = tostring(pktinfo.cols.info) .. " UID: " .. tvbuf:range(8,2):le_uint() + elseif (command == 88) then -- CMD_get_user_Template + tree:add_le(pf_uid, tvbuf(8,2)) + tree:add_le(pf_pbfill0, tvbuf(10,1)) + pktinfo.cols.info = tostring(pktinfo.cols.info) .. " UID: " .. tvbuf:range(8,2):le_uint() + elseif (command == 1503) then -- CMD_PREPARE_BUFFER + tree:add(pf_pbfill, tvbuf:range(8,1)) + tree:add_le(pf_pbcmd, tvbuf:range(9,2)) + tree:add_le(pf_pbarg, tvbuf:range(11,8)) + pktinfo.cols.info = tostring(pktinfo.cols.info) .. " - " .. rcomands[tvbuf:range(9,2):le_uint()] + elseif (command == 1504) then --CMD_READ_BUFFER + tree:add_le(pf_start, tvbuf:range(8,4)) tree:add_le(pf_size, tvbuf:range(12,4)) - elseif (prevCommand == 1503) then + pktinfo.cols.info = tostring(pktinfo.cols.info) .. " [" .. tvbuf:range(8,4):le_uint() .. "] -> " .. tvbuf:range(12,4):le_uint() + elseif (command == 1501) then --CMD_DATA + pktinfo.cols.info = tostring(pktinfo.cols.info) .. " " .. (remain) .. " Bytes" + tree:add(pf_string, tvbuf:range(8,remain)) + elseif (prevCommand == 1503) then -- CMD_PREPARE_BUFFER OK! tree:add_le(pf_pbfill0, tvbuf:range(8,1)) - tree:add_le(pf_size, tvbuf:range(9,4)) + tree:add_le(pf_size, tvbuf:range(9,4)) tree:add_le(pf_psize, tvbuf:range(13,4)) - tree:add_le(pf_pbfree, tvbuf:range(17,4)) - elseif (prevCommand == 12) or (prevCommand == 11) or (prevCommand == 1100) then - tree:add(pf_string, tvbuf:range(8,remain)) + tree:add_le(pf_pbfree, tvbuf:range(17,4)) + pktinfo.cols.info = tostring(pktinfo.cols.info) .. " BUFFER [" .. tvbuf:range(9,4):le_uint() .. "] (" .. tvbuf:range(13,4):le_uint() .. ")" + elseif (prevCommand == 12) or (prevCommand == 11) or (prevCommand == 1100) then --CMD_OPTIONS_RRQ CMD_OPTIONS_WRQ OK + tree:add(pf_string, tvbuf:range(8,remain)) + pktinfo.cols.info = tostring(pktinfo.cols.info) .. " RESP " .. tvbuf:range(8,remain):string() elseif (prevCommand == 201) or (prevCommand == 202) then local ts = tvbuf:range(8,4):le_uint() tree:add_le(pf_time, tvbuf:range(8,4)) - elseif (prevCommand == 50) then - tree:add_le(pf_fsize0, tvbuf:range(8,4)) - tree:add_le(pf_fsize1, tvbuf:range(12,4)) - tree:add_le(pf_fsize2, tvbuf:range(16,4)) - tree:add_le(pf_fsize3, tvbuf:range(20,4)) - tree:add_le(pf_fsizeu, tvbuf:range(24,4)) - tree:add_le(pf_fsize4, tvbuf:range(28,4)) - tree:add_le(pf_fsizef, tvbuf:range(32,4)) - tree:add_le(pf_fsize5, tvbuf:range(36,4)) - tree:add_le(pf_fsizer, tvbuf:range(40,4)) - tree:add_le(pf_fsize6, tvbuf:range(44,4)) - tree:add_le(pf_fsize7, tvbuf:range(48,4)) - tree:add_le(pf_fsize8, tvbuf:range(52,4)) - tree:add_le(pf_fsizec, tvbuf:range(56,4)) - tree:add_le(pf_fsize9, tvbuf:range(60,4)) - tree:add_le(pf_fsizefc, tvbuf:range(64,4)) - tree:add_le(pf_fsizeuc, tvbuf:range(68,4)) - tree:add_le(pf_fsizerc, tvbuf:range(72,4)) - tree:add_le(pf_fsizefa, tvbuf:range(76,4)) - tree:add_le(pf_fsizeua, tvbuf:range(80,4)) - tree:add_le(pf_fsizera, tvbuf:range(84,4)) - if remain > 80 then - tree:add_le(pf_fsizeff, tvbuf:range(88,4)) - tree:add_le(pf_fsize10, tvbuf:range(92,4)) - tree:add_le(pf_fsizeffc, tvbuf:range(96,4)) - end + elseif (prevCommand == 50) then + tree:add_le(pf_fsize0, tvbuf:range(8,4)) + tree:add_le(pf_fsize1, tvbuf:range(12,4)) + tree:add_le(pf_fsize2, tvbuf:range(16,4)) + tree:add_le(pf_fsize3, tvbuf:range(20,4)) + tree:add_le(pf_fsizeu, tvbuf:range(24,4)) + tree:add_le(pf_fsize4, tvbuf:range(28,4)) + tree:add_le(pf_fsizef, tvbuf:range(32,4)) + tree:add_le(pf_fsize5, tvbuf:range(36,4)) + tree:add_le(pf_fsizer, tvbuf:range(40,4)) + tree:add_le(pf_fsize6, tvbuf:range(44,4)) + tree:add_le(pf_fsize7, tvbuf:range(48,4)) + tree:add_le(pf_fsize8, tvbuf:range(52,4)) + tree:add_le(pf_fsizec, tvbuf:range(56,4)) + tree:add_le(pf_fsize9, tvbuf:range(60,4)) + tree:add_le(pf_fsizefc, tvbuf:range(64,4)) + tree:add_le(pf_fsizeuc, tvbuf:range(68,4)) + tree:add_le(pf_fsizerc, tvbuf:range(72,4)) + tree:add_le(pf_fsizefa, tvbuf:range(76,4)) + tree:add_le(pf_fsizeua, tvbuf:range(80,4)) + tree:add_le(pf_fsizera, tvbuf:range(84,4)) + if remain > 80 then + tree:add_le(pf_fsizeff, tvbuf:range(88,4)) + tree:add_le(pf_fsize10, tvbuf:range(92,4)) + tree:add_le(pf_fsizeffc, tvbuf:range(96,4)) + end else -- tree:add_le(pf_data, tvbuf:range(8,remain)) most time we need strings - tree:add(pf_string, tvbuf:range(8,remain)) + tree:add(pf_string, tvbuf:range(8,remain)) end end dprint2("zk.dissector returning",pktlen) - if rcomands[command] ~= nil then - pktinfo.cols.info:set(rcomands[command]) - else - pktinfo.cols.info:set("CMD:" .. tostring(command)) - end + prevCommand = command -- tell wireshark how much of tvbuff we dissected return pktlen @@ -529,7 +543,7 @@ DissectorTable.get("udp.port"):add(default_settings.port, zk) function zk_tcp.dissector(tvbuf, pktinfo, root) dprint2("zk_tcp.dissector called") - local pktlen = tvbuf:reported_length_remaining() + local pktlen = tvbuf:reported_length_remaining() -- We start by adding our protocol to the dissection display tree. -- A call to tree:add() returns the child created, so we can add more "under" it using that return value. @@ -547,18 +561,27 @@ function zk_tcp.dissector(tvbuf, pktinfo, root) dprint("packet length",pktlen,"too short") return end - dprint2("zk_tcp.dissector returning", pktlen) - tree:add_le(pf_machine1, tvbuf:range(0,2)) - tree:add_le(pf_machine2, tvbuf:range(2,2)) - tree:add_le(pf_length, tvbuf:range(4,4)) -- tell wireshark how much of tvbuff we dissected - if pktlen > ZK_HDR_LEN then - remain = pktlen - ZK_HDR_LEN - -- zk_tree = tree:add(zk, tvbuf:range(8, remain)) - zk.dissector(tvbuf:range(8,remain):tvb(), pktinfo, tree) - end - -- set the protocol column to show our protocol name - pktinfo.cols.protocol:set("ZK8") + dprint2("zk_tcp.dissector returning", pktlen) + local machine1 = tvbuf:range(0,2):le_uint() + local machine2 = tvbuf:range(2,2):le_uint() + + if (machine1 == 20560) and (machine2 == 32130) then + local tcp_length = tvbuf:range(4,4):le_uint64() + tree:add_le(pf_machine1, tvbuf:range(0,2)) + tree:add_le(pf_machine2, tvbuf:range(2,2)) + tree:add_le(pf_length, tvbuf:range(4,4)) + if pktlen > ZK_HDR_LEN then + remain = pktlen - ZK_HDR_LEN + -- zk_tree = tree:add(zk, tvbuf:range(8, remain)) + zk.dissector(tvbuf:range(8,remain):tvb(), pktinfo, tree) + end + -- set the protocol column to show our protocol name + pktinfo.cols.protocol:set("ZK8") + else + pktinfo.cols.protocol:set("ZK8") + pktinfo.cols.info:set("--- data " .. pktlen .. " Bytes") + end return pktlen end From 1f01965b7b76f2124e4d9d1c0d230865a615a70a Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Fri, 25 May 2018 17:26:41 -0400 Subject: [PATCH 45/83] doc live capture --- README.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 934447e..65dc47b 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Complete documentation can be found at [Readthedocs](http://pyzk.readthedocs.io/ # Api Usage -Just create a ZK object and you will ready to call api. +Just create a ZK object and you will be ready to call api. * Basic Usage ```python @@ -165,6 +165,24 @@ conn.restart() conn.free_data() ``` +* Live Capture! + +```python +# live capture! (timeout at 10s) +for attendance in conn.live_capture(): + if attendance is None: + # implement here timeout logic + pass + else: + print (attendance) # Attendance object + #if you need to break gracefully just set + # conn.end_live_capture = True + # on interactive mode, + # use Ctrl+C to break gracefully + # this way it restores timeout + # and disables live capture +``` + Test Machine ```sh @@ -184,6 +202,7 @@ optional arguments: -P PASSWORD, --password PASSWORD Device code/password -f, --force-udp Force UDP communication + -v, --verbose Print debug information -t, --templates Get templates / fingers -r, --records Get attendance records -u, --updatetime Update Date/Time @@ -202,7 +221,7 @@ optional arguments: -# Related Project +# Related Project (TODO: chekc compatibility) * [zkcluster](https://github.com/fananimi/zkcluster/ "zkcluster project") is a django apps to manage multiple fingerprint devices. From 91578260778fdb753b041b8a0da5be4174fe63c2 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Wed, 30 May 2018 22:18:43 -0400 Subject: [PATCH 46/83] minor fix --- zk/attendance.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zk/attendance.py b/zk/attendance.py index 1fbb9d1..de11bc2 100644 --- a/zk/attendance.py +++ b/zk/attendance.py @@ -7,7 +7,7 @@ class Attendance(object): self.status = status def __str__(self): - return ': {}:{}'.format(self.user_id, self.timestamp) + return ': {} : {}'.format(self.user_id, self.timestamp) def __repr__(self): - return ': {}:{}'.format(self.user_id, self.timestamp) + return ': {} : {}'.format(self.user_id, self.timestamp) From 4bfbcf6b496300ff21f2aa8286cd7317994bdc19 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Wed, 6 Jun 2018 08:48:30 -0400 Subject: [PATCH 47/83] test read_sizes and fixed finger repack --- zk/base.py | 16 +++++++++++++--- zk/finger.py | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/zk/base.py b/zk/base.py index 734c3ed..83c1e9f 100644 --- a/zk/base.py +++ b/zk/base.py @@ -576,11 +576,12 @@ class ZK(object): response_size = 1024 cmd_response = self.__send_command(command,b'', response_size) if cmd_response.get('status'): + if self.verbose: print(codecs.encode(self.__data,'hex')) size = len(self.__data) if size == 80: fields = unpack('iiiiiiiiiiiiiiiiiiii', self.__data) else: #92? - fields = unpack('iiiiiiiiiiiiiiiiiiiiiii', self.__data) + fields = unpack('iiiiiiiiiiiiiiiiiiiiiii', self.__data[:92]) #dirty hack! we need more information self.users = fields[4] self.fingers = fields[6] self.records = fields[8] @@ -851,9 +852,16 @@ class ZK(object): """ Delete specific template for tcp via user_id: - command = 134 # unknown? - command_string = pack('<24sB', user_id, temp_id) """ + if self.tcp and user_id: + command = 134 # unknown? + command_string = pack('<24sB', str(user_id), temp_id) + cmd_response = self.__send_command(command, command_string) + # users = list(filter(lambda x: x.uid==uid, users)) + if cmd_response.get('status'): + return True #refres_data (1013)? + else: + return False # probably empty! if not uid: users = self.get_users() users = list(filter(lambda x: x.user_id==str(user_id), users)) @@ -896,6 +904,8 @@ class ZK(object): if not cmd_response.get('status'): raise ZKErrorResponse("can't delete user") self.refresh_data() + if uid == (self.next_uid - 1): + self.next_uid = uid # quick undo def get_user_template(self, uid, temp_id=0, user_id=''): """ ZKFinger VX10.0 diff --git a/zk/finger.py b/zk/finger.py index 2ab0ab0..2b38fd4 100644 --- a/zk/finger.py +++ b/zk/finger.py @@ -15,7 +15,7 @@ class Finger(object): return pack("HHbb%is" % (self.size), self.size+6, self.uid, self.fid, self.valid, self.template) def repack_only(self): #only template - return pack("H%is" % (self.size), self.size+2, self.template) + return pack("H%is" % (self.size), self.size, self.template) def __eq__(self, other): return self.__dict__ == other.__dict__ From 450e448713c0df995f323e3421dab9327c9cdad2 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Wed, 6 Jun 2018 15:10:18 -0400 Subject: [PATCH 48/83] test prepacked tcp data, and unlock door --- README.md | 8 ++++---- test.py | 2 +- test_machine.py | 7 +++++++ zk/base.py | 19 ++++++++++++++++++- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 65dc47b..b022bca 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Just create a ZK object and you will be ready to call api. from zk import ZK, const conn = None -zk = ZK('192.168.1.201', port=4370, timeout=5) +zk = ZK('192.168.1.201', port=4370, timeout=5, password=0, force_udp=False, ommit_ping=False) try: print ('Connecting to device ...') conn = zk.connect() @@ -207,6 +207,8 @@ optional arguments: -r, --records Get attendance records -u, --updatetime Update Date/Time -l, --live-capture Live Event Capture +-o, --open-door Open door + -D DELETEUSER, --deleteuser DELETEUSER Delete a User (uid) -A ADDUSER, --adduser ADDUSER @@ -219,9 +221,7 @@ optional arguments: ``` - - -# Related Project (TODO: chekc compatibility) +# Related Project (TODO: check compatibility) * [zkcluster](https://github.com/fananimi/zkcluster/ "zkcluster project") is a django apps to manage multiple fingerprint devices. diff --git a/test.py b/test.py index d5f1f75..388b9d0 100644 --- a/test.py +++ b/test.py @@ -5,7 +5,7 @@ sys.path.append("zk") from zk import ZK, const conn = None -zk = ZK('192.168.1.201', port=4370, timeout=5) +zk = ZK('192.168.1.201', port=4370, timeout=5, password=0, force_udp=False, ommit_ping=False) try: print ('Connecting to device ...') conn = zk.connect() diff --git a/test_machine.py b/test_machine.py index d33e9c2..5d554da 100755 --- a/test_machine.py +++ b/test_machine.py @@ -39,6 +39,8 @@ parser.add_argument('-u', '--updatetime', action="store_true", help='Update Date/Time') parser.add_argument('-l', '--live-capture', action="store_true", help='Live Event Capture') +parser.add_argument('-o', '--open-door', action="store_true", + help='Open door') parser.add_argument('-D', '--deleteuser', type=int, help='Delete a User (uid)', default=0) parser.add_argument('-A', '--adduser', type=int, @@ -201,6 +203,11 @@ try: print ('--- sizes & capacity ---') conn.read_sizes() print (conn) + if args.open_door: + print ('') + print ('--- Opening door 10s ---') + conn.unlock(10) + print (' -- done!---') if args.live_capture: print ('') print ('--- Live Capture! (press ctrl+C to break) ---') diff --git a/zk/base.py b/zk/base.py index 83c1e9f..b4473ae 100644 --- a/zk/base.py +++ b/zk/base.py @@ -600,6 +600,20 @@ class ZK(object): else: raise ZKErrorResponse("can't read sizes") + def unlock(self, time=3): + ''' + :param time: define time in seconds + :return: + thanks to https://github.com/SoftwareHouseMerida/pyzk/ + ''' + command = const.CMD_UNLOCK + command_string = pack("I",int(time)*10) + cmd_response = self.__send_command(command, command_string) + if cmd_response.get('status'): + return True + else: + raise ZKErrorResponse("can't open door") + def __str__(self): """ for debug""" return "ZK %s://%s:%s users[%i]:%i/%i fingers:%i/%i, records:%i/%i faces:%i/%i" % ( @@ -1328,7 +1342,10 @@ class ZK(object): size = self.__get_data_size() if self.verbose: print ("recieve chunk:data size is", size) if self.tcp: - data_recv = self.__sock.recv(size + 32) + if len(self.__data)>=(8+size): #prepare data with actual data! should be 8+size+32 + data_recv = self.__data[8:] # test, maybe -32 + else: + data_recv = self.__sock.recv(size + 32) tcp_length = self.__test_tcp_top(data_recv) if tcp_length == 0: print ("Incorrect tcp packet") From 199aa96aebabf187ca2bc8d63bfcf2ff1537083c Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Wed, 6 Jun 2018 17:42:43 -0400 Subject: [PATCH 49/83] try reading a complete chunk --- zk/base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/zk/base.py b/zk/base.py index b4473ae..2c42adc 100644 --- a/zk/base.py +++ b/zk/base.py @@ -1420,7 +1420,10 @@ class ZK(object): for _retries in range(3): command = 1504 #CMD_READ_BUFFER command_string = pack(' Date: Wed, 6 Jun 2018 18:12:22 -0400 Subject: [PATCH 50/83] broken responses on tcp --- zk/base.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/zk/base.py b/zk/base.py index 2c42adc..0ea771a 100644 --- a/zk/base.py +++ b/zk/base.py @@ -387,7 +387,7 @@ class ZK(object): response_size = 1024 cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): - serialnumber = self.__data.split(b'=')[-1].split(b'\x00')[0] + serialnumber = self.__data.split(b'=', 1)[-1].split(b'\x00')[0] return serialnumber.decode() # string? else: raise ZKErrorResponse("can't read serial number") @@ -402,7 +402,7 @@ class ZK(object): cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): - platform = self.__data.split(b'=')[-1].split(b'\x00')[0] + platform = self.__data.split(b'=', 1)[-1].split(b'\x00')[0] return platform.decode() else: raise ZKErrorResponse("can't get platform") @@ -417,7 +417,7 @@ class ZK(object): cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): - mac = self.__data.split(b'=')[-1].split(b'\x00')[0] + mac = self.__data.split(b'=', 1)[-1].split(b'\x00')[0] return mac.decode() else: raise ZKErrorResponse("can't get mac") @@ -432,7 +432,7 @@ class ZK(object): cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): - device = self.__data.split(b'=')[-1].split(b'\x00')[0] + device = self.__data.split(b'=', 1)[-1].split(b'\x00')[0] return device.decode() else: return "" #no name @@ -448,7 +448,7 @@ class ZK(object): cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): - response = self.__data.split(b'=')[-1].split(b'\x00')[0] + response = self.__data.split(b'=', 1)[-1].split(b'\x00')[0] return int(response) if response else 0 else: return None @@ -463,7 +463,7 @@ class ZK(object): cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): - response = self.__data.split(b'=')[-1].split(b'\x00')[0] + response = self.__data.split(b'=', 1)[-1].split(b'\x00')[0] return int(response) if response else 0 else: return None @@ -478,7 +478,7 @@ class ZK(object): cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): - fmt = (self.__data.split(b'=')[-1].split(b'\x00')[0]) + fmt = (self.__data.split(b'=', 1)[-1].split(b'\x00')[0]) #definitivo? seleccionar firmware aqui? return int(fmt) if fmt else 0 else: @@ -494,7 +494,7 @@ class ZK(object): cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): - fmt = (self.__data.split(b'=')[-1].split(b'\x00')[0]) + fmt = (self.__data.split(b'=', 1)[-1].split(b'\x00')[0]) #definitivo? seleccionar firmware aqui? return int(fmt) if fmt else 0 else: @@ -510,7 +510,7 @@ class ZK(object): cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): - response = (self.__data.split(b'=')[-1].split(b'\x00')[0]) + response = (self.__data.split(b'=', 1)[-1].split(b'\x00')[0]) #definitivo? seleccionar firmware aqui? return int(response) if response else 0 else: @@ -526,7 +526,7 @@ class ZK(object): cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): - response = (self.__data.split(b'=')[-1].split(b'\x00')[0]) + response = (self.__data.split(b'=', 1)[-1].split(b'\x00')[0]) #definitivo? seleccionar firmware aqui? return int(response) if response else 0 else: @@ -538,13 +538,13 @@ class ZK(object): gate = b'' cmd_response = self.__send_command(const.CMD_OPTIONS_RRQ, b'IPAddress', 1024) if cmd_response.get('status'): - ip = (self.__data.split(b'=')[-1].split(b'\x00')[0]) + ip = (self.__data.split(b'=', 1)[-1].split(b'\x00')[0]) cmd_response = self.__send_command(const.CMD_OPTIONS_RRQ, b'NetMask', 1024) if cmd_response.get('status'): - mask = (self.__data.split(b'=')[-1].split(b'\x00')[0]) + mask = (self.__data.split(b'=', 1)[-1].split(b'\x00')[0]) cmd_response = self.__send_command(const.CMD_OPTIONS_RRQ, b'GATEIPAddress', 1024) if cmd_response.get('status'): - gate = (self.__data.split(b'=')[-1].split(b'\x00')[0]) + gate = (self.__data.split(b'=', 1)[-1].split(b'\x00')[0]) return {'ip': ip.decode(), 'mask': mask.decode(), 'gateway': gate.decode()} def get_pin_width(self): From 71c77d587811da63237b7140db55691529596ab5 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Fri, 8 Jun 2018 10:17:27 -0400 Subject: [PATCH 51/83] safe int cast for broken packets --- zk/base.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/zk/base.py b/zk/base.py index 0ea771a..43676b8 100644 --- a/zk/base.py +++ b/zk/base.py @@ -12,6 +12,14 @@ from zk.exception import ZKErrorResponse, ZKNetworkError from zk.user import User from zk.finger import Finger +def safe_cast(val, to_type, default=None): + #https://stackoverflow.com/questions/6330071/safe-casting-in-python + try: + return to_type(val) + except (ValueError, TypeError): + return default + + def make_commkey(key, session_id, ticks=50): """take a password and session_id and scramble them to send to the time clock. @@ -449,7 +457,7 @@ class ZK(object): cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): response = self.__data.split(b'=', 1)[-1].split(b'\x00')[0] - return int(response) if response else 0 + return safe_cast(response, int, 0) if response else 0 else: return None @@ -464,7 +472,7 @@ class ZK(object): cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): response = self.__data.split(b'=', 1)[-1].split(b'\x00')[0] - return int(response) if response else 0 + return safe_cast(response, int, 0) if response else 0 else: return None @@ -480,7 +488,7 @@ class ZK(object): if cmd_response.get('status'): fmt = (self.__data.split(b'=', 1)[-1].split(b'\x00')[0]) #definitivo? seleccionar firmware aqui? - return int(fmt) if fmt else 0 + return safe_cast(fmt, int, 0) if fmt else 0 else: raise ZKErrorResponse("can't read extend fmt") @@ -496,7 +504,7 @@ class ZK(object): if cmd_response.get('status'): fmt = (self.__data.split(b'=', 1)[-1].split(b'\x00')[0]) #definitivo? seleccionar firmware aqui? - return int(fmt) if fmt else 0 + return safe_cast(fmt, int, 0) if fmt else 0 else: return None @@ -528,7 +536,7 @@ class ZK(object): if cmd_response.get('status'): response = (self.__data.split(b'=', 1)[-1].split(b'\x00')[0]) #definitivo? seleccionar firmware aqui? - return int(response) if response else 0 + return safe_cast(response, int, 0) if response else 0 else: return None From b6cfd862f9afbbae2bb6bac596505310617aa877 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Mon, 11 Jun 2018 18:28:52 -0400 Subject: [PATCH 52/83] test status vs punch --- test_machine.py | 4 ++-- zk/attendance.py | 3 ++- zk/base.py | 22 +++++++++++----------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/test_machine.py b/test_machine.py index 5d554da..4f37979 100755 --- a/test_machine.py +++ b/test_machine.py @@ -197,7 +197,7 @@ try: i = 0 for att in attendance: i +=1 - print ("ATT {:>6}: uid:{:>3}, user_id:{:>8} t: {}, s:{}".format(i, att.uid, att.user_id, att.timestamp, att.status)) + print ("ATT {:>6}: uid:{:>3}, user_id:{:>8} t: {}, s:{} p:{}".format(i, att.uid, att.user_id, att.timestamp, att.status, att.punch)) print (' took {:.3f}[s]'.format(final - inicio)) print ('') print ('--- sizes & capacity ---') @@ -217,7 +217,7 @@ try: #counter += 1 #enable to implemet a poorman timeout print ("timeout {}".format(counter)) else: - print ("ATT {:>6}: uid:{:>3}, user_id:{:>8} t: {}, s:{}".format(counter, att.uid, att.user_id, att.timestamp, att.status)) + print ("ATT {:>6}: uid:{:>3}, user_id:{:>8} t: {}, s:{} p:{}".format(counter, att.uid, att.user_id, att.timestamp, att.status, att.punch)) if counter >= 10: conn.end_live_capture = True print('') diff --git a/zk/attendance.py b/zk/attendance.py index de11bc2..f21f4da 100644 --- a/zk/attendance.py +++ b/zk/attendance.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- class Attendance(object): - def __init__(self, uid, user_id, timestamp, status): + def __init__(self, uid, user_id, timestamp, status, punch=0): self.uid = uid self.user_id = user_id self.timestamp = timestamp self.status = status + self.punch = punch def __str__(self): return ': {} : {}'.format(self.user_id, self.timestamp) diff --git a/zk/base.py b/zk/base.py index 43676b8..4636b58 100644 --- a/zk/base.py +++ b/zk/base.py @@ -1292,8 +1292,8 @@ class ZK(object): """if len (data) == 5: continue""" - if len(data) == 12: #class 1 attendance - user_id, status, match, timehex = unpack('= 8: - uid, status, timestamp = unpack('HH4s', attendance_data.ljust(8, b'\x00')[:8]) + while len(attendance_data) >= 8:#TODO RETEST ZK6!!! + uid, status, timestamp, punch = unpack('HB4sB', attendance_data.ljust(8, b'\x00')[:8]) if self.verbose: print (codecs.encode(attendance_data[:8], 'hex')) attendance_data = attendance_data[8:] tuser = list(filter(lambda x: x.uid == uid, users)) @@ -1559,8 +1559,8 @@ class ZK(object): attendance = Attendance(uid, user_id, timestamp, status) attendances.append(attendance) elif record_size == 16: # extended - while len(attendance_data) >= 16: - user_id, timestamp, status, verified, reserved, workcode = unpack('= 16: #TODO RETEST ZK6 + user_id, timestamp, status, punch, reserved, workcode = unpack('= 40: - uid, user_id, sparator, timestamp, status, space = unpack(' Date: Tue, 12 Jun 2018 20:34:37 -0400 Subject: [PATCH 53/83] restore Attendance compatibility... simple uid is not very used with Attendance (mostly all works with user_id, and not local uid) so I'm pushing to optional argument as the original pyzk as most users get direcctly all the records with `get_attendance()`, I think no body will notice... --- zk/attendance.py | 4 ++-- zk/base.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/zk/attendance.py b/zk/attendance.py index f21f4da..4d68746 100644 --- a/zk/attendance.py +++ b/zk/attendance.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- class Attendance(object): - def __init__(self, uid, user_id, timestamp, status, punch=0): - self.uid = uid + def __init__(self, user_id, timestamp, status, punch=0, uid=0): + self.uid = uid # not really used any more self.user_id = user_id self.timestamp = timestamp self.status = status diff --git a/zk/base.py b/zk/base.py index 4636b58..7366cc9 100644 --- a/zk/base.py +++ b/zk/base.py @@ -1301,7 +1301,7 @@ class ZK(object): uid = int(user_id) else: uid = tuser[0].uid - yield Attendance(uid, user_id, timestamp, status) + yield Attendance(user_id, timestamp, status, uid=uid)#punch? elif len(data) == 36: #class 2 attendance user_id, status, punch, timehex, res = unpack('<24sBB6sI', data) user_id = (user_id.split(b'\x00')[0]).decode(errors='ignore') @@ -1311,7 +1311,7 @@ class ZK(object): uid = int(user_id) else: uid = tuser[0].uid - yield Attendance(uid, user_id, timestamp, status, punch) + yield Attendance(user_id, timestamp, status, punch, uid) else: if self.verbose: print (codecs.encode(data, 'hex')), len(data) yield codecs.encode(data, 'hex') @@ -1556,7 +1556,7 @@ class ZK(object): else: user_id = tuser[0].user_id timestamp = self.__decode_time(timestamp) - attendance = Attendance(uid, user_id, timestamp, status) + attendance = Attendance(user_id, timestamp, status, uid=uid) # punch? attendances.append(attendance) elif record_size == 16: # extended while len(attendance_data) >= 16: #TODO RETEST ZK6 @@ -1577,7 +1577,7 @@ class ZK(object): else: uid = tuser[0].uid timestamp = self.__decode_time(timestamp) - attendance = Attendance(uid, user_id, timestamp, status, punch) + attendance = Attendance(user_id, timestamp, status, punch, uid) attendances.append(attendance) else: while len(attendance_data) >= 40: @@ -1587,7 +1587,7 @@ class ZK(object): timestamp = self.__decode_time(timestamp) #status = int(status.encode("hex"), 16) - attendance = Attendance(uid, user_id, timestamp, status, punch) + attendance = Attendance(user_id, timestamp, status, punch, uid) attendances.append(attendance) attendance_data = attendance_data[40:] return attendances From 6b102f8a0f69eab346359344507a6aa995f95749 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Tue, 12 Jun 2018 20:38:35 -0400 Subject: [PATCH 54/83] missing punch but need more test on old firmware --- zk/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zk/base.py b/zk/base.py index 7366cc9..42363fd 100644 --- a/zk/base.py +++ b/zk/base.py @@ -1301,7 +1301,7 @@ class ZK(object): uid = int(user_id) else: uid = tuser[0].uid - yield Attendance(user_id, timestamp, status, uid=uid)#punch? + yield Attendance(user_id, timestamp, status, punch, uid)#punch test? elif len(data) == 36: #class 2 attendance user_id, status, punch, timehex, res = unpack('<24sBB6sI', data) user_id = (user_id.split(b'\x00')[0]).decode(errors='ignore') @@ -1556,7 +1556,7 @@ class ZK(object): else: user_id = tuser[0].user_id timestamp = self.__decode_time(timestamp) - attendance = Attendance(user_id, timestamp, status, uid=uid) # punch? + attendance = Attendance(user_id, timestamp, status, punch, uid) # punch? attendances.append(attendance) elif record_size == 16: # extended while len(attendance_data) >= 16: #TODO RETEST ZK6 From 4cedf3ee5b5d0fbb437d422c2fe02385171e297e Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Wed, 13 Jun 2018 10:14:21 -0400 Subject: [PATCH 55/83] clean up the get_sizes function, --- README.md | 21 ++++++++-- zk/base.py | 116 ++++++++++++++--------------------------------------- 2 files changed, 46 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index b022bca..934899f 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,26 @@ pyzk is an unofficial library of zksoftware the fingerprint attendance machine. # Installation -[![Build Status](https://travis-ci.org/fananimi/pyzk.svg?branch=master)](https://travis-ci.org/fananimi/pyzk) +[![Build Status](https://travis-ci.org/kurenai-ryu/pyzk.svg?branch=master)](https://travis-ci.org/kurenai-ryu/pyzk) -`pip install pyzk` +replace original pyzk, if it was installed +`pip install -U git+https://github.com/kurenai-ryu/pyzk.git` + +or clone and execute: +`python setup.py install` + +or in your project, append the path of this project + +```python +import sys +import os +sys.path.insert(1,os.path.abspath("./pyzk")) +from zk import ZK, const +``` # Documentation -Complete documentation can be found at [Readthedocs](http://pyzk.readthedocs.io/en/latest/ "pyzk's readthedocs") . +Complete documentation of the original project can be found at [Readthedocs](http://pyzk.readthedocs.io/en/latest/ "pyzk's readthedocs") . # Api Usage @@ -207,7 +220,7 @@ optional arguments: -r, --records Get attendance records -u, --updatetime Update Date/Time -l, --live-capture Live Event Capture --o, --open-door Open door + -o, --open-door Open door -D DELETEUSER, --deleteuser DELETEUSER Delete a User (uid) diff --git a/zk/base.py b/zk/base.py index 42363fd..76d6920 100644 --- a/zk/base.py +++ b/zk/base.py @@ -153,14 +153,14 @@ class ZK(object): MODIFIED now, without initial checksum ''' #checksum = 0 always? for calculating - buf = pack('HHHH', command, 0, session_id, reply_id) + command_string + buf = pack('<4H', command, 0, session_id, reply_id) + command_string buf = unpack('8B' + '%sB' % len(command_string), buf) checksum = unpack('H', self.__create_checksum(buf))[0] reply_id += 1 if reply_id >= const.USHRT_MAX: reply_id -= const.USHRT_MAX - buf = pack('HHHH', command, checksum, session_id, reply_id) + buf = pack('<4H', command, checksum, session_id, reply_id) return buf + command_string def __create_checksum(self, p): @@ -212,12 +212,12 @@ class ZK(object): self.__tcp_length = self.__test_tcp_top(self.__tcp_data_recv) if self.__tcp_length == 0: raise ZKNetworkError("TCP Packet invalid") - self.__header = unpack('HHHH', self.__tcp_data_recv[8:16]) + self.__header = unpack('<4H', self.__tcp_data_recv[8:16]) self.__data_recv = self.__tcp_data_recv[8:] # dirty hack else: self.__sock.sendto(buf, self.__address) self.__data_recv = self.__sock.recv(response_size) - self.__header = unpack('HHHH', self.__data_recv[:8]) + self.__header = unpack('<4H', self.__data_recv[:8]) except Exception as e: raise ZKNetworkError(str(e)) @@ -296,7 +296,7 @@ class ZK(object): return d def __decode_timehex(self, timehex): """timehex string of six bytes""" - year, month, day, hour, minute, second = unpack("BBBBBB", timehex) + year, month, day, hour, minute, second = unpack("6B", timehex) year += 2000 d = datetime(year, month, day, hour, minute, second) return d @@ -585,25 +585,25 @@ class ZK(object): cmd_response = self.__send_command(command,b'', response_size) if cmd_response.get('status'): if self.verbose: print(codecs.encode(self.__data,'hex')) - size = len(self.__data) - if size == 80: - fields = unpack('iiiiiiiiiiiiiiiiiiii', self.__data) - else: #92? - fields = unpack('iiiiiiiiiiiiiiiiiiiiiii', self.__data[:92]) #dirty hack! we need more information - self.users = fields[4] - self.fingers = fields[6] - self.records = fields[8] - self.dummy = fields[10] #??? - self.cards = fields[12] - self.fingers_cap = fields[14] - self.users_cap = fields[15] - self.rec_cap = fields[16] - self.fingers_av = fields[17] - self.users_av = fields[18] - self.rec_av = fields[19] - if len(fields) > 20: - self.faces = fields[20] - self.faces_cap = fields[22] + size = len(self.__data) + if len(self.__data) >= 80: + fields = unpack('20i', self.__data[:80]) + self.users = fields[4] + self.fingers = fields[6] + self.records = fields[8] + self.dummy = fields[10] #??? + self.cards = fields[12] + self.fingers_cap = fields[14] + self.users_cap = fields[15] + self.rec_cap = fields[16] + self.fingers_av = fields[17] + self.users_av = fields[18] + self.rec_av = fields[19] + self.__data = self.__data[80:] + if len(self.__data) >= 12: #face info + fields = unpack('3i', self.__data[:12]) #dirty hack! we need more information + self.faces = fields[0] + self.faces_cap = fields[2] return True else: raise ZKErrorResponse("can't read sizes") @@ -979,7 +979,7 @@ class ZK(object): return None recieved = len(data_recv) if self.verbose: print ("recieved {}, size {} rec {}".format(recieved, size, data_recv.encode('hex'))) #todo python3 - tcp_length = unpack('HHI', data_recv[:8])[2] #bytes+8 + tcp_length = unpack('= (bytes + 32): #complete - if response == const.CMD_DATA: - resp = data_recv[16:bytes+16] # no ack? - if self.verbose: print ("resp len", len(resp)) - return resp - else: - if self.verbose: print("broken packet!!!") - return '' #broken - else: # incomplete - data.append(data_recv[16:]) # w/o tcp and header - bytes -= recieved-16 - while bytes>0: #jic - data_recv = self.__sock.recv(bytes) #ideal limit? - recieved = len(data_recv) - data.append(data_recv) # w/o tcp and header - bytes -= recieved - data_recv = self.__sock.recv(16) - response = unpack('HHHH', data_recv[8:16])[0] - if response == const.CMD_ACK_OK: - return b''.join(data) - #data_recv[bytes+16:].encode('hex') #included CMD_ACK_OK - if self.verbose: print("bad response %s" % data_recv) - if self.verbose: print (data) - return '' - #else udp - while True: #limitado por respuesta no por tamaƱo - data_recv = self.__sock.recv(response_size) - response = unpack('HHHH', data_recv[:8])[0] - if self.verbose: print ("# packet response is: {}".format(response)) - if response == const.CMD_DATA: - data.append(data_recv[8:]) #header turncated - bytes -= 1024 #UDP - elif response == const.CMD_ACK_OK: - break #without problem. - else: - #truncado! continuar? - if self.verbose: print ("broken!") - break - if self.verbose: print ("still needs %s" % bytes) - return b''.join(data) def read_with_buffer(self, command, fct=0 ,ext=0): """ Test read info with buffered command (ZK6: 1503) """ From b69471f45cab3e26788812d465c013288f319066 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Wed, 13 Jun 2018 15:23:21 -0400 Subject: [PATCH 56/83] added some unittests (and moved basic test) --- basic_test.py | 36 +++++++++++ test.py | 155 ++++++++++++++++++++++++++++++++++++++---------- test_machine.py | 4 +- zk/base.py | 5 +- 4 files changed, 164 insertions(+), 36 deletions(-) create mode 100644 basic_test.py diff --git a/basic_test.py b/basic_test.py new file mode 100644 index 0000000..388b9d0 --- /dev/null +++ b/basic_test.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +import sys +sys.path.append("zk") + +from zk import ZK, const + +conn = None +zk = ZK('192.168.1.201', port=4370, timeout=5, password=0, force_udp=False, ommit_ping=False) +try: + print ('Connecting to device ...') + conn = zk.connect() + print ('Disabling device ...') + conn.disable_device() + print ('Firmware Version: : {}'.format(conn.get_firmware_version())) + # print '--- Get User ---' + users = conn.get_users() + for user in users: + privilege = 'User' + if user.privilege == const.USER_ADMIN: + privilege = 'Admin' + print ('- UID #{}'.format(user.uid)) + print (' Name : {}'.format(user.name)) + print (' Privilege : {}'.format(privilege)) + print (' Password : {}'.format(user.password)) + print (' Group ID : {}'.format(user.group_id)) + print (' User ID : {}'.format(user.user_id)) + + print ("Voice Test ...") + conn.test_voice() + print ('Enabling device ...') + conn.enable_device() +except Exception as e: + print ("Process terminate : {}".format(e)) +finally: + if conn: + conn.disconnect() diff --git a/test.py b/test.py index 388b9d0..1887407 100644 --- a/test.py +++ b/test.py @@ -1,36 +1,129 @@ -# -*- coding: utf-8 -*- import sys -sys.path.append("zk") - +import os +import unittest +import codecs +from mock import patch, Mock, MagicMock +mock_socket = MagicMock(name='zk.socket') +sys.modules['zk.socket'] = mock_socket from zk import ZK, const +from zk.base import ZK_helper +from zk.user import User +from zk.finger import Finger +from zk.attendance import Attendance +from zk.exception import ZKErrorResponse, ZKNetworkError +def dump(obj, nested_level=0, output=sys.stdout): + spacing = ' ' + if type(obj) == dict: + print >> output, '%s{' % ((nested_level) * spacing) + for k, v in obj.items(): + if hasattr(v, '__iter__'): + print >> output, '%s%s:' % ((nested_level + 1) * spacing, k) + dump(v, nested_level + 1, output) + else: + print >> output, '%s%s: %s' % ((nested_level + 1) * spacing, k, v) + print >> output, '%s}' % (nested_level * spacing) + elif type(obj) == list: + print >> output, '%s[' % ((nested_level) * spacing) + for v in obj: + if hasattr(v, '__iter__'): + dump(v, nested_level + 1, output) + else: + print >> output, '%s%s' % ((nested_level + 1) * spacing, v) + print >> output, '%s]' % ((nested_level) * spacing) + else: + print >> output, '%s%s' % (nested_level * spacing, obj) -conn = None -zk = ZK('192.168.1.201', port=4370, timeout=5, password=0, force_udp=False, ommit_ping=False) -try: - print ('Connecting to device ...') - conn = zk.connect() - print ('Disabling device ...') - conn.disable_device() - print ('Firmware Version: : {}'.format(conn.get_firmware_version())) - # print '--- Get User ---' - users = conn.get_users() - for user in users: - privilege = 'User' - if user.privilege == const.USER_ADMIN: - privilege = 'Admin' - print ('- UID #{}'.format(user.uid)) - print (' Name : {}'.format(user.name)) - print (' Privilege : {}'.format(privilege)) - print (' Password : {}'.format(user.password)) - print (' Group ID : {}'.format(user.group_id)) - print (' User ID : {}'.format(user.user_id)) - print ("Voice Test ...") - conn.test_voice() - print ('Enabling device ...') - conn.enable_device() -except Exception as e: - print ("Process terminate : {}".format(e)) -finally: - if conn: +class PYZKTest(unittest.TestCase): + def setup(self): + + pass + + def tearDown(self): + pass + + @patch('zk.base.ZK_helper') + def test_no_ping(self,helper): + """ what if ping doesn't response """ + zk = ZK('192.168.1.201') + helper.assert_called_with('192.168.1.201', 4370) # called correctly + helper.return_value.test_ping.return_value = False #no ping simulated + self.assertRaisesRegexp(ZKNetworkError, "can't reach device", zk.connect) + + @patch('zk.base.ZK_helper') + def test_correct_ping(self,helper): + """ what if ping is ok """ + helper.return_value.test_ping.return_value = True # ping simulated + zk = ZK('192.168.1.201') + helper.assert_called_with('192.168.1.201', 4370) # called correctly + self.assertRaisesRegexp(ZKNetworkError, "unpack requires", zk.connect) # no data...? + + @patch('zk.base.socket') + @patch('zk.base.ZK_helper') + def test_tcp_invalid(self, helper, socket): + """ Basic connection test """ + helper.return_value.test_ping.return_value = True # ping simulated + helper.return_value.test_tcp.return_value = 0 # helper tcp ok + socket.return_value.recv.return_value = 'Invalid tcp data' + #socket.return_value.connect_ex.return_value = 0 # socket tcp ok + #begin + zk = ZK('192.168.1.201') + helper.assert_called_with('192.168.1.201', 4370) # called correctly + self.assertRaisesRegexp(ZKNetworkError, "TCP packet invalid", zk.connect) + + @patch('zk.base.socket') + @patch('zk.base.ZK_helper') + def test_tcp_connect(self, helper, socket): + """ Basic connection test """ + helper.return_value.test_ping.return_value = True # ping simulated + helper.return_value.test_tcp.return_value = 0 # helper tcp ok + socket.return_value.recv.return_value = codecs.decode('5050827d08000000d007fffc2ffb0000','hex') # tcp CMD_ACK_OK + #begin + zk = ZK('192.168.1.201') # already tested + conn = zk.connect() + socket.return_value.send.assert_called_with(codecs.decode('5050827d08000000e80317fc00000000', 'hex')) conn.disconnect() + socket.return_value.send.assert_called_with(codecs.decode('5050827d08000000e903e6002ffb0100', 'hex')) + + @patch('zk.base.socket') + @patch('zk.base.ZK_helper') + def test_force_udp_connect(self, helper, socket): + """ Force UDP connection test """ + helper.return_value.test_ping.return_value = True # ping simulated + helper.return_value.test_tcp.return_value = 0 # helper tcp ok + socket.return_value.recv.return_value = codecs.decode('d007fffc2ffb0000','hex') # tcp CMD_ACK_OK + #begin + zk = ZK('192.168.1.201', force_udp=True) # already tested + conn = zk.connect() + socket.return_value.sendto.assert_called_with(codecs.decode('e80317fc00000000', 'hex'), ('192.168.1.201', 4370)) + conn.disconnect() + socket.return_value.sendto.assert_called_with(codecs.decode('e903e6002ffb0100', 'hex'), ('192.168.1.201', 4370)) + + @patch('zk.base.socket') + @patch('zk.base.ZK_helper') + def test_udp_connect(self, helper, socket): + """ Basic auto UDP connection test """ + helper.return_value.test_ping.return_value = True # ping simulated + helper.return_value.test_tcp.return_value = 1 # helper tcp nope + socket.return_value.recv.return_value = codecs.decode('d007fffc2ffb0000','hex') # tcp CMD_ACK_OK + #begin + zk = ZK('192.168.1.201', verbose=True) # already tested + conn = zk.connect() + conn.disconnect() + + @patch('zk.base.socket') + @patch('zk.base.ZK_helper') + def test_tcp_(self, helper, socket): + """ Basic connection test """ + helper.return_value.test_ping.return_value = True # ping simulated + helper.return_value.test_tcp.return_value = 0 # helper tcp ok + socket.return_value.recv.return_value = codecs.decode('5050827d08000000d007fffc2ffb0000','hex') # tcp CMD_ACK_OK + #begin + zk = ZK('192.168.1.201') # already tested + conn = zk.connect() + conn.disconnect() + + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/test_machine.py b/test_machine.py index 4f37979..4c907ad 100755 --- a/test_machine.py +++ b/test_machine.py @@ -10,10 +10,10 @@ from builtins import input sys.path.append("zk") from zk import ZK, const -from zk.attendance import Attendance -from zk.exception import ZKErrorResponse, ZKNetworkError from zk.user import User from zk.finger import Finger +from zk.attendance import Attendance +from zk.exception import ZKErrorResponse, ZKNetworkError conn = None diff --git a/zk/base.py b/zk/base.py index 76d6920..b27f166 100644 --- a/zk/base.py +++ b/zk/base.py @@ -70,8 +70,8 @@ class ZK_helper(object): """ Returns True if host responds to a ping request """ + print "ping" import subprocess, platform - # Ping parameters as function of OS ping_str = "-n 1" if platform.system().lower()=="windows" else "-c 1 -W 5" args = "ping " + " " + ping_str + " " + self.ip @@ -202,7 +202,6 @@ class ZK(object): ''' send command to the terminal ''' - buf = self.__create_header(command, command_string, self.__session_id, self.__reply_id) try: if self.tcp: @@ -211,7 +210,7 @@ class ZK(object): self.__tcp_data_recv = self.__sock.recv(response_size + 8) self.__tcp_length = self.__test_tcp_top(self.__tcp_data_recv) if self.__tcp_length == 0: - raise ZKNetworkError("TCP Packet invalid") + raise ZKNetworkError("TCP packet invalid") self.__header = unpack('<4H', self.__tcp_data_recv[8:16]) self.__data_recv = self.__tcp_data_recv[8:] # dirty hack else: From 8063077cd126b5122cf2f60c64954653fa9fb6cd Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Thu, 14 Jun 2018 09:43:23 -0400 Subject: [PATCH 57/83] remove test print --- zk/base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/zk/base.py b/zk/base.py index b27f166..a86f25d 100644 --- a/zk/base.py +++ b/zk/base.py @@ -70,7 +70,6 @@ class ZK_helper(object): """ Returns True if host responds to a ping request """ - print "ping" import subprocess, platform # Ping parameters as function of OS ping_str = "-n 1" if platform.system().lower()=="windows" else "-c 1 -W 5" From e78bce1793a4645d3cddf10b1eb06640bf4a202a Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Thu, 14 Jun 2018 21:12:40 -0400 Subject: [PATCH 58/83] added some test --- test.py | 125 ++++++++++++++++++++++++++++++++++++++++++++++++----- zk/base.py | 2 +- 2 files changed, 116 insertions(+), 11 deletions(-) diff --git a/test.py b/test.py index 1887407..b4f8a37 100644 --- a/test.py +++ b/test.py @@ -45,15 +45,17 @@ class PYZKTest(unittest.TestCase): @patch('zk.base.ZK_helper') def test_no_ping(self,helper): """ what if ping doesn't response """ + helper.return_value.test_ping.return_value = False #no ping simulated + #begin zk = ZK('192.168.1.201') helper.assert_called_with('192.168.1.201', 4370) # called correctly - helper.return_value.test_ping.return_value = False #no ping simulated self.assertRaisesRegexp(ZKNetworkError, "can't reach device", zk.connect) @patch('zk.base.ZK_helper') def test_correct_ping(self,helper): """ what if ping is ok """ helper.return_value.test_ping.return_value = True # ping simulated + #begin zk = ZK('192.168.1.201') helper.assert_called_with('192.168.1.201', 4370) # called correctly self.assertRaisesRegexp(ZKNetworkError, "unpack requires", zk.connect) # no data...? @@ -65,7 +67,6 @@ class PYZKTest(unittest.TestCase): helper.return_value.test_ping.return_value = True # ping simulated helper.return_value.test_tcp.return_value = 0 # helper tcp ok socket.return_value.recv.return_value = 'Invalid tcp data' - #socket.return_value.connect_ex.return_value = 0 # socket tcp ok #begin zk = ZK('192.168.1.201') helper.assert_called_with('192.168.1.201', 4370) # called correctly @@ -93,7 +94,7 @@ class PYZKTest(unittest.TestCase): helper.return_value.test_tcp.return_value = 0 # helper tcp ok socket.return_value.recv.return_value = codecs.decode('d007fffc2ffb0000','hex') # tcp CMD_ACK_OK #begin - zk = ZK('192.168.1.201', force_udp=True) # already tested + zk = ZK('192.168.1.201', force_udp=True) conn = zk.connect() socket.return_value.sendto.assert_called_with(codecs.decode('e80317fc00000000', 'hex'), ('192.168.1.201', 4370)) conn.disconnect() @@ -107,23 +108,127 @@ class PYZKTest(unittest.TestCase): helper.return_value.test_tcp.return_value = 1 # helper tcp nope socket.return_value.recv.return_value = codecs.decode('d007fffc2ffb0000','hex') # tcp CMD_ACK_OK #begin - zk = ZK('192.168.1.201', verbose=True) # already tested + zk = ZK('192.168.1.201') conn = zk.connect() + socket.return_value.sendto.assert_called_with(codecs.decode('e80317fc00000000', 'hex'), ('192.168.1.201', 4370)) + conn.disconnect() + socket.return_value.sendto.assert_called_with(codecs.decode('e903e6002ffb0100', 'hex'), ('192.168.1.201', 4370)) + + @patch('zk.base.socket') + @patch('zk.base.ZK_helper') + def test_tcp_unauth(self, helper, socket): + """ Basic auth test """ + helper.return_value.test_ping.return_value = True # ping simulated + helper.return_value.test_tcp.return_value = 0 # helper tcp ok + socket.return_value.recv.side_effect = [ + codecs.decode('5050827d08000000d5075bb2cf450000', 'hex'), # tcp CMD_UNAUTH + codecs.decode('5050827d08000000d5075ab2cf450100', 'hex') # tcp CMD_UNAUTH + ] + #begin + zk = ZK('192.168.1.201', password=12) + self.assertRaisesRegexp(ZKErrorResponse, "Unauthenticated", zk.connect) + socket.return_value.send.assert_called_with(codecs.decode('5050827d0c0000004e044e2ccf450100614d323c', 'hex')) # try with password 12 + + @patch('zk.base.socket') + @patch('zk.base.ZK_helper') + def test_tcp_auth(self, helper, socket): + """ Basic auth test """ + helper.return_value.test_ping.return_value = True # ping simulated + helper.return_value.test_tcp.return_value = 0 # helper tcp ok + socket.return_value.recv.side_effect = [ + codecs.decode('5050827d08000000d5075bb2cf450000', 'hex'), # tcp CMD_UNAUTH + codecs.decode('5050827d08000000d0075fb2cf450100', 'hex'), # tcp CMD_ACK_OK + codecs.decode('5050827d08000000d00745b2cf451b00', 'hex') # tcp random CMD_ACK_OK TODO: generate proper sequenced response + + ] + #begin + zk = ZK('192.168.1.201', password=45) + conn = zk.connect() + socket.return_value.send.assert_called_with(codecs.decode('5050827d0c0000004e044db0cf45010061c9323c', 'hex')) #auth with pass 45 + conn.disconnect() + socket.return_value.send.assert_called_with(codecs.decode('5050827d08000000e90345b6cf450200', 'hex')) #exit + + @patch('zk.base.socket') + @patch('zk.base.ZK_helper') + def test_tcp_get_size(self, helper, socket): + """ can read sizes? """ + helper.return_value.test_ping.return_value = True # ping simulated + helper.return_value.test_tcp.return_value = 0 # helper tcp ok + socket.return_value.recv.side_effect = [ + codecs.decode('5050827d08000000d0075fb2cf450100', 'hex'), # tcp CMD_ACK_OK + codecs.decode('5050827d64000000d007a3159663130000000000000000000000000000000000070000000000000006000000000000005d020000000000000f0c0000000000000100000000000000b80b000010270000a0860100b20b00000927000043840100000000000000', 'hex'), #sizes + codecs.decode('5050827d08000000d00745b2cf451b00', 'hex'), # tcp random CMD_ACK_OK TODO: generate proper sequenced response + ] + #begin + zk = ZK('192.168.1.201') # already tested + conn = zk.connect() + socket.return_value.send.assert_called_with(codecs.decode('5050827d08000000e80317fc00000000', 'hex')) + conn.read_sizes() + socket.return_value.send.assert_called_with(codecs.decode('5050827d080000003200fcb9cf450200', 'hex')) + conn.disconnect() + self.assertEqual(conn.users, 7, "missed user data %s" % conn.users) + self.assertEqual(conn.fingers, 6, "missed finger data %s" % conn.fingers) + self.assertEqual(conn.records, 605, "missed record data %s" % conn.records) + self.assertEqual(conn.users_cap, 10000, "missed user cap %s" % conn.users_cap) + self.assertEqual(conn.fingers_cap, 3000, "missed finger cap %s" % conn.fingers_cap) + self.assertEqual(conn.rec_cap, 100000, "missed record cap %s" % conn.rec_cap) + + @patch('zk.base.socket') + @patch('zk.base.ZK_helper') + def test_tcp_get_users_small(self, helper, socket): + """ can get empty? """ + helper.return_value.test_ping.return_value = True # ping simulated + helper.return_value.test_tcp.return_value = 0 # helper tcp ok + socket.return_value.recv.side_effect = [ + codecs.decode('5050827d08000000d0075fb2cf450100', 'hex'), # tcp CMD_ACK_OK + codecs.decode('5050827d64000000d007a3159663130000000000000000000000000000000000070000000000000006000000000000005d020000000000000f0c0000000000000100000000000000b80b000010270000a0860100b20b00000927000043840100000000000000', 'hex'), #sizes + codecs.decode('5050827d04020000dd05942c96631500f801000001000e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003830380000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003832310000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003833350000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003833310000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003833320000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003836000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000383432000000000000000000000000000000000000000000','hex'), #DATA directly(not ok) + codecs.decode('5050827d08000000d00745b2cf451b00', 'hex'), # tcp random CMD_ACK_OK TODO: generate proper sequenced response + #codecs.decode('5050827d08000000d00745b2cf451b00', 'hex') # tcp random CMD_ACK_OK TODO: generate proper sequenced response + ] + #begin + zk = ZK('192.168.1.201' ) + conn = zk.connect() + socket.return_value.send.assert_called_with(codecs.decode('5050827d08000000e80317fc00000000', 'hex')) + users = conn.get_users() + socket.return_value.send.assert_called_with(codecs.decode('5050827d13000000df053ca6cf4514000109000500000000000000', 'hex')) #get users + self.assertEqual(len(users), 7, "incorrect size %s" % len(users)) + #assert one user + usu = users[3] + self.assertIsInstance(usu.uid, int, "uid should be int() %s" % type(usu.uid)) + self.assertIsInstance(usu.user_id, (str, unicode), "user_id should be str() or unicode() %s" % type(usu.user_id)) + self.assertEqual(usu.uid, 4, "incorrect uid %s" % usu.uid) + self.assertEqual(usu.user_id, "831", "incorrect user_id %s" % usu.user_id) + self.assertEqual(usu.name, "NN-831", "incorrect uid %s" % usu.name) # generated conn.disconnect() @patch('zk.base.socket') @patch('zk.base.ZK_helper') - def test_tcp_(self, helper, socket): - """ Basic connection test """ + def _test_tcp_get_template(self, helper, socket): + """ can get empty? """ helper.return_value.test_ping.return_value = True # ping simulated helper.return_value.test_tcp.return_value = 0 # helper tcp ok - socket.return_value.recv.return_value = codecs.decode('5050827d08000000d007fffc2ffb0000','hex') # tcp CMD_ACK_OK + socket.return_value.recv.side_effect = [ + codecs.decode('5050827d08000000d0075fb2cf450100', 'hex'), # tcp CMD_ACK_OK + codecs.decode('5050827d15000000d007acf93064160000941d0000941d0000b400be00', 'hex'), # ack ok with size 7572 + codecs.decode('5050827d10000000dc05477830641700941d000000000100', 'hex'), #prepare data + codecs.decode('5050827d08000000d00745b2cf451b00', 'hex'), # tcp random CMD_ACK_OK TODO: generate proper sequenced response + #codecs.decode('5050827d08000000d00745b2cf451b00', 'hex') # tcp random CMD_ACK_OK TODO: generate proper sequenced response + ] #begin - zk = ZK('192.168.1.201') # already tested + zk = ZK('192.168.1.201', verbose=True) conn = zk.connect() + socket.return_value.send.assert_called_with(codecs.decode('5050827d08000000e80317fc00000000', 'hex')) + templates = conn.get_templates() + self.assertEqual(len(templates), 6, "incorrect size %s" % len(templates)) + #assert one user + usu = users[3] + self.assertIsInstance(usu.uid, int, "uid should be int() %s" % type(usu.uid)) + self.assertIsInstance(usu.user_id, (str, unicode), "user_id should be str() or unicode() %s" % type(usu.user_id)) + self.assertEqual(usu.uid, 4, "incorrect uid %s" % usu.uid) + self.assertEqual(usu.user_id, "831", "incorrect user_id %s" % usu.user_id) + self.assertEqual(usu.name, "NN-831", "incorrect uid %s" % usu.name) # generated conn.disconnect() - - if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/zk/base.py b/zk/base.py index a86f25d..29baa14 100644 --- a/zk/base.py +++ b/zk/base.py @@ -1341,7 +1341,7 @@ class ZK(object): def __recieve_chunk(self): """ recieve a chunk """ if self.__response == const.CMD_DATA: # less than 1024!!! - if self.verbose: print ("size was {} len is {}".format(size, len(self.__data))) + if self.verbose: print ("_rc len is {}".format(len(self.__data))) return self.__data #without headers elif self.__response == const.CMD_PREPARE_DATA: data = [] From 850bfe578f669100beacba80fd5fbd6b92cd47e8 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Mon, 2 Jul 2018 16:25:51 -0400 Subject: [PATCH 59/83] fixed self connect detections --- zk/base.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zk/base.py b/zk/base.py index 29baa14..4e7d876 100644 --- a/zk/base.py +++ b/zk/base.py @@ -131,6 +131,10 @@ class ZK(object): self.__data_recv = None self.__data = None + def __nonzero__(self): + """ for boolean test""" + return self.is_connect + def __create_socket(self): """ based on self.tcp""" if self.tcp: @@ -342,9 +346,9 @@ class ZK(object): ''' diconnect from the connected device ''' + self.is_connect = False cmd_response = self.__send_command(const.CMD_EXIT) if cmd_response.get('status'): - self.is_connect = False if self.__sock: self.__sock.close() #leave to GC return True From ff7b4902c2f508af67723399c15dcd41227fd8d6 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Thu, 5 Jul 2018 11:52:36 -0400 Subject: [PATCH 60/83] force int values on finger --- zk/finger.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zk/finger.py b/zk/finger.py index 2b38fd4..994cec9 100644 --- a/zk/finger.py +++ b/zk/finger.py @@ -4,9 +4,9 @@ import codecs class Finger(object): def __init__(self, uid, fid, valid, template): self.size = len(template) # template only - self.uid = uid - self.fid = fid - self.valid = valid + self.uid = int(uid) + self.fid = int(fid) + self.valid = int(valid) self.template = template #self.mark = str().encode("hex") self.mark = codecs.encode(template[:8], 'hex') + b'...' + codecs.encode(template[-8:], 'hex') From 46560b451140e5f78d32bbcb5e15645bf003ab5e Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Fri, 20 Jul 2018 19:47:36 -0400 Subject: [PATCH 61/83] added test case and fixed incomplete data --- test.py | 69 +++++++++++++++- zk/base.py | 230 ++++++++++++++++++++++------------------------------- 2 files changed, 164 insertions(+), 135 deletions(-) diff --git a/test.py b/test.py index b4f8a37..4b03a8b 100644 --- a/test.py +++ b/test.py @@ -201,6 +201,73 @@ class PYZKTest(unittest.TestCase): self.assertEqual(usu.user_id, "831", "incorrect user_id %s" % usu.user_id) self.assertEqual(usu.name, "NN-831", "incorrect uid %s" % usu.name) # generated conn.disconnect() + + @patch('zk.base.socket') + @patch('zk.base.ZK_helper') + def test_tcp_get_users_broken_tcp(self, helper, socket): + """ tst case for https://github.com/fananimi/pyzk/pull/18#issuecomment-406250746 """ + helper.return_value.test_ping.return_value = True # ping simulated + helper.return_value.test_tcp.return_value = 0 # helper tcp ok + socket.return_value.recv.side_effect = [ + codecs.decode('5050827d09000000d007babb5c3c100009', 'hex'), # tcp CMD_ACK_OK + codecs.decode('5050827d58000000d007292c5c3c13000000000000000000000000000000000046000000000000004600000000000000990c0000000000001a010000000000000600000006000000f4010000f401000050c30000ae010000ae010000b7b60000', 'hex'), #sizes + codecs.decode('5050827d15000000d007a7625c3c150000b4130000b4130000cdef2300','hex'), #PREPARE_BUFFER -> OK 5044 + codecs.decode('5050827d10000000dc050da65c3c1600b4130000f0030000', 'hex'), # read_buffer -> Prepare_data 5044 + codecs.decode('5050827df8030000dd05d05800001600b013000001000e35313437393833004a6573757353616c646976617200000000000000000000000000000001000000000000000035313437393833000000000000000000000000000000000002000e33343934383636004e69657665734c6f70657a00000000000000000000000000000000000100000000000000003334393438363600000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003337333139333600000000000000000000000000', 'hex'), # DATA 1016 -8 (util 216) + codecs.decode('0000000100000000000000003734383433330000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003433333939353800000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003333373335313100000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003337373535363100000000000000000000000000000000000b000000', 'hex'), # raw data 256 + codecs.decode('0000000004000e00000000000000000000000000000000000000000000000000000000000000000000000001000000000000000032333338323035000000000000000000000000000000000005000e000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000333632363439300000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000313838343633340000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000000000000', 'hex'), #raw data 256 + codecs.decode('00000000000000000000000000000000000000000000000000000000000000000000000100000000000000003131313336333200000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003130353233383900000000000000000000000000000000000d00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003135333538333600000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000000000000000100000000', 'hex'), #raw data 256 + codecs.decode('000000003933313637300000000000000000000000000000', 'hex'), #raw data 24 + + codecs.decode('5050827df8030000dd0520b601001600000000000f00003334323931343800000000000000000000000000000000000000000000000000000000000100000000000000003334323931343800000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003139303636393700000000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003139333831333500000000000000000000000000', 'hex'), # DATA 1016 -8 (util216 + codecs.decode('00000000120000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000393231303537000000000000000000000000000000000000130000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000333634383739340000000000000000000000000000000000140000323831353732000000000000000000000000000000000000000000000000000000000000010000000000000000323831353732000000000000000000000000000000000000150000000000000000000000000000000000000000000000000000000000000000000000', 'hex'), #raw data 256 + codecs.decode('00000001000000000000000031383133323236000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000035393037353800000000000000000000000000000000000017000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000031363933373232000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000033363430323131000000000000000000000000000000000019000000', 'hex'), #raw data 256 + codecs.decode('00000000000000000000000000000000000000000000000000000000000000000000000100000000000000003331303733390000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003433353430393400000000000000000000000000000000001b00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003338303736333200000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000000000000000000100000000', 'hex'), #raw data 256 + codecs.decode('000000003231333938313700000000000000000000000000', 'hex'), #raw data 24 + + codecs.decode('5050827df8030000dd059a2102001600000000001d00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003333383738313900000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003439353634363800000000000000000000000000000000001f00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003832343030300000000000000000000000000000', 'hex'), #DATA 1016 -8 (util 216) + codecs.decode('00000000200000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000333937373437370000000000000000000000000000000000210000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000343435383038340000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000343430353130390000000000000000000000000000000000230000000000000000000000000000000000000000000000000000000000000000000000', 'hex'), #raw data 256 + codecs.decode('00000001000000000000000033353732363931000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000033363336333832000000000000000000000000000000000025000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000033333232353432000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000039393437303800000000000000000000000000000000000027000000', 'hex'), #raw data 256 + codecs.decode('00000000000000000000000000000000000000000000000000000000000000000000000100000000000000003836333539380000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003338383736383000000000000000000000000000000000002900000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003739393434350000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000000000000000000100000000', 'hex'), # raw data 256 + codecs.decode('000000003532313136340000000000000000000000000000', 'hex'), # raw data 24 + + codecs.decode('5050827df8030000dd053da903001600000000002b00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003439373033323400000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000000000000', 'hex'), # DATA 1016 -8 (util 112) + codecs.decode('0000000100000000000000003134363732353100000000000000000000000000000000002d000e32363635373336006d61726368756b0000000000000000000000000000000000000000000100000000000000003236363537333600000000000000000000000000', 'hex'), # raw data 104 + codecs.decode('000000002e00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003136383133353200000000000000000000000000000000002f000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000034393633363732000000000000000000000000000000000030000000', 'hex'), # raw data 152 + codecs.decode('00000000000000000000000000000000000000000000000000000000000000000000000100000000000000003337363137373100000000000000000000000000000000003100000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003231353939353100000000000000000000000000000000003200000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003136393734323700000000000000000000000000000000003300000000000000000000000000000000000000000000000000000000000000000000000000000100000000', 'hex'), # raw data 256 + codecs.decode('0000000033373336323437000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000031323930313635000000000000000000000000000000000035000000000000000000000000000000000000000000000000000000', 'hex'), # raw data 128 + codecs.decode('0000000000000000000000010000000000000000333236333636330000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000393031353036000000000000000000000000000000000000370000000000000000000000', 'hex'), # raw data 128 + codecs.decode('0000000000000000000000000000000000000000000000000000000100000000000000003238313732393300000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003437303630333800000000000000000000000000', 'hex'), # raw data 128 + + codecs.decode('5050827df8030000dd05037d04001600000000003900000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003136343731353600000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000000000', 'hex'), # DATA 1016 -8 (util 112) + codecs.decode('0000000100000000000000003530313435310000000000000000000000000000000000003b00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003534363236373300000000000000000000000000000000003c00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003533363730310000000000000000000000000000000000003d00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003435383033303700000000000000000000000000000000003e000000', 'hex'), # raw data 256 + codecs.decode('00000000000000000000000000000000000000000000000000000000000000000000000100000000000000003136333835333200000000000000000000000000000000003f000e3336323634313900000000000000000000000000000000000000000000000000000000000100000000000000003336323634313900000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003233323331383500000000000000000000000000000000004100000000000000000000000000000000000000000000000000000000000000000000000000000100000000', 'hex'), # raw data 256 + codecs.decode('0000000035323930373337000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000033393839303636000000000000000000000000000000000043000000000000000000000000000000000000000000000000000000', 'hex'), # raw data 128 + codecs.decode('0000000000000000000000010000000000000000343033323930390000000000000000000000000000000000440000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000323034363338380000000000000000000000000000000000450000000000000000000000', 'hex'), # raw data 128 + codecs.decode('0000000000000000000000000000000000000000000000000000000100000000000000003733383730330000000000000000000000000000000000004600000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003239313836333600000000000000000000000000', 'hex'), # raw data 128 + + codecs.decode('5050827d0c000000dd0507fa0500160000000000', 'hex'), # DATA 12-8 (util 4 ok) and ACK OK!!! + + codecs.decode('5050827d08000000d00745b2cf451b00', 'hex'), # CMD_ACK_OK for get_users TODO: generate proper sequenced response + codecs.decode('5050827d08000000d00745b2cf451b00', 'hex'), # CMD_ACK_OK for free_data TODO: generate proper sequenced response + codecs.decode('5050827d08000000d00745b2cf451b00', 'hex'), # CMD_ACK_OK for exit TODO: generate proper sequenced response + ] + #begin + zk = ZK('192.168.1.201') # , verbose=True) + conn = zk.connect() + socket.return_value.send.assert_called_with(codecs.decode('5050827d08000000e80317fc00000000', 'hex')) + users = conn.get_users() + #print (users) #debug + socket.return_value.send.assert_called_with(codecs.decode('5050827d08000000de05aebd5c3c1700', 'hex')) #get users + self.assertEqual(len(users), 70, "incorrect size %s" % len(users)) + #assert one user + usu = users[1] + self.assertIsInstance(usu.uid, int, "uid should be int() %s" % type(usu.uid)) + self.assertIsInstance(usu.user_id, (str, unicode), "user_id should be str() or unicode() %s" % type(usu.user_id)) + self.assertEqual(usu.uid, 2, "incorrect uid %s" % usu.uid) + self.assertEqual(usu.user_id, "3494866", "incorrect user_id %s" % usu.user_id) + self.assertEqual(usu.name, "NievesLopez", "incorrect uid %s" % usu.name) # check test case + conn.disconnect() @patch('zk.base.socket') @patch('zk.base.ZK_helper') @@ -231,4 +298,4 @@ class PYZKTest(unittest.TestCase): conn.disconnect() if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/zk/base.py b/zk/base.py index 4e7d876..d6b125d 100644 --- a/zk/base.py +++ b/zk/base.py @@ -958,88 +958,6 @@ class ZK(object): else: if self.verbose: print ("can't read/find finger") return None - #----------------------------------------------------------------- - if not cmd_response.get('status'): - return None #("can't get user template") - #else - if cmd_response.get('code') == const.CMD_DATA: # less than 1024!!! - resp = self.__data[:-1] - if self.tcp: - if resp[-6:] == b'\x00\x00\x00\x00\x00\x00': # padding? bug? - resp = resp[:-6] - if self.verbose: print("tcp too small!") - return Finger(uid, temp_id, 1, resp) - if cmd_response.get('code') == const.CMD_PREPARE_DATA: - data = [] - bytes = self.__get_data_size() #TODO: check with size - size = bytes - if self.tcp: - data_recv = self.__sock.recv(bytes + 32) - tcp_length = self.__test_tcp_top(data_recv) - if tcp_length == 0: - print ("Incorrect tcp packet") - return None - recieved = len(data_recv) - if self.verbose: print ("recieved {}, size {} rec {}".format(recieved, size, data_recv.encode('hex'))) #todo python3 - tcp_length = unpack('= (bytes + 32): #complete - if response == const.CMD_DATA: - resp = data_recv[16:bytes+16][:size-1] # no ack? - if resp[-6:] == b'\x00\x00\x00\x00\x00\x00': # padding? bug? - if self.verbose: print ("cutting from",len(resp)) - resp = resp[:-6] - if self.verbose: print ("resp len1", len(resp)) - return Finger(uid, temp_id, 1, resp) #mistery - else: - if self.verbose: print("broken packet!!!") - return None #broken - else: # incomplete - if self.verbose: print ("try incomplete") - data.append(data_recv[16:]) # w/o tcp and header - bytes -= recieved-16 - while bytes>0: #jic - data_recv = self.__sock.recv(bytes) #ideal limit? - recieved = len(data_recv) - if self.verbose: print ("partial recv {}".format(recieved)) - data.append(data_recv) # w/o tcp and header - bytes -= recieved - data_recv = self.__sock.recv(16) - response = unpack('<4H', data_recv[8:16])[0] - if response == const.CMD_ACK_OK: - resp = b''.join(data)[:size-1] # testing - if resp[-6:] == b'\x00\x00\x00\x00\x00\x00': - resp = resp[:-6] - if self.verbose: print ("resp len2", len(resp)) - return Finger(uid, temp_id, 1, resp) - #data_recv[bytes+16:].encode('hex') #included CMD_ACK_OK - if self.verbose: print("bad response %s" % data_recv) - if self.verbose: print(data) - return None - #else udp - size = bytes - while True: #limitado por respuesta no por tamaƱo - data_recv = self.__sock.recv(response_size) - response = unpack('<4H', data_recv[:8])[0] - if self.verbose: print("# packet response is: {}".format(response)) - if response == const.CMD_DATA: - data.append(data_recv[8:]) #header turncated - bytes -= 1024 - elif response == const.CMD_ACK_OK: - break #without problem. - else: - #truncado! continuar? - if self.verbose: print("broken!") - break - if self.verbose: print("still needs %s" % bytes) - data = b''.join(data)[:-1] - if data[-6:] == b'\x00\x00\x00\x00\x00\x00': # padding? bug? - data = data[:-6] - #uid 32 fing 03, starts with 4d-9b-53-53-32-31 - #CMD_USERTEMP_RRQ desn't need [:-1] - return Finger(uid, temp_id, 1, data) def get_templates(self): """ return array of all fingers """ @@ -1073,7 +991,7 @@ class ZK(object): users = [] max_uid = 0 userdata, size = self.read_with_buffer(const.CMD_USERTEMP_RRQ, const.FCT_USER) - if self.verbose: print("user size %i" % size) + if self.verbose: print("user size {} (= {})".format(size, len(userdata))) if size <= 4: print("WRN: missing user data")# debug return [] @@ -1342,6 +1260,73 @@ class ZK(object): else: raise ZKErrorResponse("can't clear data") + def __recieve_tcp_data(self, data_recv, size): + """ data_recv, raw tcp packet + must analyze tcp_length + + must return data, broken + """ + data = [] + tcp_length = self.__test_tcp_top(data_recv) # tcp header + if self.verbose: print ("tcp_length {}, size {}".format(tcp_length, size)) + if tcp_length <= 0: + if self.verbose: print ("Incorrect tcp packet") + return None, b"" + if (tcp_length - 8) < size: #broken on smaller DATAs + if self.verbose: print ("tcp length too small... retrying") + resp, bh = self.__recieve_tcp_data(data_recv, tcp_length - 8) + data.append(resp) + size -= len(resp) + if self.verbose: print ("new tcp DATA packet to fill misssing {}".format(size)) + data_recv = bh + self.__sock.recv(size) #ideal limit? + resp, bh = self.__recieve_tcp_data(data_recv, size) + data.append(resp) + if self.verbose: print ("for misssing {} recieved {} with extra {}".format(size, len(resp), len(bh))) + return b''.join(data), bh + recieved = len(data_recv) + if self.verbose: print ("recieved {}, size {}".format(recieved, size)) + #if (tcp_length - 16) > (recieved - 16): #broken first DATA + # #reparse as more data packets? + # if self.verbose: print ("trying".format(recieved, size)) + # _data, bh = self.__recieve_tcp_data(data_recv, tcp_length-16) + #analize first response + response = unpack('HHHH', data_recv[8:16])[0] + if recieved >= (size + 32): #complete with ACK_OK included + if response == const.CMD_DATA: + resp = data_recv[16 : size + 16] # no ack? + if self.verbose: print ("resp complete len {}".format(len(resp))) + return resp, data_recv[size + 16:] + else: + if self.verbose: print("incorrect response!!! {}".format(response)) + return None, b"" #broken + else: # response DATA incomplete (or missing ACK_OK) + if self.verbose: print ("try DATA incomplete (actual valid {})".format(recieved-16)) + data.append(data_recv[16 : size + 16 ]) # w/o DATA tcp and header + size -= recieved - 16 # w/o DATA tcp and header + broken_header = b"" + if size < 0: #broken ack header? + broken_header = data_recv[size:] + if self.verbose: print ("broken", (broken_header).encode('hex')) #TODO python3 + if size > 0: #need raw data to complete + data_recv = self.__recieve_raw_data(size) + data.append(data_recv) # w/o tcp and header + return b''.join(data), broken_header + #get cmd_ack_ok on __rchunk + + + def __recieve_raw_data(self, size): + """ partial data ? """ + data = [] + if self.verbose: print ("expecting {} bytes raw data".format(size)) + while size > 0: + data_recv = self.__sock.recv(size) #ideal limit? + recieved = len(data_recv) + if self.verbose: print ("partial recv {}".format(recieved)) + data.append(data_recv) # w/o tcp and header + size -= recieved + if self.verbose: print ("still need {}".format(size)) + return b''.join(data) + def __recieve_chunk(self): """ recieve a chunk """ if self.__response == const.CMD_DATA: # less than 1024!!! @@ -1350,61 +1335,36 @@ class ZK(object): elif self.__response == const.CMD_PREPARE_DATA: data = [] size = self.__get_data_size() - if self.verbose: print ("recieve chunk:data size is", size) + if self.verbose: print ("recieve chunk: prepare data size is {}".format(size)) if self.tcp: - if len(self.__data)>=(8+size): #prepare data with actual data! should be 8+size+32 + if len(self.__data) >= (8 + size): #prepare data with actual data! should be 8+size+32 data_recv = self.__data[8:] # test, maybe -32 else: - data_recv = self.__sock.recv(size + 32) - tcp_length = self.__test_tcp_top(data_recv) - if tcp_length == 0: - print ("Incorrect tcp packet") - return None - recieved = len(data_recv) - if self.verbose: print ("recieved {}, size {}".format(recieved, size)) - if tcp_length < (size + 8): - if self.verbose: print ("request chunk too big!") - response = unpack('HHHH', data_recv[8:16])[0] - if recieved >= (size + 32): #complete with ACK_OK included - if response == const.CMD_DATA: - resp = data_recv[16 : size + 16] # no ack? - if self.verbose: print ("resp complete len", len(resp)) - return resp - else: - if self.verbose: print("broken packet!!! {}".format(response)) - return None #broken - else: # incomplete - if self.verbose: print ("try incomplete (actual valid {})".format(recieved-16)) - data.append(data_recv[16 : size+ 16 ]) # w/o DATA tcp and header - size -= recieved-16 # w/o DATA tcp and header - broken_header = b"" - if size < 0: #broken ack header? - broken_header = data_recv[size:] - if self.verbose: print ("broken", (broken_header).encode('hex')) #TODO python3 - while size>0: #jic - if self.verbose: print ("still need {}".format(size)) - data_recv = self.__sock.recv(size) #ideal limit? - recieved = len(data_recv) - if self.verbose: print ("partial recv {}".format(recieved)) - data.append(data_recv) # w/o tcp and header - size -= recieved - #get cmd_ack_ok + data_recv = self.__sock.recv(size + 32) #could have two commands + resp, broken_header = self.__recieve_tcp_data(data_recv, size) + data.append(resp) + # get CMD_ACK_OK + if len(broken_header) < 16: data_recv = broken_header + self.__sock.recv(16) - #could be broken - if len(data_recv) < 16: - print ("trying to complete broken ACK %s /16" % len(data_recv)) - if self.verbose: print (data_recv.encode('hex')) #todo python3 - data_recv += self.__sock.recv(16 - len(data_recv)) #TODO: CHECK HERE_! - if not self.__test_tcp_top(data_recv): - if self.verbose: print ("invalid tcp ACK OK") - return None #b''.join(data) # incomplete? - response = unpack('HHHH', data_recv[8:16])[0] - if response == const.CMD_ACK_OK: - return b''.join(data) - #data_recv[bytes+16:].encode('hex') #included CMD_ACK_OK - if self.verbose: print("bad response %s" % data_recv) - if self.verbose: print (data) - return None + else: + data_recv = broken_header + #could be broken + if len(data_recv) < 16: + print ("trying to complete broken ACK %s /16" % len(data_recv)) + if self.verbose: print (data_recv.encode('hex')) #todo python3 + data_recv += self.__sock.recv(16 - len(data_recv)) #TODO: CHECK HERE_! + if not self.__test_tcp_top(data_recv): + if self.verbose: print ("invalid chunk tcp ACK OK") + return None #b''.join(data) # incomplete? + response = unpack('HHHH', data_recv[8:16])[0] + if response == const.CMD_ACK_OK: + if self.verbose: print ("chunk tcp ACK OK!") + return b''.join(data) + if self.verbose: print("bad response %s" % data_recv) + if self.verbose: print (codecs.encode(data,'hex')) + return None + + return resp #else udp while True: #limitado por respuesta no por tamaƱo data_recv = self.__sock.recv(1024+8) @@ -1459,10 +1419,12 @@ class ZK(object): #direct!!! small!!! size = len(self.__data) return self.__data, size + #else ACK_OK with size size = unpack('I', self.__data[1:5])[0] # extra info??? if self.verbose: print ("size fill be %i" % size) remain = size % MAX_CHUNK packets = (size-remain) // MAX_CHUNK # should be size /16k + if self.verbose: print ("rwb: #{} packets of max {} bytes, and extra {} bytes remain".format(packets, MAX_CHUNK, remain)) for _wlk in range(packets): data.append(self.__read_chunk(start,MAX_CHUNK)) start += MAX_CHUNK From c78d65216205f5343aeb6126c44aaeda514ea1f8 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Tue, 24 Jul 2018 19:49:18 -0400 Subject: [PATCH 62/83] added supported list and -basic flag for simpler tests --- README.md | 66 ++++++++++++++++++++++++++++++++++++++++++++++++- test_machine.py | 9 +++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 934899f..1a36213 100644 --- a/README.md +++ b/README.md @@ -234,7 +234,71 @@ optional arguments: ``` -# Related Project (TODO: check compatibility) +# Compatible devices + +``` +Firmware Version : Ver 6.21 Nov 19 2008 +Platform : ZEM500 +DeviceName : U580 + +Firmware Version : Ver 6.60 Oct 29 2012 +Platform : ZEM800_TFT +DeviceName : iFace402/ID + +Firmware Version : Ver 6.60 Dec 27 2014 +Platform : ZEM600_TFT +DeviceName : iFace800/ID + +Firmware Version : Ver 6.60 Mar 18 2013 +Platform : ZEM560 +DeviceName : MA300 + +Firmware Version : Ver 6.60 Dec 1 2010 +Platform : ZEM510_TFT +DeviceName : T4-C + +Firmware Version : Ver 6.60 Apr 9 2010 +Platform : ZEM510_TFT +DeviceName : T4-C + +Firmware Version : Ver 6.60 Mar 18 2011 +Platform : ZEM600_TFT +DeviceName : iClock260 + +Firmware Version : Ver 6.60 Nov 6 2017 (remote tested with correct results) +Platform : ZMM220_TFT +DeviceName : (unknown device) (broken info but at least the important data was read) +``` + + + +### Latest tested (not really confirmed) + +``` +Firmware Version : Ver 6.60 Jun 16 2015 +Platform : JZ4725_TFT +DeviceName : iClock260 + +Firmware Version : Ver 6.60 Jun 16 2015 +Platform : JZ4725_TFT +DeviceName : K14 (not tested, but same behavior like the other one) +``` + + + +### Not Working (needs more tests, more information) + +``` +Firmware Version : Ver 6.4.1 (build 99) (display version 2012-08-31) +Platform : +DeviceName : iClock260 (no capture data - probably similar problem as the latest TESTED) +``` + +If you have another version tested and it worked, please inform me to update this list! + + + +# Related Project (TODO: check compatibility with this fork) * [zkcluster](https://github.com/fananimi/zkcluster/ "zkcluster project") is a django apps to manage multiple fingerprint devices. diff --git a/test_machine.py b/test_machine.py index 4c907ad..48fc2ad 100755 --- a/test_machine.py +++ b/test_machine.py @@ -15,6 +15,9 @@ from zk.finger import Finger from zk.attendance import Attendance from zk.exception import ZKErrorResponse, ZKNetworkError +class BasicException(Exception): + pass + conn = None @@ -27,6 +30,8 @@ parser.add_argument('-T', '--timeout', type=int, help='Default [10] seconds (0: disable timeout)', default=1) parser.add_argument('-P', '--password', type=int, help='Device code/password', default=0) +parser.add_argument('-b', '--basic', action="store_true", + help='display Basic Information (no buld read, ie: users)') parser.add_argument('-f', '--force-udp', action="store_true", help='Force UDP communication') parser.add_argument('-v', '--verbose', action="store_true", @@ -89,6 +94,8 @@ try: conn.read_sizes() print (conn) print ('') + if args.basic: + raise BasicException("Basic Info... Done!") print ('--- Get User ---') inicio = time.time() users = conn.get_users() @@ -223,6 +230,8 @@ try: print('') print('--- capture End!---') print ('') +except BasicException as e: + print (e) except Exception as e: print ("Process terminate : {}".format(e)) print ("Error: %s" % sys.exc_info()[0]) From 20b1768ca89add9235dcb558f8b00a705b6582bf Mon Sep 17 00:00:00 2001 From: kurenai-ryu Date: Fri, 27 Jul 2018 17:37:05 -0400 Subject: [PATCH 63/83] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 27 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/custom.md | 7 ++++++ .github/ISSUE_TEMPLATE/feature_request.md | 17 ++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/custom.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..7457487 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. initialize data '...' +2. execute command '....' +3. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Capture Data** +Try to always include captured data (pcap files, and verbose output from test_machine if applicable) + +**System (please complete the following information):** + - OS: [e.g. iOS, Debian 9] + - Python version [e.g. 2.7, 3.5]] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 0000000..99bb9a0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,7 @@ +--- +name: Custom issue template +about: Describe this issue template's purpose here. + +--- + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..066b2d9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From cd2ae44b92ce6c83f4d92c6d7ca382a151559b8d Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Fri, 27 Jul 2018 18:14:58 -0400 Subject: [PATCH 64/83] added raw finger template (dump) for test_machine TODO: upload dumped finger --- README.md | 12 ++++++++---- test_machine.py | 44 +++++++++++++++++++++++++++++--------------- zk/finger.py | 3 +++ 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 1a36213..b385843 100644 --- a/README.md +++ b/README.md @@ -188,9 +188,11 @@ for attendance in conn.live_capture(): pass else: print (attendance) # Attendance object + # #if you need to break gracefully just set # conn.end_live_capture = True - # on interactive mode, + # + # On interactive mode, # use Ctrl+C to break gracefully # this way it restores timeout # and disables live capture @@ -214,9 +216,11 @@ optional arguments: Default [10] seconds (0: disable timeout) -P PASSWORD, --password PASSWORD Device code/password + -b, --basic get Basic Information only. (no bulk read, ie: users) -f, --force-udp Force UDP communication -v, --verbose Print debug information - -t, --templates Get templates / fingers + -t, --templates Get templates / fingers (compare bulk and single read) + -tr, --templates-raw Get raw templates (dump templates) -r, --records Get attendance records -u, --updatetime Update Date/Time -l, --live-capture Live Event Capture @@ -307,7 +311,7 @@ If you have another version tested and it worked, please inform me to update thi # Todo * Create better documentation -* Finger template downloader & uploader +* ~~Finger template downloader & uploader~~ * HTTP Rest api -* Create real time api (if possible) +* ~~Create real time api (if possible)~~ * and much more ... diff --git a/test_machine.py b/test_machine.py index 48fc2ad..d1492ff 100755 --- a/test_machine.py +++ b/test_machine.py @@ -5,6 +5,7 @@ import traceback import argparse import time import datetime +import codecs from builtins import input sys.path.append("zk") @@ -31,13 +32,15 @@ parser.add_argument('-T', '--timeout', type=int, parser.add_argument('-P', '--password', type=int, help='Device code/password', default=0) parser.add_argument('-b', '--basic', action="store_true", - help='display Basic Information (no buld read, ie: users)') + help='get Basic Information only. (no bulk read, ie: users)') parser.add_argument('-f', '--force-udp', action="store_true", help='Force UDP communication') parser.add_argument('-v', '--verbose', action="store_true", help='Print debug information') parser.add_argument('-t', '--templates', action="store_true", - help='Get templates / fingers') + help='Get templates / fingers (compare bulk and single read)') +parser.add_argument('-tr', '--templates-raw', action="store_true", + help='Get raw templates (dump templates)') parser.add_argument('-r', '--records', action="store_true", help='Get attendance records') parser.add_argument('-u', '--updatetime', action="store_true", @@ -178,23 +181,34 @@ try: conn.refresh_data() #print ("Voice Test ...") #conn.test_voice(10) - if args.templates: + if args.templates or args.templates_raw: print ("Read Templates...") inicio = time.time() templates = conn.get_templates() final = time.time() print (' took {:.3f}[s]'.format(final - inicio)) - print ('now checking individually...') - for tem in templates: - tem2 =conn.get_user_template(tem.uid,tem.fid) - if tem2 is None: - print ("bulk! %s" % tem) - elif tem == tem2: # compare with alternative method - print ("OK! %s" % tem) - else: - print ("dif-1 %s" % tem) - print ("dif-2 %s" % tem2) - print (' took {:.3f}[s]'.format(final - inicio)) + if args.templates: + print ('now checking individually...') + i = 0 + for tem in templates: + i += 1 + tem2 =conn.get_user_template(tem.uid,tem.fid) + if tem2 is None: + print ("%i: bulk! %s" % (i, tem)) + elif tem == tem2: # compare with alternative method + print ("%i: OK! %s" % (i, tem)) + else: + print ("%i: dif-1 %s" % (i, tem)) + print ("%i: dif-2 %s" % (i, tem2)) + print (' took {:.3f}[s]'.format(final - inicio)) + else: + print ('template dump') + i = 0 + for tem in templates: + i += 1 + print ("%i: %s" % (i, tem.dump())) + print (' took {:.3f}[s]'.format(final - inicio)) + if args.records: print ("Read Records...") inicio = time.time() @@ -203,7 +217,7 @@ try: print (' took {:.3f}[s]'.format(final - inicio)) i = 0 for att in attendance: - i +=1 + i += 1 print ("ATT {:>6}: uid:{:>3}, user_id:{:>8} t: {}, s:{} p:{}".format(i, att.uid, att.user_id, att.timestamp, att.status, att.punch)) print (' took {:.3f}[s]'.format(final - inicio)) print ('') diff --git a/zk/finger.py b/zk/finger.py index 994cec9..77aab11 100644 --- a/zk/finger.py +++ b/zk/finger.py @@ -25,3 +25,6 @@ class Finger(object): def __repr__(self): return " [uid:{:>3}, fid:{}, size:{:>4} v:{} t:{}]".format(self.uid, self.fid, self.size, self.valid, self.mark) + + def dump(self): + return " [uid:{:>3}, fid:{}, size:{:>4} v:{} t:{}]".format(self.uid, self.fid, self.size, self.valid, codecs.encode(self.template, 'hex')) From 005d1a72214974e65a2dfeac84375431d1eadb81 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Thu, 9 Aug 2018 20:07:37 -0400 Subject: [PATCH 65/83] minor fixes, and name encoding default UTF-8 --- zk/base.py | 82 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/zk/base.py b/zk/base.py index d6b125d..f901c6b 100644 --- a/zk/base.py +++ b/zk/base.py @@ -94,7 +94,7 @@ class ZK_helper(object): class ZK(object): """ Clase ZK """ - def __init__(self, ip, port=4370, timeout=60, password=0, force_udp=False, ommit_ping=False, verbose=False): + def __init__(self, ip, port=4370, timeout=60, password=0, force_udp=False, ommit_ping=False, verbose=False, encoding='UTF-8'): """ initialize instance """ self.is_connect = False self.is_enabled = True #let's asume @@ -108,6 +108,7 @@ class ZK(object): self.force_udp = force_udp self.ommit_ping = ommit_ping self.verbose = verbose + self.encoding = encoding self.tcp = False self.users = 0 self.fingers = 0 @@ -194,7 +195,7 @@ class ZK(object): def __test_tcp_top(self, packet): """ return size!""" - if len(packet)<=8: + if len(packet)<=8: return 0 # invalid packet tcp_header = unpack('= 28: uid, privilege, password, name, card, group_id, timezone, user_id = unpack(' max_uid: max_uid = uid - password = (password.split(b'\x00')[0]).decode(errors='ignore') - name = (name.split(b'\x00')[0]).decode(errors='ignore').strip() + password = (password.split(b'\x00')[0]).decode(self.encoding, errors='ignore') + name = (name.split(b'\x00')[0]).decode(self.encoding, errors='ignore').strip() #card = unpack('I', card)[0] #or hex value? group_id = str(group_id) user_id = str(user_id) - #TODO: check card value and find in ver8 + #TODO: check card value and find in ver8 if not name: name = "NN-%s" % user_id user = User(uid, name, privilege, password, group_id, user_id, card) @@ -1023,10 +1027,10 @@ class ZK(object): #u2 = int(uid[1].encode("hex"), 16) #uid = u1 + (u2 * 256) #privilege = int(privilege.encode("hex"), 16) - password = (password.split(b'\x00')[0]).decode(errors='ignore') - name = (name.split(b'\x00')[0]).decode(errors='ignore').strip() - group_id = (group_id.split(b'\x00')[0]).decode(errors='ignore').strip() - user_id = (user_id.split(b'\x00')[0]).decode(errors='ignore') + password = (password.split(b'\x00')[0]).decode(self.encoding, errors='ignore') + name = (name.split(b'\x00')[0]).decode(self.encoding, errors='ignore').strip() + group_id = (group_id.split(b'\x00')[0]).decode(self.encoding, errors='ignore').strip() + user_id = (user_id.split(b'\x00')[0]).decode(self.encoding, errors='ignore') if uid > max_uid: max_uid = uid #card = int(unpack('I', separator)[0]) if not name: @@ -1074,7 +1078,7 @@ class ZK(object): cmd_response = self.__send_command(command, command_string) if not cmd_response.get('status'): raise ZKErrorResponse("cant' reg events %i" % flags) - + def set_sdk_build_1(self): """ """ command = const.CMD_OPTIONS_WRQ @@ -1210,7 +1214,7 @@ class ZK(object): if self.verbose: print ("empty") continue """if len (data) == 5: - + continue""" if len(data) == 12: #class 1 attendance #TODO: RETEST ZK6 user_id, status, punch, timehex = unpack(' Date: Mon, 13 Aug 2018 11:04:17 -0400 Subject: [PATCH 66/83] fixed unittest for python3 --- test.py | 61 +++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/test.py b/test.py index 4b03a8b..637b87f 100644 --- a/test.py +++ b/test.py @@ -11,6 +11,12 @@ from zk.user import User from zk.finger import Finger from zk.attendance import Attendance from zk.exception import ZKErrorResponse, ZKNetworkError + +try: + unittest.TestCase.assertRaisesRegex +except AttributeError: + unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp + def dump(obj, nested_level=0, output=sys.stdout): spacing = ' ' if type(obj) == dict: @@ -41,36 +47,40 @@ class PYZKTest(unittest.TestCase): def tearDown(self): pass - + + @patch('zk.base.socket') @patch('zk.base.ZK_helper') - def test_no_ping(self,helper): + def test_no_ping(self,helper, socket): """ what if ping doesn't response """ helper.return_value.test_ping.return_value = False #no ping simulated #begin zk = ZK('192.168.1.201') helper.assert_called_with('192.168.1.201', 4370) # called correctly - self.assertRaisesRegexp(ZKNetworkError, "can't reach device", zk.connect) + self.assertRaisesRegex(ZKNetworkError, "can't reach device", zk.connect) + @patch('zk.base.socket') @patch('zk.base.ZK_helper') - def test_correct_ping(self,helper): + def test_correct_ping(self,helper, socket): """ what if ping is ok """ helper.return_value.test_ping.return_value = True # ping simulated + helper.return_value.test_tcp.return_value = 1 # helper tcp ok + socket.return_value.recv.return_value = b'' #begin zk = ZK('192.168.1.201') helper.assert_called_with('192.168.1.201', 4370) # called correctly - self.assertRaisesRegexp(ZKNetworkError, "unpack requires", zk.connect) # no data...? + self.assertRaisesRegex(ZKNetworkError, "unpack requires", zk.connect) # no data...? @patch('zk.base.socket') @patch('zk.base.ZK_helper') def test_tcp_invalid(self, helper, socket): - """ Basic connection test """ + """ Basic tcp invalid """ helper.return_value.test_ping.return_value = True # ping simulated helper.return_value.test_tcp.return_value = 0 # helper tcp ok - socket.return_value.recv.return_value = 'Invalid tcp data' + socket.return_value.recv.return_value = b'Invalid tcp data' #begin zk = ZK('192.168.1.201') helper.assert_called_with('192.168.1.201', 4370) # called correctly - self.assertRaisesRegexp(ZKNetworkError, "TCP packet invalid", zk.connect) + self.assertRaisesRegex(ZKNetworkError, "TCP packet invalid", zk.connect) @patch('zk.base.socket') @patch('zk.base.ZK_helper') @@ -117,7 +127,7 @@ class PYZKTest(unittest.TestCase): @patch('zk.base.socket') @patch('zk.base.ZK_helper') def test_tcp_unauth(self, helper, socket): - """ Basic auth test """ + """ Basic unauth test """ helper.return_value.test_ping.return_value = True # ping simulated helper.return_value.test_tcp.return_value = 0 # helper tcp ok socket.return_value.recv.side_effect = [ @@ -126,7 +136,7 @@ class PYZKTest(unittest.TestCase): ] #begin zk = ZK('192.168.1.201', password=12) - self.assertRaisesRegexp(ZKErrorResponse, "Unauthenticated", zk.connect) + self.assertRaisesRegex(ZKErrorResponse, "Unauthenticated", zk.connect) socket.return_value.send.assert_called_with(codecs.decode('5050827d0c0000004e044e2ccf450100614d323c', 'hex')) # try with password 12 @patch('zk.base.socket') @@ -139,7 +149,7 @@ class PYZKTest(unittest.TestCase): codecs.decode('5050827d08000000d5075bb2cf450000', 'hex'), # tcp CMD_UNAUTH codecs.decode('5050827d08000000d0075fb2cf450100', 'hex'), # tcp CMD_ACK_OK codecs.decode('5050827d08000000d00745b2cf451b00', 'hex') # tcp random CMD_ACK_OK TODO: generate proper sequenced response - + ] #begin zk = ZK('192.168.1.201', password=45) @@ -196,12 +206,15 @@ class PYZKTest(unittest.TestCase): #assert one user usu = users[3] self.assertIsInstance(usu.uid, int, "uid should be int() %s" % type(usu.uid)) - self.assertIsInstance(usu.user_id, (str, unicode), "user_id should be str() or unicode() %s" % type(usu.user_id)) + if sys.version_info >= (3, 0): + self.assertIsInstance(usu.user_id, (str, bytes), "user_id should be str() or bytes() %s" % type(usu.user_id)) + else: + self.assertIsInstance(usu.user_id, (str, unicode), "user_id should be str() or unicode() %s" % type(usu.user_id)) self.assertEqual(usu.uid, 4, "incorrect uid %s" % usu.uid) self.assertEqual(usu.user_id, "831", "incorrect user_id %s" % usu.user_id) self.assertEqual(usu.name, "NN-831", "incorrect uid %s" % usu.name) # generated conn.disconnect() - + @patch('zk.base.socket') @patch('zk.base.ZK_helper') def test_tcp_get_users_broken_tcp(self, helper, socket): @@ -218,19 +231,19 @@ class PYZKTest(unittest.TestCase): codecs.decode('0000000004000e00000000000000000000000000000000000000000000000000000000000000000000000001000000000000000032333338323035000000000000000000000000000000000005000e000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000333632363439300000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000313838343633340000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000000000000', 'hex'), #raw data 256 codecs.decode('00000000000000000000000000000000000000000000000000000000000000000000000100000000000000003131313336333200000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003130353233383900000000000000000000000000000000000d00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003135333538333600000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000000000000000100000000', 'hex'), #raw data 256 codecs.decode('000000003933313637300000000000000000000000000000', 'hex'), #raw data 24 - + codecs.decode('5050827df8030000dd0520b601001600000000000f00003334323931343800000000000000000000000000000000000000000000000000000000000100000000000000003334323931343800000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003139303636393700000000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003139333831333500000000000000000000000000', 'hex'), # DATA 1016 -8 (util216 codecs.decode('00000000120000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000393231303537000000000000000000000000000000000000130000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000333634383739340000000000000000000000000000000000140000323831353732000000000000000000000000000000000000000000000000000000000000010000000000000000323831353732000000000000000000000000000000000000150000000000000000000000000000000000000000000000000000000000000000000000', 'hex'), #raw data 256 codecs.decode('00000001000000000000000031383133323236000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000035393037353800000000000000000000000000000000000017000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000031363933373232000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000033363430323131000000000000000000000000000000000019000000', 'hex'), #raw data 256 codecs.decode('00000000000000000000000000000000000000000000000000000000000000000000000100000000000000003331303733390000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003433353430393400000000000000000000000000000000001b00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003338303736333200000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000000000000000000100000000', 'hex'), #raw data 256 codecs.decode('000000003231333938313700000000000000000000000000', 'hex'), #raw data 24 - + codecs.decode('5050827df8030000dd059a2102001600000000001d00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003333383738313900000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003439353634363800000000000000000000000000000000001f00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003832343030300000000000000000000000000000', 'hex'), #DATA 1016 -8 (util 216) codecs.decode('00000000200000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000333937373437370000000000000000000000000000000000210000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000343435383038340000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000343430353130390000000000000000000000000000000000230000000000000000000000000000000000000000000000000000000000000000000000', 'hex'), #raw data 256 codecs.decode('00000001000000000000000033353732363931000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000033363336333832000000000000000000000000000000000025000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000033333232353432000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000039393437303800000000000000000000000000000000000027000000', 'hex'), #raw data 256 codecs.decode('00000000000000000000000000000000000000000000000000000000000000000000000100000000000000003836333539380000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003338383736383000000000000000000000000000000000002900000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003739393434350000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000000000000000000100000000', 'hex'), # raw data 256 codecs.decode('000000003532313136340000000000000000000000000000', 'hex'), # raw data 24 - + codecs.decode('5050827df8030000dd053da903001600000000002b00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003439373033323400000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000000000000', 'hex'), # DATA 1016 -8 (util 112) codecs.decode('0000000100000000000000003134363732353100000000000000000000000000000000002d000e32363635373336006d61726368756b0000000000000000000000000000000000000000000100000000000000003236363537333600000000000000000000000000', 'hex'), # raw data 104 codecs.decode('000000002e00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003136383133353200000000000000000000000000000000002f000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000034393633363732000000000000000000000000000000000030000000', 'hex'), # raw data 152 @@ -238,16 +251,16 @@ class PYZKTest(unittest.TestCase): codecs.decode('0000000033373336323437000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000031323930313635000000000000000000000000000000000035000000000000000000000000000000000000000000000000000000', 'hex'), # raw data 128 codecs.decode('0000000000000000000000010000000000000000333236333636330000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000393031353036000000000000000000000000000000000000370000000000000000000000', 'hex'), # raw data 128 codecs.decode('0000000000000000000000000000000000000000000000000000000100000000000000003238313732393300000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003437303630333800000000000000000000000000', 'hex'), # raw data 128 - + codecs.decode('5050827df8030000dd05037d04001600000000003900000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003136343731353600000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000000000', 'hex'), # DATA 1016 -8 (util 112) codecs.decode('0000000100000000000000003530313435310000000000000000000000000000000000003b00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003534363236373300000000000000000000000000000000003c00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003533363730310000000000000000000000000000000000003d00000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003435383033303700000000000000000000000000000000003e000000', 'hex'), # raw data 256 codecs.decode('00000000000000000000000000000000000000000000000000000000000000000000000100000000000000003136333835333200000000000000000000000000000000003f000e3336323634313900000000000000000000000000000000000000000000000000000000000100000000000000003336323634313900000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003233323331383500000000000000000000000000000000004100000000000000000000000000000000000000000000000000000000000000000000000000000100000000', 'hex'), # raw data 256 codecs.decode('0000000035323930373337000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000033393839303636000000000000000000000000000000000043000000000000000000000000000000000000000000000000000000', 'hex'), # raw data 128 codecs.decode('0000000000000000000000010000000000000000343033323930390000000000000000000000000000000000440000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000323034363338380000000000000000000000000000000000450000000000000000000000', 'hex'), # raw data 128 codecs.decode('0000000000000000000000000000000000000000000000000000000100000000000000003733383730330000000000000000000000000000000000004600000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000003239313836333600000000000000000000000000', 'hex'), # raw data 128 - + codecs.decode('5050827d0c000000dd0507fa0500160000000000', 'hex'), # DATA 12-8 (util 4 ok) and ACK OK!!! - + codecs.decode('5050827d08000000d00745b2cf451b00', 'hex'), # CMD_ACK_OK for get_users TODO: generate proper sequenced response codecs.decode('5050827d08000000d00745b2cf451b00', 'hex'), # CMD_ACK_OK for free_data TODO: generate proper sequenced response codecs.decode('5050827d08000000d00745b2cf451b00', 'hex'), # CMD_ACK_OK for exit TODO: generate proper sequenced response @@ -263,7 +276,10 @@ class PYZKTest(unittest.TestCase): #assert one user usu = users[1] self.assertIsInstance(usu.uid, int, "uid should be int() %s" % type(usu.uid)) - self.assertIsInstance(usu.user_id, (str, unicode), "user_id should be str() or unicode() %s" % type(usu.user_id)) + if sys.version_info >= (3, 0): + self.assertIsInstance(usu.user_id, (str, bytes), "user_id should be str() or bytes() %s" % type(usu.user_id)) + else: + self.assertIsInstance(usu.user_id, (str, unicode), "user_id should be str() or unicode() %s" % type(usu.user_id)) self.assertEqual(usu.uid, 2, "incorrect uid %s" % usu.uid) self.assertEqual(usu.user_id, "3494866", "incorrect user_id %s" % usu.user_id) self.assertEqual(usu.name, "NievesLopez", "incorrect uid %s" % usu.name) # check test case @@ -291,7 +307,10 @@ class PYZKTest(unittest.TestCase): #assert one user usu = users[3] self.assertIsInstance(usu.uid, int, "uid should be int() %s" % type(usu.uid)) - self.assertIsInstance(usu.user_id, (str, unicode), "user_id should be str() or unicode() %s" % type(usu.user_id)) + if sys.version_info >= (3, 0): + self.assertIsInstance(usu.user_id, (str, bytes), "user_id should be str() or bytes() %s" % type(usu.user_id)) + else: + self.assertIsInstance(usu.user_id, (str, unicode), "user_id should be str() or unicode() %s" % type(usu.user_id)) self.assertEqual(usu.uid, 4, "incorrect uid %s" % usu.uid) self.assertEqual(usu.user_id, "831", "incorrect user_id %s" % usu.user_id) self.assertEqual(usu.name, "NN-831", "incorrect uid %s" % usu.name) # generated From a726545d18df7989e27cc15e851f2734c5cffcbc Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Tue, 21 Aug 2018 09:00:46 -0400 Subject: [PATCH 67/83] test missing timeout --- README.md | 29 ++++++++++++++++++----------- zk/base.py | 2 ++ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index b385843..2cd3acb 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,22 @@ pyzk is an unofficial library of zksoftware the fingerprint attendance machine. [![Build Status](https://travis-ci.org/kurenai-ryu/pyzk.svg?branch=master)](https://travis-ci.org/kurenai-ryu/pyzk) replace original pyzk, if it was installed -`pip install -U git+https://github.com/kurenai-ryu/pyzk.git` + +```sh +pip install -U git+https://github.com/kurenai-ryu/pyzk.git +``` + +or using pipenv: + +```sh +pipenv install git+https://gith.com/kurenai-ryu/pyzk#egg=pyzk +``` or clone and execute: -`python setup.py install` + +```sh +python setup.py install +``` or in your project, append the path of this project @@ -68,21 +80,21 @@ finally: * Connect/Disconnect -``` +```python conn = zk.connect() conn.disconnect() ``` * Disable/Enable Connected Device -``` +```python conn.disable_device() conn.enable_device() ``` * Get and Set Time -``` +```python from datetime import datetime zktime = conn.get_time() @@ -95,7 +107,7 @@ conn.set_time(newtime) * Ger Firmware Version and extra information -``` +```python conn.get_firmware_version() conn.get_serialnumber() conn.get_platform() @@ -235,7 +247,6 @@ optional arguments: -F FINGER, --finger FINGER Finger for enroll (fid=0) - ``` # Compatible devices @@ -288,8 +299,6 @@ Platform : JZ4725_TFT DeviceName : K14 (not tested, but same behavior like the other one) ``` - - ### Not Working (needs more tests, more information) ``` @@ -300,8 +309,6 @@ DeviceName : iClock260 (no capture data - probably similar problem as the latest If you have another version tested and it worked, please inform me to update this list! - - # Related Project (TODO: check compatibility with this fork) * [zkcluster](https://github.com/fananimi/zkcluster/ "zkcluster project") is a django apps to manage multiple fingerprint devices. diff --git a/zk/base.py b/zk/base.py index f901c6b..1f90185 100644 --- a/zk/base.py +++ b/zk/base.py @@ -140,9 +140,11 @@ class ZK(object): """ based on self.tcp""" if self.tcp: self.__sock = socket(AF_INET, SOCK_STREAM) + self.__sock.settimeout(self.__timeout) self.__sock.connect_ex(self.__address) else: self.__sock = socket(AF_INET, SOCK_DGRAM) + self.__sock.settimeout(self.__timeout) def __create_tcp_top(self, packet): """ witch the complete packet set top header """ From 99b677a9daff1bbaa21652333b46279b59b26c4f Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Thu, 30 Aug 2018 16:31:49 -0400 Subject: [PATCH 68/83] minor fixes, backup generator (TODO: restorer) --- .gitignore | 3 +- README.md | 6 ++- test_backup_restore.py | 117 +++++++++++++++++++++++++++++++++++++++++ test_machine.py | 4 +- zk/attendance.py | 4 +- zk/base.py | 2 +- 6 files changed, 128 insertions(+), 8 deletions(-) create mode 100755 test_backup_restore.py diff --git a/.gitignore b/.gitignore index 70e6997..88ba600 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ build/ dist/ *.egg-info/ -*.test \ No newline at end of file +*.test +*.bak \ No newline at end of file diff --git a/README.md b/README.md index 2cd3acb..014c3c4 100644 --- a/README.md +++ b/README.md @@ -309,9 +309,11 @@ DeviceName : iClock260 (no capture data - probably similar problem as the latest If you have another version tested and it worked, please inform me to update this list! -# Related Project (TODO: check compatibility with this fork) +# Related Project -* [zkcluster](https://github.com/fananimi/zkcluster/ "zkcluster project") is a django apps to manage multiple fingerprint devices. +* [zkcluster](https://github.com/kurenai-ryu/zkcluster/ "zkcluster project") is a django apps to manage multiple fingerprint devices. (Initial support form the [original project](https://github.com/fananimi/zkcluster/)) + +# Related Project (TODO: check compatibility with this fork) * [Driji](https://github.com/fananimi/driji/ "Driji project") is an attendance apps based fingerprint for school diff --git a/test_backup_restore.py b/test_backup_restore.py new file mode 100755 index 0000000..b47fe9f --- /dev/null +++ b/test_backup_restore.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python2 +# # -*- coding: utf-8 -*- +import sys +import traceback +import argparse +import time +import datetime +import codecs +from builtins import input + +import pickle + +sys.path.append("zk") + +from zk import ZK, const +from zk.user import User +from zk.finger import Finger +from zk.attendance import Attendance +from zk.exception import ZKErrorResponse, ZKNetworkError + +class BasicException(Exception): + pass + +conn = None + +parser = argparse.ArgumentParser(description='ZK Basic Backup/Restore Tool') +parser.add_argument('-a', '--address', + help='ZK device Address [192.168.1.201]', default='192.168.1.201') +parser.add_argument('-p', '--port', type=int, + help='ZK device port [4370]', default=4370) +parser.add_argument('-T', '--timeout', type=int, + help='Default [10] seconds (0: disable timeout)', default=10) +parser.add_argument('-P', '--password', type=int, + help='Device code/password', default=0) +parser.add_argument('-f', '--force-udp', action="store_true", + help='Force UDP communication') +parser.add_argument('-v', '--verbose', action="store_true", + help='Print debug information') +parser.add_argument('-r', '--restore', action="store_true", + help='Restore from backup') +parser.add_argument('-F', '--filename', + help='backup filename', default='') + +args = parser.parse_args() + + + +zk = ZK(args.address, port=args.port, timeout=args.timeout, password=args.password, force_udp=args.force_udp, verbose=args.verbose) +try: + print('Connecting to device ...') + conn = zk.connect() + serialnumber = conn.get_serialnumber() + fp_version = conn.get_fp_version() + print ('Serial Number : {}'.format(serialnumber)) + print ('Finger Version : {}'.format(fp_version)) + filename = args.filename if args.filename else "{}.bak".format(serialnumber) + print ('') + if not args.restore: + print ('--- sizes & capacity ---') + conn.read_sizes() + print (conn) + print ('--- Get User ---') + inicio = time.time() + users = conn.get_users() + final = time.time() + print ('Read {} users took {:.3f}[s]'.format(len(users), final - inicio)) + print ("Read Templates...") + inicio = time.time() + templates = conn.get_templates() + final = time.time() + print ('Read {} templates took {:.3f}[s]'.format(len(templates), final - inicio)) + #save to file! + print ('') + print ('Saving to file {} ...'.format(filename)) + output = open(filename, 'wb') + data = { + 'version':'1.00ut', + 'serial': serialnumber, + 'fp_version': fp_version, + 'users': users, + 'templates':templates + } + pickle.dump(data, output, -1) + output.close() + else: + print ('Reading file {}'.format(filename)) + infile = open(filename, 'rb') + data = pickle.load(infile) + infile.close() + #compare versions... + if data['version'] != '1.00ut': + raise BasicException("file with different version... aborting!") + if data['fp_version'] != fp_version: + raise BasicException("fingerprint version mismmatch {} != {} ... aborting!".format(fp_version, data['fp_version'])) + print ('WARNING! the next step will erase the current device content.') + print ('Please input the serialnumber of this device [{}] to acknowledge the ERASING!'.format(serialnumber)) + new_serial = input ('Serial Number : ') + if new_serial != serialnumber: + raise BasicException('Serial number mismatch') + print ('Erasing device!!! Work in progress') + print ('Restoring Data... Work in progress') +except BasicException as e: + print (e) + print ('') +except Exception as e: + print ("Process terminate : {}".format(e)) + print ("Error: %s" % sys.exc_info()[0]) + print ('-'*60) + traceback.print_exc(file=sys.stdout) + print ('-'*60) +finally: + if conn: + print ('Enabling device ...') + conn.enable_device() + conn.disconnect() + print ('ok bye!') + print ('') diff --git a/test_machine.py b/test_machine.py index d1492ff..4af5724 100755 --- a/test_machine.py +++ b/test_machine.py @@ -21,14 +21,13 @@ class BasicException(Exception): conn = None - parser = argparse.ArgumentParser(description='ZK Basic Reading Tests') parser.add_argument('-a', '--address', help='ZK device Address [192.168.1.201]', default='192.168.1.201') parser.add_argument('-p', '--port', type=int, help='ZK device port [4370]', default=4370) parser.add_argument('-T', '--timeout', type=int, - help='Default [10] seconds (0: disable timeout)', default=1) + help='Default [10] seconds (0: disable timeout)', default=10) parser.add_argument('-P', '--password', type=int, help='Device code/password', default=0) parser.add_argument('-b', '--basic', action="store_true", @@ -246,6 +245,7 @@ try: print ('') except BasicException as e: print (e) + print ('') except Exception as e: print ("Process terminate : {}".format(e)) print ("Error: %s" % sys.exc_info()[0]) diff --git a/zk/attendance.py b/zk/attendance.py index 4d68746..3ffd9d7 100644 --- a/zk/attendance.py +++ b/zk/attendance.py @@ -8,7 +8,7 @@ class Attendance(object): self.punch = punch def __str__(self): - return ': {} : {}'.format(self.user_id, self.timestamp) + return ': {} : {} ({}, {})'.format(self.user_id, self.timestamp, self.status, self.punch) def __repr__(self): - return ': {} : {}'.format(self.user_id, self.timestamp) + return ': {} : {} ({}, {})'.format(self.user_id, self.timestamp,self.status, self.punch) diff --git a/zk/base.py b/zk/base.py index 1f90185..830d11c 100644 --- a/zk/base.py +++ b/zk/base.py @@ -973,7 +973,7 @@ class ZK(object): if self.verbose: print("WRN: no user data") # debug return [] total_size = unpack('i', templatedata[0:4])[0] - print ("get template total size {}, size {} len {}".format(total_size, size, len(templatedata))) + if self.verbose: print ("get template total size {}, size {} len {}".format(total_size, size, len(templatedata))) templatedata = templatedata[4:] #total size not used # ZKFinger VX10.0 the only finger firmware tested while total_size: From 5826aac263aab08dc0ac762777ee737598d81293 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Fri, 31 Aug 2018 20:16:10 -0400 Subject: [PATCH 69/83] latest test for smaller TCP DATA packet (on JZ4725_TFT) --- README.md | 5 +++++ test.py | 39 ++++++++++++++++++++++++++++++++++++++- zk/base.py | 31 +++++++++++++++++++++++++++---- 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 014c3c4..d8b4160 100644 --- a/README.md +++ b/README.md @@ -283,6 +283,7 @@ DeviceName : iClock260 Firmware Version : Ver 6.60 Nov 6 2017 (remote tested with correct results) Platform : ZMM220_TFT DeviceName : (unknown device) (broken info but at least the important data was read) + ``` @@ -297,6 +298,10 @@ DeviceName : iClock260 Firmware Version : Ver 6.60 Jun 16 2015 Platform : JZ4725_TFT DeviceName : K14 (not tested, but same behavior like the other one) + +Firmware Version : Ver 6.60 Jun 9 2017 +Platform : JZ4725_TFT +DeviceName : K20 (Active testing! latest fix) ``` ### Not Working (needs more tests, more information) diff --git a/test.py b/test.py index 637b87f..e96debd 100644 --- a/test.py +++ b/test.py @@ -185,7 +185,7 @@ class PYZKTest(unittest.TestCase): @patch('zk.base.socket') @patch('zk.base.ZK_helper') - def test_tcp_get_users_small(self, helper, socket): + def test_tcp_get_users_small_data(self, helper, socket): """ can get empty? """ helper.return_value.test_ping.return_value = True # ping simulated helper.return_value.test_tcp.return_value = 0 # helper tcp ok @@ -215,6 +215,43 @@ class PYZKTest(unittest.TestCase): self.assertEqual(usu.name, "NN-831", "incorrect uid %s" % usu.name) # generated conn.disconnect() + @patch('zk.base.socket') + @patch('zk.base.ZK_helper') + def test_tcp_get_users_broken_data(self, helper, socket): + """ test case for K20 """ + helper.return_value.test_ping.return_value = True # ping simulated + helper.return_value.test_tcp.return_value = 0 # helper tcp ok + socket.return_value.recv.side_effect = [ + codecs.decode('5050827d08000000d007d7d758200000','hex'), #ACK Ok + codecs.decode('5050827d58000000d0074c49582013000000000000000000000000000000000002000000000000000000000000000000000000000000000007000000000000000000000000000000f4010000f401000050c30000f4010000f201000050c30000','hex'),#Sizes + codecs.decode('5050827d9c000000dd053c87582015009000000001000000000000000000006366756c616e6f0000000000000000000000000000000000000000000000000000000000003130303030316c70000000000000000000000000000000000200000000000000000000726d656e67616e6f0000000000000000000000000000000000','hex'),#DATA112 + codecs.decode('000000000000000000000000323232323232636200000000000000000000000000000000','hex'), #extra data 36 + #codecs.decode('','hex'), # + codecs.decode('5050827d08000000d00745b2cf451b00', 'hex'), # CMD_ACK_OK for get_users TODO: generate proper sequenced response + codecs.decode('5050827d08000000d00745b2cf451b00', 'hex'), # CMD_ACK_OK for free_data TODO: generate proper sequenced response + codecs.decode('5050827d08000000d00745b2cf451b00', 'hex'), # CMD_ACK_OK for exit TODO: generate proper sequenced response + ] + #begin + zk = ZK('192.168.1.201') #, verbose=True) + conn = zk.connect() + socket.return_value.send.assert_called_with(codecs.decode('5050827d08000000e80317fc00000000', 'hex')) + users = conn.get_users() + #print (users) #debug + socket.return_value.send.assert_called_with(codecs.decode('5050827d13000000df05b3cb582014000109000500000000000000', 'hex')) #get users + self.assertEqual(len(users), 2, "incorrect size %s" % len(users)) + #assert one user + usu = users[1] + self.assertIsInstance(usu.uid, int, "uid should be int() %s" % type(usu.uid)) + if sys.version_info >= (3, 0): + self.assertIsInstance(usu.user_id, (str, bytes), "user_id should be str() or bytes() %s" % type(usu.user_id)) + else: + self.assertIsInstance(usu.user_id, (str, unicode), "user_id should be str() or unicode() %s" % type(usu.user_id)) + self.assertEqual(usu.uid, 2, "incorrect uid %s" % usu.uid) + self.assertEqual(usu.user_id, "222222cb", "incorrect user_id %s" % usu.user_id) + self.assertEqual(usu.name, "rmengano", "incorrect uid %s" % usu.name) # check test case + conn.disconnect() + + @patch('zk.base.socket') @patch('zk.base.ZK_helper') def test_tcp_get_users_broken_tcp(self, helper, socket): diff --git a/zk/base.py b/zk/base.py index 830d11c..b54d64b 100644 --- a/zk/base.py +++ b/zk/base.py @@ -1336,8 +1336,19 @@ class ZK(object): def __recieve_chunk(self): """ recieve a chunk """ if self.__response == const.CMD_DATA: # less than 1024!!! - if self.verbose: print ("_rc len is {}".format(len(self.__data))) - return self.__data #without headers + if self.tcp: #MUST CHECK TCP SIZE + if self.verbose: print ("_rc_DATA! is {} bytes, tcp length is {}".format(len(self.__data), self.__tcp_length)) + if len(self.__data) < (self.__tcp_length - 8): + need = (self.__tcp_length - 8) - len(self.__data) + more_data = self.__recieve_raw_data(need) + if self.verbose: print ("need more data: {}".format(need)) + return b''.join([self.__data, more_data]) + else: #enough data + if self.verbose: print ("Enough data") + return self.__data + else: #UDP + if self.verbose: print ("_rc len is {}".format(len(self.__data))) + return self.__data #without headers elif self.__response == const.CMD_PREPARE_DATA: data = [] size = self.__get_data_size() @@ -1423,8 +1434,20 @@ class ZK(object): raise ZKErrorResponse("RWB Not supported") if cmd_response['code'] == const.CMD_DATA: #direct!!! small!!! - size = len(self.__data) - return self.__data, size + if self.tcp: #MUST CHECK TCP SIZE + if self.verbose: print ("DATA! is {} bytes, tcp length is {}".format(len(self.__data), self.__tcp_length)) + if len(self.__data) < (self.__tcp_length - 8): + need = (self.__tcp_length - 8) - len(self.__data) + more_data = self.__recieve_raw_data(need) + if self.verbose: print ("need more data: {}".format(need)) + return b''.join([self.__data, more_data]), len(self.__data) + len(more_data) + else: #enough data + if self.verbose: print ("Enough data") + size = len(self.__data) + return self.__data, size + else: #udp is direct + size = len(self.__data) + return self.__data, size #else ACK_OK with size size = unpack('I', self.__data[1:5])[0] # extra info??? if self.verbose: print ("size fill be %i" % size) From d2c10f31b0a911128b3f2099c9ffc221bad49a3c Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Fri, 31 Aug 2018 20:17:28 -0400 Subject: [PATCH 70/83] better order on fix of small TCP DATA --- zk/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zk/base.py b/zk/base.py index b54d64b..aa387da 100644 --- a/zk/base.py +++ b/zk/base.py @@ -1340,8 +1340,8 @@ class ZK(object): if self.verbose: print ("_rc_DATA! is {} bytes, tcp length is {}".format(len(self.__data), self.__tcp_length)) if len(self.__data) < (self.__tcp_length - 8): need = (self.__tcp_length - 8) - len(self.__data) - more_data = self.__recieve_raw_data(need) if self.verbose: print ("need more data: {}".format(need)) + more_data = self.__recieve_raw_data(need) return b''.join([self.__data, more_data]) else: #enough data if self.verbose: print ("Enough data") @@ -1438,8 +1438,8 @@ class ZK(object): if self.verbose: print ("DATA! is {} bytes, tcp length is {}".format(len(self.__data), self.__tcp_length)) if len(self.__data) < (self.__tcp_length - 8): need = (self.__tcp_length - 8) - len(self.__data) - more_data = self.__recieve_raw_data(need) if self.verbose: print ("need more data: {}".format(need)) + more_data = self.__recieve_raw_data(need) return b''.join([self.__data, more_data]), len(self.__data) + len(more_data) else: #enough data if self.verbose: print ("Enough data") From e5e6a65f3c564c28557a7d4337b6b8e9b75dc0ad Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Mon, 3 Sep 2018 17:42:16 -0400 Subject: [PATCH 71/83] restore working with pickle! (can't share data between python versions) --- test.py | 3 +++ test_backup_restore.py | 25 +++++++++++++++++++++---- zk/base.py | 13 +++++++++---- zk/user.py | 10 +++++++--- 4 files changed, 40 insertions(+), 11 deletions(-) mode change 100644 => 100755 test.py diff --git a/test.py b/test.py old mode 100644 new mode 100755 index e96debd..841a8af --- a/test.py +++ b/test.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python2 +# # -*- coding: utf-8 -*- + import sys import os import unittest diff --git a/test_backup_restore.py b/test_backup_restore.py index b47fe9f..7be520f 100755 --- a/test_backup_restore.py +++ b/test_backup_restore.py @@ -38,8 +38,8 @@ parser.add_argument('-v', '--verbose', action="store_true", help='Print debug information') parser.add_argument('-r', '--restore', action="store_true", help='Restore from backup') -parser.add_argument('-F', '--filename', - help='backup filename', default='') +parser.add_argument('filename', nargs='?', + help='backup filename (default [serialnumber].bak)', default='') args = parser.parse_args() @@ -64,6 +64,8 @@ try: users = conn.get_users() final = time.time() print ('Read {} users took {:.3f}[s]'.format(len(users), final - inicio)) + if len(users) == 0: + raise BasicException("Empty user list, aborting...") print ("Read Templates...") inicio = time.time() templates = conn.get_templates() @@ -92,13 +94,28 @@ try: raise BasicException("file with different version... aborting!") if data['fp_version'] != fp_version: raise BasicException("fingerprint version mismmatch {} != {} ... aborting!".format(fp_version, data['fp_version'])) + #TODO: check data consistency... + print ("INFO: ready to write {} users".format(len(data['users']))) + print ("INFO: ready to write {} templates".format(len(data['templates']))) + #input serial number to corroborate. print ('WARNING! the next step will erase the current device content.') print ('Please input the serialnumber of this device [{}] to acknowledge the ERASING!'.format(serialnumber)) new_serial = input ('Serial Number : ') if new_serial != serialnumber: raise BasicException('Serial number mismatch') - print ('Erasing device!!! Work in progress') - print ('Restoring Data... Work in progress') + conn.disable_device() + print ('Erasing device...') + conn.clear_data() + print ('Restoring Data...') + for u in data['users']: + #look for Templates + temps = list(filter(lambda f: f.uid ==u.uid, data['templates'])) + #print ("user {} has {} fingers".format(u.uid, len(temps))) + conn.save_user_template(u,temps) + conn.enable_device() + print ('--- final sizes & capacity ---') + conn.read_sizes() + print (conn) except BasicException as e: print (e) print ('') diff --git a/zk/base.py b/zk/base.py index aa387da..c0f8b04 100644 --- a/zk/base.py +++ b/zk/base.py @@ -109,6 +109,7 @@ class ZK(object): self.ommit_ping = ommit_ping self.verbose = verbose self.encoding = encoding + User.encoding = encoding self.tcp = False self.users = 0 self.fingers = 0 @@ -775,14 +776,18 @@ class ZK(object): return False #some devices doesn't support sound #raise ZKErrorResponse("can't test voice") - def set_user(self, uid, name, privilege=0, password='', group_id='', user_id='', card=0): + def set_user(self, uid=None, name='', privilege=0, password='', group_id='', user_id='', card=0): ''' create or update user by uid ''' command = const.CMD_USER_WRQ + if uid is None: + uid = self.next_uid # keeps uid=0 + if not user_id: + user_id = self.next_user_id # else... if not user_id: user_id = str(uid) #ZK6 needs uid2 == uid - #uid = chr(uid % 256) + chr(uid >> 8) + #TODO: check what happens if name is missing... if privilege not in [const.USER_DEFAULT, const.USER_ADMIN]: privilege = const.USER_DEFAULT privilege = int(privilege) @@ -827,8 +832,8 @@ class ZK(object): raise ZKErrorResponse("Can't find user") if isinstance(fingers, Finger): fingers = [fingers] - fpack = "" - table = "" + fpack = b"" + table = b"" fnum = 0x10 # possibly flag tstart = 0 for finger in fingers: diff --git a/zk/user.py b/zk/user.py index 265953f..7a5fbe6 100644 --- a/zk/user.py +++ b/zk/user.py @@ -1,21 +1,25 @@ # -*- coding: utf-8 -*- from struct import pack #, unpack class User(object): + encoding = 'UTF-8' def __init__(self, uid, name, privilege, password='', group_id='', user_id='', card=0): self.uid = uid self.name = str(name) self.privilege = privilege self.password = str(password) - self.group_id = group_id + self.group_id = str(group_id) self.user_id = user_id self.card = card # 64 int to 40 bit int + def repack29(self): # with 02 for zk6 (size 29) - return pack(" 7sx group id, timezone? - return pack(": [uid:{}, name:{} user_id:{}]'.format(self.uid, self.name, self.user_id) From 84e4d98eb1d42b39a2d0bbb65c5ff63d1e7af04e Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Mon, 3 Sep 2018 17:53:04 -0400 Subject: [PATCH 72/83] missing back restore doc, and clear_attendance flag --- - | Bin 0 -> 44947 bytes README.md | 31 +++++++++++++++++++++++++++++++ test_backup_restore.py | 5 +++++ 3 files changed, 36 insertions(+) create mode 100644 - diff --git a/- b/- new file mode 100644 index 0000000000000000000000000000000000000000..80c966e616ef86d68b37a137f4590a766dc338c3 GIT binary patch literal 44947 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 literal 0 HcmV?d00001 diff --git a/README.md b/README.md index d8b4160..92c6c97 100644 --- a/README.md +++ b/README.md @@ -249,6 +249,37 @@ optional arguments: ``` +Backup/Restore (Users and fingers only!!!) (WARNING! destructive test! do it at your own risk!) + +```sh +usage: ./test_backup_restore.py [-h] [-a ADDRESS] [-p PORT] [-T TIMEOUT] + [-P PASSWORD] [-f] [-v] [-r] + [filename] + +ZK Basic Backup/Restore Tool + +positional arguments: + filename backup filename (default [serialnumber].bak) + +optional arguments: + -h, --help show this help message and exit + -a ADDRESS, --address ADDRESS + ZK device Address [192.168.1.201] + -p PORT, --port PORT ZK device port [4370] + -T TIMEOUT, --timeout TIMEOUT + Default [10] seconds (0: disable timeout) + -P PASSWORD, --password PASSWORD + Device code/password + -f, --force-udp Force UDP communication + -v, --verbose Print debug information + -r, --restore Restore from backup + -c, --clear-attendance + On Restore, also clears the attendance [default keep + attendance] +``` + +to restore on a different device, make sure to specify the `filename`. on restoring, it asks for the serial number of the destination device (to make sure it was correct, as it deletes all data) WARNING. there is no way to restore attendance data, you can keep it or clear it, but once cleared, there is no way to restore it. + # Compatible devices ``` diff --git a/test_backup_restore.py b/test_backup_restore.py index 7be520f..660c11b 100755 --- a/test_backup_restore.py +++ b/test_backup_restore.py @@ -38,6 +38,8 @@ parser.add_argument('-v', '--verbose', action="store_true", help='Print debug information') parser.add_argument('-r', '--restore', action="store_true", help='Restore from backup') +parser.add_argument('-c', '--clear-attendance', action="store_true", + help='On Restore, also clears the attendance [default keep attendance]') parser.add_argument('filename', nargs='?', help='backup filename (default [serialnumber].bak)', default='') @@ -106,6 +108,9 @@ try: conn.disable_device() print ('Erasing device...') conn.clear_data() + if args.clear_attendance: + print ('Clearing attendance too!') + conn.clear_attendance() print ('Restoring Data...') for u in data['users']: #look for Templates From 69275a79bdbdc2cc370ee7ebbce5593c7840a44f Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Mon, 3 Sep 2018 20:00:30 -0400 Subject: [PATCH 73/83] backup file changed from pickle to json (for python2&3 support) --- - | Bin 44947 -> 0 bytes test_backup_restore.py | 34 ++++++++++++++++++++-------------- zk/finger.py | 22 +++++++++++++++++++--- zk/user.py | 15 ++++++++++++++- 4 files changed, 53 insertions(+), 18 deletions(-) delete mode 100644 - diff --git a/- b/- deleted file mode 100644 index 80c966e616ef86d68b37a137f4590a766dc338c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44947 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 diff --git a/test_backup_restore.py b/test_backup_restore.py index 660c11b..54812ca 100755 --- a/test_backup_restore.py +++ b/test_backup_restore.py @@ -8,7 +8,7 @@ import datetime import codecs from builtins import input -import pickle +import json sys.path.append("zk") @@ -55,7 +55,7 @@ try: fp_version = conn.get_fp_version() print ('Serial Number : {}'.format(serialnumber)) print ('Finger Version : {}'.format(fp_version)) - filename = args.filename if args.filename else "{}.bak".format(serialnumber) + filename = args.filename if args.filename else "{}.json.bak".format(serialnumber) print ('') if not args.restore: print ('--- sizes & capacity ---') @@ -76,29 +76,33 @@ try: #save to file! print ('') print ('Saving to file {} ...'.format(filename)) - output = open(filename, 'wb') + output = open(filename, 'w') data = { - 'version':'1.00ut', + 'version':'1.00jut', 'serial': serialnumber, 'fp_version': fp_version, - 'users': users, - 'templates':templates + 'users': [u.__dict__ for u in users], + 'templates':[t.json_pack() for t in templates] } - pickle.dump(data, output, -1) + json.dump(data, output, indent=1) output.close() else: print ('Reading file {}'.format(filename)) - infile = open(filename, 'rb') - data = pickle.load(infile) + infile = open(filename, 'r') + data = json.load(infile) infile.close() #compare versions... - if data['version'] != '1.00ut': + if data['version'] != '1.00jut': raise BasicException("file with different version... aborting!") if data['fp_version'] != fp_version: raise BasicException("fingerprint version mismmatch {} != {} ... aborting!".format(fp_version, data['fp_version'])) #TODO: check data consistency... - print ("INFO: ready to write {} users".format(len(data['users']))) - print ("INFO: ready to write {} templates".format(len(data['templates']))) + users = [User.json_unpack(u) for u in data['users']] + #print (users) + print ("INFO: ready to write {} users".format(len(users))) + templates = [Finger.json_unpack(t) for t in data['templates']] + #print (templates) + print ("INFO: ready to write {} templates".format(len(templates))) #input serial number to corroborate. print ('WARNING! the next step will erase the current device content.') print ('Please input the serialnumber of this device [{}] to acknowledge the ERASING!'.format(serialnumber)) @@ -111,10 +115,12 @@ try: if args.clear_attendance: print ('Clearing attendance too!') conn.clear_attendance() + conn.read_sizes() + print (conn) print ('Restoring Data...') - for u in data['users']: + for u in users: #look for Templates - temps = list(filter(lambda f: f.uid ==u.uid, data['templates'])) + temps = list(filter(lambda f: f.uid ==u.uid, templates)) #print ("user {} has {} fingers".format(u.uid, len(temps))) conn.save_user_template(u,temps) conn.enable_device() diff --git a/zk/finger.py b/zk/finger.py index 77aab11..26de3f0 100644 --- a/zk/finger.py +++ b/zk/finger.py @@ -16,10 +16,26 @@ class Finger(object): def repack_only(self): #only template return pack("H%is" % (self.size), self.size, self.template) - - def __eq__(self, other): + @staticmethod + def json_unpack(json): + return Finger( + uid=json['uid'], + fid=json['fid'], + valid=json['valid'], + template=codecs.decode(json['template'],'hex') + ) + def json_pack(self): #packs for json + return { + "size": self.size, + "uid": self.uid, + "fid": self.fid, + "valid": self.valid, + "template": codecs.encode(self.template, 'hex').decode('ascii') + } + + def __eq__(self, other): return self.__dict__ == other.__dict__ - + def __str__(self): return " [uid:{:>3}, fid:{}, size:{:>4} v:{} t:{}]".format(self.uid, self.fid, self.size, self.valid, self.mark) diff --git a/zk/user.py b/zk/user.py index 7a5fbe6..8c7da46 100644 --- a/zk/user.py +++ b/zk/user.py @@ -10,7 +10,20 @@ class User(object): self.password = str(password) self.group_id = str(group_id) self.user_id = user_id - self.card = card # 64 int to 40 bit int + self.card = int(card) # 64 int to 40 bit int + + @staticmethod + def json_unpack(json): + #validate? + return User( + uid=json['uid'], + name=json['name'], + privilege=json['privilege'], + password=json['password'], + group_id=json['group_id'], + user_id=json['user_id'], + card=json['card'] + ) def repack29(self): # with 02 for zk6 (size 29) return pack(" Date: Wed, 12 Sep 2018 15:21:07 -0400 Subject: [PATCH 74/83] test huellas --- test.py | 95 +++++++++++++++++++++++++++++++++++++++++++++++++ test_machine.py | 12 ++++++- zk/base.py | 2 ++ 3 files changed, 108 insertions(+), 1 deletion(-) diff --git a/test.py b/test.py index 841a8af..63adf64 100755 --- a/test.py +++ b/test.py @@ -356,5 +356,100 @@ class PYZKTest(unittest.TestCase): self.assertEqual(usu.name, "NN-831", "incorrect uid %s" % usu.name) # generated conn.disconnect() + @patch('zk.base.socket') + @patch('zk.base.ZK_helper') + def _test_tcp_get_template_1(self, helper, socket): + """ cchekc correct template 1 """ + helper.return_value.test_ping.return_value = True # ping simulated + helper.return_value.test_tcp.return_value = 0 # helper tcp ok + socket.return_value.recv.side_effect = [ + codecs.decode('5050827d08000000d0075fb2cf450100', 'hex'), # tcp CMD_ACK_OK + codecs.decode('5050827d10000000dc055558d0983200dc040000f0030000', 'hex'), # tcp PREPARE_DATA 1244 + codecs.decode('5050827df8030000dd0500f4000032004d9853533231000004dbda0408050709ced000001cda69010000008406316adb0c0012062900d000aad221001600390caf001cdbb106240031007e033bdb3b00e9067700850083d42b004300c503f40043dbd6037b005000460ea7db5900910f90009f0012d5e7005c00970a5f006ddb', 'hex'), # DATA (tcp 1016, actual 112?) + codecs.decode('930fa1009a00560f86db9d00820e86006f007dd3f400ab00a60fcd01b7dbb00b4b00bd0079083adbc00045035d000600c1df7300cc0039049e00dddb380e8c00da00e30dd8dbdc00220e130027004dd9f500e3009d0a6a00e9db26090001ef00ea03c5dbf0002306', 'hex'), #raw data 104 + codecs.decode('d000380028d83400ff00430f6200fbdba70dfb0002016203c5db0201a5044b00c10132d4de0006019f080a000cdab70541000f01fe0f19db1901c902e600dc0198d839002f01360ed80037dabd04d4003301520104da38014f01a100830196d5f5004b015c0411005cdacd03bc67ab8d162b48ad18f7fec7448e448387afa1a3', 'hex'), # raw 128 + codecs.decode('062b37ca3cf9f53c8087f9150926e03335df1b71aedbd0f2', 'hex'), # raw 24 + codecs.decode('b40da90541168df1551f70fc15b51bf26d7d4501bf12915e6485fd966f0ba2072728987dc1018a12ab105ec7aa003508fef08a49b923f3e85e42edf5ea861bd1600d23151787fc78d522f38431883e809f0e4dd2008ecd8ed97670035acf0c763503f27c37ec76d982806986c6016bf952d01e0673820570a87e1a236005ad81', 'hex'), # raw 128 + codecs.decode('7d8734949952bb929d81e5fdbcf99ca0c4886d8c65098c0e9aa6ac81e103c684607951d03b0ce9f0cd785885ad27d4f61bfc5de8bc7411de8d8f5910c518e004e9229304f90f9a891395912680ebc6f4c57fd3fceeb684f7c18ba78107fc2e16073e89f6d6b67fbb', 'hex'), # raw 104 + codecs.decode('fb11e2feb3effd0e5391c61da77176359f7e4d8a0ff3090a01204501c76a19af07002b003ac0042300dbab0113c2fa07c56e02cbc32bc10400a1c31349df0008102d2a04c5120c9b8904008f0810fb0404c20f3a6407006fd709fbecfe0400041529f60304fd1931fb0b006ede0c391bc1c0c0460e00a3210b1a34c2ffffc3fd', 'hex'), # raw 128 + codecs.decode('980f04832806404a5bc1940505da86292d0f0056f600f925', 'hex'), # raw 24 + codecs.decode('5c43c243ff06c5733a5d85c7080040473f3d31dd01774d8983c4c000778982750b009459d551c426c3c0170900929b17fba3fc780800376135fefbe0ff1100396aed3b3146265ac0c1ffff15c5357232fffdc0fdc03f3bc141914514003f85e738fdfa2441ff5cc0ff45951504ec7ee9c0fac1fc053dc424c0554affc103c5f8', 'hex'), # raw 128 + codecs.decode('94f2fd0e00668b06eac1f9b3c3fdc2fd08008388f3ef460a00869e13a56079cf013fb82d22c394c2c619c3c33ac45304c527e19d4d0c008aab1305c0fa1aff6050110083687dc713c396c0c2c1c104c1c6b10f0072b54cc14d83c519c1760e0055b9f8c1f8187486', 'hex'), # raw 104 + codecs.decode('750d00797ff0fdee593bc1090086781657267f11004cc1375050827df4000000dd0548b10100320038ffc024c2fec4c1c18c05c4fad0013ec54051c2879d00cb56521cc2c204c50fc2e62506008eca1a05fec5250d0072d23dc344c2c45cc10a008bd31a3afefa1a92c0080034e68642c45d0d005bdd376707c08da002008ede', 'hex'), # raw 128 + codecs.decode('24ffc100e405213306002de78637c4de011de846ff98c100', 'hex'), # raw 24 + codecs.decode('07283b590300fef3f5f800da10f5494b031000071819061035084365650b14900834c0c1c4c104c1c5a302100e1134c1c01045c83c8806110e2185c22edd11082424fec006ff02cb052834c3c073c910d4eb965b3833ff0bc582cce18d876a051106f337f826c00410013d2b05c200ca003f4cfeff03d56454ccc101', 'hex'), # raw 124 + codecs.decode('5050827d08000000d007fcf701003200', 'hex'), # tcp CMD_ACK_OK + #codecs.decode('5050827d08000000d00745b2cf451b00', 'hex'), # tcp random CMD_ACK_OK TODO: generate proper sequenced response + ] + #begin + zk = ZK('192.168.1.201', verbose=True) + conn = zk.connect() + socket.return_value.send.assert_called_with(codecs.decode('5050827d08000000e80317fc00000000', 'hex')) + template = conn.get_user_template(14, 1) + self.assertEqual(template.size, 1243, "incorrect size %s" % template.size) + self.assertEqual(template.mark, "4d98535332310000...feff03d56454ccc1", "incorrect mark %s" % template.mark) + self.assertEqual(template.uid, 14, "incorrect uid %s" % template.uid) + conn.disconnect() + + @patch('zk.base.socket') + @patch('zk.base.ZK_helper') + def test_tcp_get_template_1f(self, helper, socket): + """ cchekc correct template 1 fixed""" + helper.return_value.test_ping.return_value = True # ping simulated + helper.return_value.test_tcp.return_value = 0 # helper tcp ok + socket.return_value.recv.side_effect = [ + codecs.decode('5050827d08000000d0075fb2cf450100', 'hex'), # tcp CMD_ACK_OK + codecs.decode('5050827d10000000dc055558d0983200dc040000f0030000', 'hex'), # tcp PREPARE_DATA 1244 + codecs.decode('5050827df8030000dd0500f4000032004d9853533231000004dbda0408050709ced000001cda69010000008406316adb0c0012062900d000aad221001600390caf001cdbb106240031007e033bdb3b00e9067700850083d42b004300c503f40043dbd6037b005000460ea7db5900910f90009f0012d5e7005c00970a5f006ddb930fa1009a00560f86db9d00820e86006f007dd3f400ab00a60fcd01b7dbb00b4b00bd0079083adbc00045035d000600c1df7300cc0039049e00dddb380e8c00da00e30dd8dbdc00220e130027004dd9f500e3009d0a6a00e9db26090001ef00ea03c5dbf0002306', 'hex'), # DATA (tcp 1016, actual 112 +104 + codecs.decode('d000380028d83400ff00430f6200fbdba70dfb0002016203c5db0201a5044b00c10132d4de0006019f080a000cdab70541000f01fe0f19db1901c902e600dc0198d839002f01360ed80037dabd04d4003301520104da38014f01a100830196d5f5004b015c0411005cdacd03bc67ab8d162b48ad18f7fec7448e448387afa1a3062b37ca3cf9f53c8087f9150926e03335df1b71aedbd0f2', 'hex'), # raw 128 + 24 + codecs.decode('b40da90541168df1551f70fc15b51bf26d7d4501bf12915e6485fd966f0ba2072728987dc1018a12ab105ec7aa003508fef08a49b923f3e85e42edf5ea861bd1600d23151787fc78d522f38431883e809f0e4dd2008ecd8ed97670035acf0c763503f27c37ec76d982806986c6016bf952d01e0673820570a87e1a236005ad817d8734949952bb929d81e5fdbcf99ca0c4886d8c65098c0e9aa6ac81e103c684607951d03b0ce9f0cd785885ad27d4f61bfc5de8bc7411de8d8f5910c518e004e9229304f90f9a891395912680ebc6f4c57fd3fceeb684f7c18ba78107fc2e16073e89f6d6b67fbb', 'hex'), # raw 128 +104 + codecs.decode('fb11e2feb3effd0e5391c61da77176359f7e4d8a0ff3090a01204501c76a19af07002b003ac0042300dbab0113c2fa07c56e02cbc32bc10400a1c31349df0008102d2a04c5120c9b8904008f0810fb0404c20f3a6407006fd709fbecfe0400041529f60304fd1931fb0b006ede0c391bc1c0c0460e00a3210b1a34c2ffffc3fd980f04832806404a5bc1940505da86292d0f0056f600f925', 'hex'), # raw 128 +24 + codecs.decode('5c43c243ff06c5733a5d85c7080040473f3d31dd01774d8983c4c000778982750b009459d551c426c3c0170900929b17fba3fc780800376135fefbe0ff1100396aed3b3146265ac0c1ffff15c5357232fffdc0fdc03f3bc141914514003f85e738fdfa2441ff5cc0ff45951504ec7ee9c0fac1fc053dc424c0554affc103c5f894f2fd0e00668b06eac1f9b3c3fdc2fd08008388f3ef460a00869e13a56079cf013fb82d22c394c2c619c3c33ac45304c527e19d4d0c008aab1305c0fa1aff6050110083687dc713c396c0c2c1c104c1c6b10f0072b54cc14d83c519c1760e0055b9f8c1f8187486', 'hex'), # raw 128 +104 + codecs.decode('750d00797ff0fdee593bc1090086781657267f11004cc137', 'hex'), # raw 24? + codecs.decode('5050827df4000000dd0548b10100320038ffc024c2fec4c1c18c05c4fad0013ec54051c2879d00cb56521cc2c204c50fc2e62506008eca1a05fec5250d0072d23dc344c2c45cc10a008bd31a3afefa1a92c0080034e68642c45d0d005bdd376707c08da002008ede24ffc100e405213306002de78637c4de011de846ff98c100', 'hex'), # raw 128-24 (104) +24 + codecs.decode('07283b590300fef3f5f800da10f5494b031000071819061035084365650b14900834c0c1c4c104c1c5a302100e1134c1c01045c83c8806110e2185c22edd11082424fec006ff02cb052834c3c073c910d4eb965b3833ff0bc582cce18d876a051106f337f826c00410013d2b05c200ca003f4cfeff03d56454ccc101', 'hex'), # raw 124 + codecs.decode('5050827d08000000d007fcf701003200', 'hex'), # tcp CMD_ACK_OK + codecs.decode('5050827d08000000d00745b2cf451b00', 'hex'), # tcp random CMD_ACK_OK TODO: generate proper sequenced response + ] + #begin + zk = ZK('192.168.1.201') #, verbose=True) + conn = zk.connect() + socket.return_value.send.assert_called_with(codecs.decode('5050827d08000000e80317fc00000000', 'hex')) + template = conn.get_user_template(14, 1) + self.assertEqual(template.size, 1243, "incorrect size %s" % template.size) + self.assertEqual(template.mark, "4d98535332310000...feff03d56454ccc1", "incorrect mark %s" % template.mark) + self.assertEqual(template.uid, 14, "incorrect uid %s" % template.uid) + conn.disconnect() + + @patch('zk.base.socket') + @patch('zk.base.ZK_helper') + def test_tcp_get_template_2f(self, helper, socket): + """ cchekc correct template 2 fixed""" + helper.return_value.test_ping.return_value = True # ping simulated + helper.return_value.test_tcp.return_value = 0 # helper tcp ok + socket.return_value.recv.side_effect = [ + codecs.decode('5050827d08000000d0075fb2cf450100', 'hex'), # tcp CMD_ACK_OK + codecs.decode('5050827d10000000dc053b59d0983500f3030000f0030000', 'hex'), # tcp PREPARE_DATA 1011 + codecs.decode('5050827df8030000dd056855000035004ab153533231000003f2f10408050709ced000001bf36901000000831f256cf23e00740f4c008900f2f879005500fe0fe3005bf2d30a60005c00a00f32f26600580a2700ad00e3fd98007500800f000082f21a0f68008300300e5bf28d00570930004b00dafd4c009a00dd090900a8f2270f8600ad008a0b1ff2b000480f4400730040fc5400b800430f4400c6f2370ab100ca00f30ecbf2cb002f0f4a001300c7fdaa00e400b50c4300e6f2b706bf00ea00f90668f2f2002e0dad003000b7f7cf00f600350cbe0008f31f0dd0000c017101cbf20f019c01', 'hex'), # DATA (tcp 1016, actual 112 +104 + codecs.decode('5e00d4012dfdda001301a408e00019f3400c12002201fc0c4ff2570193096d0092018dfc3c7a62107e85688f818ff39a358ef99acb0fee06d47da2e2116a7c77f102a57bd1890a6a598b5ee2db0a0f64a384b28da105f29ca7eff9a137194560847d1565aa827ffc69705ffa8189f19f1f9ca10abbf2160f791a6e0dd8af0f723e062b6e84000a997780c100f6684b8016188780d7f44d0a', 'hex'), # raw 128 + 24 + codecs.decode('5083790fd0fa1a089ef44b807572db9b0900d9795083397a8780ca0161091489ae7b7c134278a6004c00b68bcf80e9f98982509a0e01dbf02e6a441a21138a70ddeaf1f9b16a8f1025f2ceef74f369094b70b2fb3a176bb339f9860f6459f304bb679757b3fca891ba733c4c6444c72032f303131c9705004b3079bc0600a03a89c405fdc03205004b456254c6006fb276c20a00a94343c2fc30779505001b4f862804f27d51faff31c2cd007fa50141c12f1800085a9431c181c4fe83c10674c33275300600245c89fcc0ad07005b5c6b88040503a96267c1830700e9695d30c1c2510a0031ae57', 'hex'), # raw 128 +104 + codecs.decode('5fa47a04007c7574510f039e80f0fd3bfefe9d55c3fa01c7841746ff06fa1ff2ee8ea07e787e0689c133c1c3c0c2ffc004c1fcae07005990578c040d03dc9350c0c4376a3a8623f2f29ea2c17c67b0928330726b6a83ff08c582afa8c5c3c3c1c3fec300895f0efdfd2809000bae21be5afd0c001cb68c59c20dc3fefda205004fb8150cfbc1030089bbffc30ef245bc467bc07404c288fd', 'hex'), # raw 128 +24 + codecs.decode('0155bd46786445c3c130c0040091c52938c320f305c8a4c1ff7b05c08a63c3c2c1c2c3c13ac1c132c1ffc2c0c0c205c3c336050084c9306ec100b13f352c0700cacdf56b72f611f61a2d1605d5ef41a4fec0f818004c17c63e0dfef9c0fdfffe3b3649a0fac00c004ada856a6464c20b006cf83145c1c032c23d04109804d57617e28f07a0fe3bff3bfbfe0afc2ac0fdc138c01095f91bc543281101cbb0c19758fe9282c3c26270737997c1c0c2c0c204c70be27f0f2084c5fc070913ad1731c2c1c37b0125130c1ba958c049ff4e9bc6529262c1c290c2076ac2ed11e718a9554b068bc730b196', 'hex'), # raw 128 +104 + codecs.decode('c2c1c2c1077dfc830210074929c1c910c5af81c0c1ffc2fe', 'hex'), # raw 24? + codecs.decode('5050827d0b000000dd054ba201003500a05701', 'hex'), # raw 43-24 (104) + + codecs.decode('5050827d08000000d007fcf701003200', 'hex'), # tcp CMD_ACK_OK + codecs.decode('5050827d08000000d00745b2cf451b00', 'hex'), # tcp random CMD_ACK_OK TODO: generate proper sequenced response + ] + #begin + zk = ZK('192.168.1.201', verbose=True) + conn = zk.connect() + socket.return_value.send.assert_called_with(codecs.decode('5050827d08000000e80317fc00000000', 'hex')) + template = conn.get_user_template(14, 1) + self.assertEqual(template.size, 1010, "incorrect size %s" % template.size) + self.assertEqual(template.mark, "4ab1535332310000...81c0c1ffc2fea057", "incorrect mark %s" % template.mark) + self.assertEqual(template.uid, 14, "incorrect uid %s" % template.uid) + conn.disconnect() + if __name__ == '__main__': unittest.main() diff --git a/test_machine.py b/test_machine.py index 4af5724..5d09586 100755 --- a/test_machine.py +++ b/test_machine.py @@ -40,6 +40,8 @@ parser.add_argument('-t', '--templates', action="store_true", help='Get templates / fingers (compare bulk and single read)') parser.add_argument('-tr', '--templates-raw', action="store_true", help='Get raw templates (dump templates)') +parser.add_argument('-ti', '--templates-index', type=int, + help='Get specific template', default=0) parser.add_argument('-r', '--records', action="store_true", help='Get attendance records') parser.add_argument('-u', '--updatetime', action="store_true", @@ -166,6 +168,7 @@ try: conn.save_user_template(zk_user)# forced creation args.enrolluser = uid conn.refresh_data() + if args.enrolluser: uid = int(args.enrolluser) print ('--- Enrolling User #{} ---'.format(uid)) @@ -180,7 +183,14 @@ try: conn.refresh_data() #print ("Voice Test ...") #conn.test_voice(10) - if args.templates or args.templates_raw: + if args.templates_index: + print ("Read Single template... {}".format(args.templates_index)) + inicio = time.time() + template = conn.get_user_template(args.templates_index, args.finger) + final = time.time() + print (' took {:.3f}[s]'.format(final - inicio)) + print (" single! {}".format(template)) + elif args.templates or args.templates_raw: print ("Read Templates...") inicio = time.time() templates = conn.get_templates() diff --git a/zk/base.py b/zk/base.py index c0f8b04..5096d0e 100644 --- a/zk/base.py +++ b/zk/base.py @@ -1290,6 +1290,7 @@ class ZK(object): size -= len(resp) if self.verbose: print ("new tcp DATA packet to fill misssing {}".format(size)) data_recv = bh + self.__sock.recv(size) #ideal limit? + if self.verbose: print ("new tcp DATA starting with {} bytes".format(len(data_recv))) resp, bh = self.__recieve_tcp_data(data_recv, size) data.append(resp) if self.verbose: print ("for misssing {} recieved {} with extra {}".format(size, len(resp), len(bh))) @@ -1333,6 +1334,7 @@ class ZK(object): data_recv = self.__sock.recv(size) #ideal limit? recieved = len(data_recv) if self.verbose: print ("partial recv {}".format(recieved)) + if recieved < 100 and self.verbose: print (" recv {}".format(codecs.encode(data_recv, 'hex'))) data.append(data_recv) # w/o tcp and header size -= recieved if self.verbose: print ("still need {}".format(size)) From 5c8eb1f4d2b3748610f0c3496654d78dd03e3575 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Wed, 12 Sep 2018 16:20:37 -0400 Subject: [PATCH 75/83] test: fix read broken data packets --- zk/base.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/zk/base.py b/zk/base.py index 5096d0e..08c20e3 100644 --- a/zk/base.py +++ b/zk/base.py @@ -1289,7 +1289,7 @@ class ZK(object): data.append(resp) size -= len(resp) if self.verbose: print ("new tcp DATA packet to fill misssing {}".format(size)) - data_recv = bh + self.__sock.recv(size) #ideal limit? + data_recv = bh + self.__sock.recv(size + 16 ) #ideal limit + header if self.verbose: print ("new tcp DATA starting with {} bytes".format(len(data_recv))) resp, bh = self.__recieve_tcp_data(data_recv, size) data.append(resp) @@ -1358,13 +1358,17 @@ class ZK(object): return self.__data #without headers elif self.__response == const.CMD_PREPARE_DATA: data = [] - size = self.__get_data_size() + size = self.__get_data_size() # from prepare data response... if self.verbose: print ("recieve chunk: prepare data size is {}".format(size)) if self.tcp: + if self.verbose: print ("recieve chunk: len data is {}".format(len(self.__data))) + #ideally 8 bytes of PREPARE_DATA only... + #but sometimes it comes with data... + if len(self.__data) >= (8 + size): #prepare data with actual data! should be 8+size+32 - data_recv = self.__data[8:] # test, maybe -32 + data_recv = self.__data[8:] # no need for more data! test, maybe -32 else: - data_recv = self.__sock.recv(size + 32) #could have two commands + data_recv = self.__data[8:] + self.__sock.recv(size + 32) #could have two commands resp, broken_header = self.__recieve_tcp_data(data_recv, size) data.append(resp) # get CMD_ACK_OK From 7a97e382c7d77fa5210b3985f36f20b9f2e2e840 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Thu, 13 Sep 2018 09:47:49 -0400 Subject: [PATCH 76/83] update readme --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 92c6c97..7befb98 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ for attendance in conn.live_capture(): # and disables live capture ``` -Test Machine +**Test Machine** ```sh usage: ./test_machine.py [-h] [-a ADDRESS] [-p PORT] [-T TIMEOUT] [-P PASSWORD] @@ -249,7 +249,7 @@ optional arguments: ``` -Backup/Restore (Users and fingers only!!!) (WARNING! destructive test! do it at your own risk!) +**Backup/Restore (Users and fingers only!!!)** *(WARNING! destructive test! do it at your own risk!)* ```sh usage: ./test_backup_restore.py [-h] [-a ADDRESS] [-p PORT] [-T TIMEOUT] @@ -333,6 +333,10 @@ DeviceName : K14 (not tested, but same behavior like the other one) Firmware Version : Ver 6.60 Jun 9 2017 Platform : JZ4725_TFT DeviceName : K20 (Active testing! latest fix) + +Firmware Version : Ver 6.70 Jul 12 2013 +Platform : ZEM600_TFT +DeviceName : iClock880-H/ID (Active testing! latest fix) ``` ### Not Working (needs more tests, more information) From 1233ec86f3dc44060e7b76e284a3873a80a384db Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Thu, 13 Sep 2018 10:23:58 -0400 Subject: [PATCH 77/83] added erase to backup (to clean tests) --- README.md | 1 + test_backup_restore.py | 34 ++++++++++++++++++++-------------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 7befb98..9559b9f 100644 --- a/README.md +++ b/README.md @@ -272,6 +272,7 @@ optional arguments: Device code/password -f, --force-udp Force UDP communication -v, --verbose Print debug information + -E, --erase clean the device after writting backup! -r, --restore Restore from backup -c, --clear-attendance On Restore, also clears the attendance [default keep diff --git a/test_backup_restore.py b/test_backup_restore.py index 54812ca..5450e3d 100755 --- a/test_backup_restore.py +++ b/test_backup_restore.py @@ -36,6 +36,8 @@ parser.add_argument('-f', '--force-udp', action="store_true", help='Force UDP communication') parser.add_argument('-v', '--verbose', action="store_true", help='Print debug information') +parser.add_argument('-E', '--erase', action="store_true", + help='clean the device after writting backup!') parser.add_argument('-r', '--restore', action="store_true", help='Restore from backup') parser.add_argument('-c', '--clear-attendance', action="store_true", @@ -45,6 +47,21 @@ parser.add_argument('filename', nargs='?', args = parser.parse_args() +def erase_device(conn, serialnumber, clear_attendance=False): + """input serial number to corroborate.""" + print ('WARNING! the next step will erase the current device content.') + print ('Please input the serialnumber of this device [{}] to acknowledge the ERASING!'.format(serialnumber)) + new_serial = input ('Serial Number : ') + if new_serial != serialnumber: + raise BasicException('Serial number mismatch') + conn.disable_device() + print ('Erasing device...') + conn.clear_data() + if clear_attendance: + print ('Clearing attendance too!') + conn.clear_attendance() + conn.read_sizes() + print (conn) zk = ZK(args.address, port=args.port, timeout=args.timeout, password=args.password, force_udp=args.force_udp, verbose=args.verbose) @@ -86,6 +103,8 @@ try: } json.dump(data, output, indent=1) output.close() + if args.erase: + erase_device(conn, serialnumber, args.clear_attendance) else: print ('Reading file {}'.format(filename)) infile = open(filename, 'r') @@ -103,20 +122,7 @@ try: templates = [Finger.json_unpack(t) for t in data['templates']] #print (templates) print ("INFO: ready to write {} templates".format(len(templates))) - #input serial number to corroborate. - print ('WARNING! the next step will erase the current device content.') - print ('Please input the serialnumber of this device [{}] to acknowledge the ERASING!'.format(serialnumber)) - new_serial = input ('Serial Number : ') - if new_serial != serialnumber: - raise BasicException('Serial number mismatch') - conn.disable_device() - print ('Erasing device...') - conn.clear_data() - if args.clear_attendance: - print ('Clearing attendance too!') - conn.clear_attendance() - conn.read_sizes() - print (conn) + erase_device(conn, serialnumber, args.clear_attendance) print ('Restoring Data...') for u in users: #look for Templates From c2533da942b2ffcf1d3f300c571c690d28dcbc28 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Thu, 13 Sep 2018 10:30:34 -0400 Subject: [PATCH 78/83] don't read empty fingers --- zk/base.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/zk/base.py b/zk/base.py index 08c20e3..03bb3b9 100644 --- a/zk/base.py +++ b/zk/base.py @@ -972,6 +972,9 @@ class ZK(object): def get_templates(self): """ return array of all fingers """ + self.read_sizes() # last update + if self.fingers == 0: #lazy + return [] templates = [] templatedata, size = self.read_with_buffer(const.CMD_DB_RRQ, const.FCT_FINGERTMP) if size < 4: From 5406985142aabdb9cc925b7c82bfdba47f9410ab Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Thu, 13 Sep 2018 11:46:01 -0400 Subject: [PATCH 79/83] fix extend fmt (not needed) --- zk/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zk/base.py b/zk/base.py index 03bb3b9..81661f1 100644 --- a/zk/base.py +++ b/zk/base.py @@ -499,7 +499,8 @@ class ZK(object): #definitivo? seleccionar firmware aqui? return safe_cast(fmt, int, 0) if fmt else 0 else: - raise ZKErrorResponse("Can't read extend fmt") + return None + #raise ZKErrorResponse("Can't read extend fmt") def get_user_extend_fmt(self): ''' From 063c520bb14e2a268814b40e951696f03e71b8ab Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Thu, 13 Sep 2018 12:07:32 -0400 Subject: [PATCH 80/83] fix face_fun_on --- README.md | 11 ++++++++--- zk/base.py | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9559b9f..5ea09fb 100644 --- a/README.md +++ b/README.md @@ -316,6 +316,9 @@ Firmware Version : Ver 6.60 Nov 6 2017 (remote tested with correct results) Platform : ZMM220_TFT DeviceName : (unknown device) (broken info but at least the important data was read) +Firmware Version : Ver 6.60 Jun 9 2017 +Platform : JZ4725_TFT +DeviceName : K20 (latest checked correctly!) ``` @@ -331,9 +334,11 @@ Firmware Version : Ver 6.60 Jun 16 2015 Platform : JZ4725_TFT DeviceName : K14 (not tested, but same behavior like the other one) -Firmware Version : Ver 6.60 Jun 9 2017 -Platform : JZ4725_TFT -DeviceName : K20 (Active testing! latest fix) + + +Firmware Version : Ver 6.60 Jun 5 2015 +Platform : ZMM200_TFT +DeviceName : iClock3000/ID (Active testing! latest fix) Firmware Version : Ver 6.70 Jul 12 2013 Platform : ZEM600_TFT diff --git a/zk/base.py b/zk/base.py index 81661f1..983d739 100644 --- a/zk/base.py +++ b/zk/base.py @@ -530,7 +530,7 @@ class ZK(object): if cmd_response.get('status'): response = (self.__data.split(b'=', 1)[-1].split(b'\x00')[0]) #definitivo? seleccionar firmware aqui? - return int(response) if response else 0 + return safe_cast(response, int ,0) if response else 0 else: return None From ded7b1d395dae7349a3643f3ce96c0cddf7228e7 Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Thu, 13 Sep 2018 12:22:47 -0400 Subject: [PATCH 81/83] added clear error (test) --- zk/base.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/zk/base.py b/zk/base.py index 983d739..bcb0198 100644 --- a/zk/base.py +++ b/zk/base.py @@ -484,7 +484,14 @@ class ZK(object): return safe_cast(response, int, 0) if response else 0 else: return None - + def _clear_error(self, command_string=b''): + """ clear ACK_error """ + cmd_response = self.__send_command(const.CMD_ACK_ERROR, command_string, 1024) + # cmd_response['code'] shuld be CMD_ACK_UNKNOWN + cmd_response = self.__send_command(const.CMD_ACK_UNKNOWN, command_string, 1024) + cmd_response = self.__send_command(const.CMD_ACK_UNKNOWN, command_string, 1024) + cmd_response = self.__send_command(const.CMD_ACK_UNKNOWN, command_string, 1024) + def get_extend_fmt(self): ''' determine extend fmt @@ -499,6 +506,7 @@ class ZK(object): #definitivo? seleccionar firmware aqui? return safe_cast(fmt, int, 0) if fmt else 0 else: + self._clear_error(command_string) return None #raise ZKErrorResponse("Can't read extend fmt") @@ -516,6 +524,7 @@ class ZK(object): #definitivo? seleccionar firmware aqui? return safe_cast(fmt, int, 0) if fmt else 0 else: + self._clear_error(command_string) return None def get_face_fun_on(self): @@ -532,6 +541,7 @@ class ZK(object): #definitivo? seleccionar firmware aqui? return safe_cast(response, int ,0) if response else 0 else: + self._clear_error(command_string) return None def get_compat_old_firmware(self): @@ -548,6 +558,7 @@ class ZK(object): #definitivo? seleccionar firmware aqui? return safe_cast(response, int, 0) if response else 0 else: + self._clear_error(command_string) return None def get_network_params(self): From 2603513ce3c269e102602366e2e5e00fd9506dbf Mon Sep 17 00:00:00 2001 From: Arturo Hernandez Date: Thu, 13 Sep 2018 12:38:52 -0400 Subject: [PATCH 82/83] added 0 end string --- zk/base.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/zk/base.py b/zk/base.py index bcb0198..4724df6 100644 --- a/zk/base.py +++ b/zk/base.py @@ -397,7 +397,7 @@ class ZK(object): return the serial number ''' command = const.CMD_OPTIONS_RRQ - command_string = b'~SerialNumber' + command_string = b'~SerialNumber\x00' response_size = 1024 cmd_response = self.__send_command(command, command_string, response_size) if cmd_response.get('status'): @@ -412,7 +412,7 @@ class ZK(object): return the platform name ''' command = const.CMD_OPTIONS_RRQ - command_string = b'~Platform' + command_string = b'~Platform\x00' response_size = 1024 cmd_response = self.__send_command(command, command_string, response_size) @@ -428,7 +428,7 @@ class ZK(object): return the mac ''' command = const.CMD_OPTIONS_RRQ - command_string = b'MAC' + command_string = b'MAC\x00' response_size = 1024 cmd_response = self.__send_command(command, command_string, response_size) @@ -443,7 +443,7 @@ class ZK(object): return the device name ''' command = const.CMD_OPTIONS_RRQ - command_string = b'~DeviceName' + command_string = b'~DeviceName\x00' response_size = 1024 cmd_response = self.__send_command(command, command_string, response_size) @@ -459,7 +459,7 @@ class ZK(object): return the face version ''' command = const.CMD_OPTIONS_RRQ - command_string = b'ZKFaceVersion' + command_string = b'ZKFaceVersion\x00' response_size = 1024 cmd_response = self.__send_command(command, command_string, response_size) @@ -474,7 +474,7 @@ class ZK(object): return the fingerprint version ''' command = const.CMD_OPTIONS_RRQ - command_string = b'~ZKFPVersion' + command_string = b'~ZKFPVersion\x00' response_size = 1024 cmd_response = self.__send_command(command, command_string, response_size) @@ -497,7 +497,7 @@ class ZK(object): determine extend fmt ''' command = const.CMD_OPTIONS_RRQ - command_string = b'~ExtendFmt' + command_string = b'~ExtendFmt\x00' response_size = 1024 cmd_response = self.__send_command(command, command_string, response_size) @@ -515,7 +515,7 @@ class ZK(object): determine user extend fmt ''' command = const.CMD_OPTIONS_RRQ - command_string = b'~UserExtFmt' + command_string = b'~UserExtFmt\x00' response_size = 1024 cmd_response = self.__send_command(command, command_string, response_size) @@ -532,7 +532,7 @@ class ZK(object): determine extend fmt ''' command = const.CMD_OPTIONS_RRQ - command_string = b'FaceFunOn' + command_string = b'FaceFunOn\x00' response_size = 1024 cmd_response = self.__send_command(command, command_string, response_size) @@ -549,7 +549,7 @@ class ZK(object): determine old firmware ''' command = const.CMD_OPTIONS_RRQ - command_string = b'CompatOldFirmware' + command_string = b'CompatOldFirmware\x00' response_size = 1024 cmd_response = self.__send_command(command, command_string, response_size) @@ -565,13 +565,13 @@ class ZK(object): ip = self.__address[0] mask = b'' gate = b'' - cmd_response = self.__send_command(const.CMD_OPTIONS_RRQ, b'IPAddress', 1024) + cmd_response = self.__send_command(const.CMD_OPTIONS_RRQ, b'IPAddress\x00', 1024) if cmd_response.get('status'): ip = (self.__data.split(b'=', 1)[-1].split(b'\x00')[0]) - cmd_response = self.__send_command(const.CMD_OPTIONS_RRQ, b'NetMask', 1024) + cmd_response = self.__send_command(const.CMD_OPTIONS_RRQ, b'NetMask\x00', 1024) if cmd_response.get('status'): mask = (self.__data.split(b'=', 1)[-1].split(b'\x00')[0]) - cmd_response = self.__send_command(const.CMD_OPTIONS_RRQ, b'GATEIPAddress', 1024) + cmd_response = self.__send_command(const.CMD_OPTIONS_RRQ, b'GATEIPAddress\x00', 1024) if cmd_response.get('status'): gate = (self.__data.split(b'=', 1)[-1].split(b'\x00')[0]) return {'ip': ip.decode(), 'mask': mask.decode(), 'gateway': gate.decode()} From 0e1cfd3f8c32bf156d64c380d6a3617ebee45f16 Mon Sep 17 00:00:00 2001 From: kurenai-ryu Date: Mon, 1 Oct 2018 17:43:48 -0400 Subject: [PATCH 83/83] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 7457487..e7eb329 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -17,7 +17,7 @@ Steps to reproduce the behavior: A clear and concise description of what you expected to happen. **Capture Data** -Try to always include captured data (pcap files, and verbose output from test_machine if applicable) +Try to always include captured data (pcap files from wireshark [(tutorial)](https://youtu.be/AsDedCgkhnA), and verbose output from test_machine if applicable) **System (please complete the following information):** - OS: [e.g. iOS, Debian 9]