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))
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))
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)
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)
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