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