def test_bytes(self): self.assertEqual(bencode(b''), b'0:') self.assertEqual(bencode(b'spam'), b'4:spam') self.assertEqual(bencode(b'4:spam'), b'6:4:spam') self.assertEqual(bencode(bytearray(b'spam')), b'4:spam') self.assertEqual(bdecode(b'0:'), b'') self.assertEqual(bdecode(b'4:spam'), b'spam') self.assertEqual(bdecode(b'6:4:spam'), b'4:spam')
def test_mixed(self): self.assertEqual( bencode([[b'abc', b'127.0.0.1', 1919], [b'def', b'127.0.0.1', 1921]]), b'll3:abc9:127.0.0.1i1919eel3:def9:127.0.0.1i1921eee') self.assertEqual( bdecode(b'll3:abc9:127.0.0.1i1919eel3:def9:127.0.0.1i1921eee'), [[b'abc', b'127.0.0.1', 1919], [b'def', b'127.0.0.1', 1921]])
def test_dict(self): self.assertEqual(bencode({ b'foo': 42, b'bar': b'spam' }), b'd3:bar4:spam3:fooi42ee') self.assertEqual(bdecode(b'd3:bar4:spam3:fooi42ee'), { b'foo': 42, b'bar': b'spam' })
def debug_kademlia_packet(data, source, destination, node): if log.level != logging.DEBUG: return try: packet = _datagram_formatter.fromPrimitive(encoding.bdecode(data)) if isinstance(packet, RequestMessage): log.debug("request %s --> %s %s (node time %s)", source[0], destination[0], packet.request, node.clock.seconds()) elif isinstance(packet, ResponseMessage): if isinstance(packet.response, bytes): log.debug("response %s <-- %s %s (node time %s)", destination[0], source[0], packet.response, node.clock.seconds()) else: log.debug("response %s <-- %s %i contacts (node time %s)", destination[0], source[0], len(packet.response), node.clock.seconds()) elif isinstance(packet, ErrorMessage): log.error("error %s <-- %s %s (node time %s)", destination[0], source[0], packet.exceptionType, node.clock.seconds()) except DecodeError: log.exception("decode error %s --> %s (node time %s)", source[0], destination[0], node.clock.seconds())
def test_integer(self): self.assertEqual(bencode(42), b'i42e') self.assertEqual(bdecode(b'i42e'), 42)
def test_list(self): self.assertEqual(bencode([b'spam', 42]), b'l4:spami42ee') self.assertEqual(bdecode(b'l4:spami42ee'), [b'spam', 42])
def datagramReceived(self, datagram, address): """ Handles and parses incoming RPC messages (and responses) @note: This is automatically called by Twisted when the protocol receives a UDP datagram """ if chr(datagram[0]) == '\x00' and chr(datagram[25]) == '\x00': totalPackets = (datagram[1] << 8) | datagram[2] msgID = datagram[5:25] seqNumber = (datagram[3] << 8) | datagram[4] if msgID not in self._partialMessages: self._partialMessages[msgID] = {} self._partialMessages[msgID][seqNumber] = datagram[26:] if len(self._partialMessages[msgID]) == totalPackets: keys = self._partialMessages[msgID].keys() keys.sort() data = b'' for key in keys: data += self._partialMessages[msgID][key] datagram = data del self._partialMessages[msgID] else: return try: msgPrimitive = encoding.bdecode(datagram) message = self._translator.fromPrimitive(msgPrimitive) except (encoding.DecodeError, ValueError) as err: # We received some rubbish here log.warning("Error decoding datagram %s from %s:%i - %s", hexlify(datagram), address[0], address[1], err) return except (IndexError, KeyError): log.warning("Couldn't decode dht datagram from %s", address) return if isinstance(message, msgtypes.RequestMessage): # This is an RPC method request remoteContact = self._node.contact_manager.make_contact( message.nodeID, address[0], address[1], self) remoteContact.update_last_requested() # only add a requesting contact to the routing table if it has replied to one of our requests if remoteContact.contact_is_good is True: df = self._node.addContact(remoteContact) else: df = defer.succeed(None) df.addCallback(lambda _: self._handleRPC( remoteContact, message.id, message.request, message.args)) # if the contact is not known to be bad (yet) and we haven't yet queried it, send it a ping so that it # will be added to our routing table if successful if remoteContact.contact_is_good is None and remoteContact.lastReplied is None: df.addCallback(lambda _: self._ping_queue.enqueue_maybe_ping( remoteContact)) elif isinstance(message, msgtypes.ErrorMessage): # The RPC request raised a remote exception; raise it locally if message.exceptionType in BUILTIN_EXCEPTIONS: exception_type = BUILTIN_EXCEPTIONS[message.exceptionType] else: exception_type = UnknownRemoteException remoteException = exception_type(message.response) log.error("DHT RECV REMOTE EXCEPTION FROM %s:%i: %s", address[0], address[1], remoteException) if message.id in self._sentMessages: # Cancel timeout timer for this RPC remoteContact, df, timeoutCall, timeoutCanceller, method = self._sentMessages[ message.id][0:5] timeoutCanceller() del self._sentMessages[message.id] # reject replies coming from a different address than what we sent our request to if (remoteContact.address, remoteContact.port) != address: log.warning( "Sent request to node %s at %s:%i, got reply from %s:%i", remoteContact.log_id(), remoteContact.address, remoteContact.port, address[0], address[1]) df.errback(TimeoutError(remoteContact.id)) return # this error is returned by nodes that can be contacted but have an old # and broken version of the ping command, if they return it the node can # be contacted, so we'll treat it as a successful ping old_ping_error = "ping() got an unexpected keyword argument '_rpcNodeContact'" if isinstance(remoteException, TypeError) and \ remoteException.message == old_ping_error: log.debug("old pong error") df.callback('pong') else: df.errback(remoteException) elif isinstance(message, msgtypes.ResponseMessage): # Find the message that triggered this response if message.id in self._sentMessages: # Cancel timeout timer for this RPC remoteContact, df, timeoutCall, timeoutCanceller, method = self._sentMessages[ message.id][0:5] timeoutCanceller() del self._sentMessages[message.id] log.debug("%s:%i RECV response to %s from %s:%i", self._node.externalIP, self._node.port, method, remoteContact.address, remoteContact.port) # When joining the network we made Contact objects for the seed nodes with node ids set to None # Thus, the sent_to_id will also be None, and the contact objects need the ids to be manually set. # These replies have be distinguished from those where the node id in the datagram does not match # the node id of the node we sent a message to (these messages are treated as an error) if remoteContact.id and remoteContact.id != message.nodeID: # sent_to_id will be None for bootstrap log.debug("mismatch: (%s) %s:%i (%s vs %s)", method, remoteContact.address, remoteContact.port, remoteContact.log_id(False), hexlify(message.nodeID)) df.errback(TimeoutError(remoteContact.id)) return elif not remoteContact.id: remoteContact.set_id(message.nodeID) # We got a result from the RPC df.callback(message.response) else: # If the original message isn't found, it must have timed out # TODO: we should probably do something with this... pass