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'
        })
Example #4
0
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])
Example #7
0
    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