Example #1
0
    def testMessage(self):
        Message(99, b"", self.ser.serializer_id, 0,
                0)  # doesn't check msg type here
        self.assertRaises(Pyro4.errors.ProtocolError, Message.from_header,
                          "FOOBAR")
        msg = Message(Pyro4.message.MSG_CONNECT, b"hello",
                      self.ser.serializer_id, 0, 0)
        self.assertEqual(Pyro4.message.MSG_CONNECT, msg.type)
        self.assertEqual(5, msg.data_size)
        self.assertEqual(b"hello", msg.data)
        self.assertEqual(4 + 2 + 20, msg.annotations_size)
        mac = pyrohmac(b"hello", msg.annotations)
        self.assertDictEqual({"HMAC": mac}, msg.annotations)

        hdr = msg.to_bytes()[:24]
        msg = Message.from_header(hdr)
        self.assertEqual(Pyro4.message.MSG_CONNECT, msg.type)
        self.assertEqual(4 + 2 + 20, msg.annotations_size)
        self.assertEqual(5, msg.data_size)

        hdr = Message(Pyro4.message.MSG_RESULT, b"", self.ser.serializer_id, 0,
                      0).to_bytes()[:24]
        msg = Message.from_header(hdr)
        self.assertEqual(Pyro4.message.MSG_RESULT, msg.type)
        self.assertEqual(4 + 2 + 20, msg.annotations_size)
        self.assertEqual(0, msg.data_size)

        hdr = Message(Pyro4.message.MSG_RESULT, b"hello", 12345, 60006,
                      30003).to_bytes()[:24]
        msg = Message.from_header(hdr)
        self.assertEqual(Pyro4.message.MSG_RESULT, msg.type)
        self.assertEqual(60006, msg.flags)
        self.assertEqual(5, msg.data_size)
        self.assertEqual(12345, msg.serializer_id)
        self.assertEqual(30003, msg.seq)

        msg = Message(255, b"", self.ser.serializer_id, 0, 255).to_bytes()
        self.assertEqual(50, len(msg))
        msg = Message(1, b"", self.ser.serializer_id, 0, 255).to_bytes()
        self.assertEqual(50, len(msg))
        msg = Message(1, b"", self.ser.serializer_id, flags=253,
                      seq=254).to_bytes()
        self.assertEqual(50, len(msg))

        # compression is a job of the code supplying the data, so the messagefactory should leave it untouched
        data = b"x" * 1000
        msg = Message(Pyro4.message.MSG_INVOKE, data, self.ser.serializer_id,
                      0, 0).to_bytes()
        msg2 = Message(Pyro4.message.MSG_INVOKE, data, self.ser.serializer_id,
                       Pyro4.message.FLAGS_COMPRESSED, 0).to_bytes()
        self.assertEqual(len(msg), len(msg2))
Example #2
0
    def testMessage(self):
        Message(99, b"", self.ser.serializer_id, 0, 0, hmac_key=b"secret")  # doesn't check msg type here
        self.assertRaises(Pyro4.errors.ProtocolError, Message.from_header, "FOOBAR")
        msg = Message(Pyro4.message.MSG_CONNECT, b"hello", self.ser.serializer_id, 0, 0, hmac_key=b"secret")
        self.assertEqual(Pyro4.message.MSG_CONNECT, msg.type)
        self.assertEqual(5, msg.data_size)
        self.assertEqual(b"hello", msg.data)
        self.assertEqual(4 + 2 + 20, msg.annotations_size)
        mac = pyrohmac(b"hello", b"secret", msg.annotations)
        self.assertDictEqual({"HMAC": mac}, msg.annotations)

        hdr = msg.to_bytes()[:24]
        msg = Message.from_header(hdr)
        self.assertEqual(Pyro4.message.MSG_CONNECT, msg.type)
        self.assertEqual(4 + 2 + 20, msg.annotations_size)
        self.assertEqual(5, msg.data_size)

        hdr = Message(Pyro4.message.MSG_RESULT, b"", self.ser.serializer_id, 0, 0, hmac_key=b"secret").to_bytes()[:24]
        msg = Message.from_header(hdr)
        self.assertEqual(Pyro4.message.MSG_RESULT, msg.type)
        self.assertEqual(4 + 2 + 20, msg.annotations_size)
        self.assertEqual(0, msg.data_size)

        hdr = Message(Pyro4.message.MSG_RESULT, b"hello", 12345, 60006, 30003, hmac_key=b"secret").to_bytes()[:24]
        msg = Message.from_header(hdr)
        self.assertEqual(Pyro4.message.MSG_RESULT, msg.type)
        self.assertEqual(60006, msg.flags)
        self.assertEqual(5, msg.data_size)
        self.assertEqual(12345, msg.serializer_id)
        self.assertEqual(30003, msg.seq)

        msg = Message(255, b"", self.ser.serializer_id, 0, 255, hmac_key=b"secret").to_bytes()
        self.assertEqual(50, len(msg))
        msg = Message(1, b"", self.ser.serializer_id, 0, 255, hmac_key=b"secret").to_bytes()
        self.assertEqual(50, len(msg))
        msg = Message(1, b"", self.ser.serializer_id, flags=253, seq=254, hmac_key=b"secret").to_bytes()
        self.assertEqual(50, len(msg))

        # compression is a job of the code supplying the data, so the messagefactory should leave it untouched
        data = b"x" * 1000
        msg = Message(Pyro4.message.MSG_INVOKE, data, self.ser.serializer_id, 0, 0, hmac_key=b"secret").to_bytes()
        msg2 = Message(
            Pyro4.message.MSG_INVOKE,
            data,
            self.ser.serializer_id,
            Pyro4.message.FLAGS_COMPRESSED,
            0,
            hmac_key=b"secret",
        ).to_bytes()
        self.assertEqual(len(msg), len(msg2))
Example #3
0
 def testMessageHeaderDatasize(self):
     msg = Message(Pyro4.message.MSG_RESULT, b"hello", 12345, 60006, 30003)
     msg.data_size = 0x12345678  # hack it to a large value to see if it comes back ok
     hdr = msg.to_bytes()[:24]
     msg = Message.from_header(hdr)
     self.assertEqual(Pyro4.message.MSG_RESULT, msg.type)
     self.assertEqual(60006, msg.flags)
     self.assertEqual(0x12345678, msg.data_size)
     self.assertEqual(12345, msg.serializer_id)
     self.assertEqual(30003, msg.seq)
Example #4
0
 def testMessageHeaderDatasize(self):
     msg = Message(Pyro4.message.MSG_RESULT, b"hello", 12345, 60006, 30003, hmac_key=b"secret")
     msg.data_size = 0x12345678  # hack it to a large value to see if it comes back ok
     hdr = msg.to_bytes()[:24]
     msg = Message.from_header(hdr)
     self.assertEqual(Pyro4.message.MSG_RESULT, msg.type)
     self.assertEqual(60006, msg.flags)
     self.assertEqual(0x12345678, msg.data_size)
     self.assertEqual(12345, msg.serializer_id)
     self.assertEqual(30003, msg.seq)
Example #5
0
    def dataReceived(self, data):
        """
        This function must aggregate and standardize the I/O behavior of several Pyro4 functions:

         - Daemon.handleRequest
         - Message.recv
         - Protocol._pyroInvoke

        Due to differences between synchronous and asynchronous approaches, twisted-pyro uses states to determine how
        to route received data.  Because state data need not persist across connections (unlike state information in
        many applications), it is attached to the Protocol.  These states are:

         - server:  indicates that a handshake will be required upon connection
         - header:  waiting on enough data to parse a message header and respond accordingly (idle state for servers)
         - annotations:  header parsed, waiting on amount of annotation data requested in header
         - data:  header parsed, waiting on amount of data requested in header
         - response:  the other end is waiting for data from us (idle state for clients)
        """
        log = logger.debug 
        log("Handling %d bytes of data" % len(data))
        self.data += data
        if self.state == "header" and len(self.data) >= Message.header_size:
            log("... enough data received to process header.")
            # Have enough data to process headers
            self.request = Message.from_header(self.data[:Message.header_size])
            if Pyro4.config.LOGWIRE:
                log("wiredata received: msgtype=%d flags=0x%x ser=%d seq=%d data=%r" %
                    (self.request.type, self.request.flags, self.request.serializer_id, self.request.seq, self.request.data))
            if self.required_message_types and self.request.type not in self.required_message_types:
                err = "invalid msg type %d received" % self.request.type
                logger.error(err)
                self._return_error(errors.ProtocolError(err))
            if self.request.serializer_id not in \
                    set([util.get_serializer(ser_name).serializer_id
                         for ser_name in Pyro4.config.SERIALIZERS_ACCEPTED]):
                self._return_error(errors.ProtocolError("message used serializer that is not accepted: %d" % self.request.serializer_id))
            self.data = self.data[Message.header_size:]
            if self.request.annotations_size:
                self.state = "annotations"
            else:
                self.state = "data"

        if self.state == "annotations" and len(self.data) >= self.request.annotations_size:
            log("... enough data received to process annotation.")
            self.request.annotations = {}
            annotations_data = self.data[:self.request.annotations_size]
            self.data = self.data[self.request.annotations_size:]
            i = 0
            while i < self.request.annotations_size:
                anno, length = struct.unpack("!4sH", annotations_data[i:i+6])
                self.request.annotations[anno] = annotations_data[i+6:i+6+length]
                i += 6+length
            if b"HMAC" in self.request.annotations and Pyro4.config.HMAC_KEY:
                if self.request.annotations[b"HMAC"] != self.request.hmac():
                    self._return_error(errors.SecurityError("message hmac mismatch"))
            elif (b"HMAC" in self.request.annotations) != bool(Pyro4.config.HMAC_KEY):
                # Message contains hmac and local HMAC_KEY not set, or vice versa. This is not allowed.
                err = "hmac key config not symmetric"
                logger.warning(err)
                self._return_error(errors.SecurityError(err))
            self.state = "data"

        if self.state == "data" and len(self.data) >= self.request.data_size:
            log("... enough data received to process data.")
            # A oneway call can be immediately followed by another call.  Otherwise, we should not receive any
            # additional data until we have sent a response
            if self.request.flags & Pyro4.message.FLAGS_ONEWAY:
                if self.request.type == message.MSG_PING:
                    error_msg = "ONEWAY ping doesn't make sense"
                    self._return_error(errors.ProtocolError(error_msg))
            else:
                if len(self.data) > self.request.data_size:
                    self.transport.loseConnection()
                    error_msg = "max message size exceeded (%d where max=%d)" % \
                                (self.request.data_size + self.request.annotations_size, Pyro4.config.MAX_MESSAGE_SIZE)
                    self._return_error(errors.ProtocolError(error_msg))

            # Transfer data to message
            self.request.data = self.data[:self.request.data_size]
            self.data = self.data[self.request.data_size:]

            # Execute message
            d = Deferred()

            if self.request.type == message.MSG_CONNECT:
                raise NotImplementedError("No action provided for MSG_CONNECT")

            elif self.request.type == message.MSG_CONNECTOK:
                # We only reach this spot if it is a valid message type so we're in client handshake mode.  Update
                # protocol to support client __call__ execution (i.e. invocation)
                self.required_message_types = [message.MSG_CONNECTOK]
                raise NotImplementedError("No action provided for MSG_CONNECTOK")

            elif self.request.type == message.MSG_CONNECTFAIL:
                raise NotImplementedError("No action provided for MSG_CONNECTFAIL")

            elif self.request.type == message.MSG_INVOKE:
                log("Responding to invoke request.")
                # Must be a static method so the Protocol can reset after oneway messages
                d.addCallback(self._pyro_remote_call)
                reactor.callLater(0, d.callback, self.request)

            elif self.request.type == message.MSG_PING:
                log("Responding to ping request.")
                reactor.callLater(0, d.callback, b"pong")

            elif self.request.type == message.MSG_RESULT:
                # Trigger callback with data or raise exception
                raise NotImplementedError("No action provided for MSG_RESULT")

            if self.request.flags & Pyro4.message.FLAGS_ONEWAY:
                log("... ONEWAY request, not building response.")

                def reraise(response):
                    if isinstance(response, Exception):
                        log.exception("ONEWAY call resulted in an exception: %s" % str(response))
                        return Failure(response)
                d.addCallback(reraise)

                # In Pyro4 core, the ONEWAY_THREADED option determines whether or not the execution order of ONEWAY
                # calls is guaranteed. To replicate this behavior, we reference the same flag.
                if Pyro4.config.ONEWAY_THREADED:
                    self.reset()
                else:
                    self.state = "blocked"

                    def process_next_message(response):
                        self.reset()
                        return response
                    d.addBoth(process_next_message)
            else:
                log("... appending response callbacks.")
                # If the previous call was not oneway, we maintain state on the protocol
                log("... setting state to 'response'")
                self.state = "response"
                log("... adding build/send callbacks")
                d.addCallback(self._build_response)
                d.addCallback(self._send_response)

        if self.state == "response" and len(self.data) > 0:
            error_msg = "data received while in response state" % \
                        (self.request.data_size + self.request.annotations_size, Pyro4.config.MAX_MESSAGE_SIZE)
            self._return_error(errors.ProtocolError(error_msg))

        if self.state == "blocked":
            # Waiting on a ONEWAY call (with guaranteed execution order) to complete.  When it completes, the system
            # will reset itself to accept the next call.
            pass