def testHmacMethod(self): data = Message(Pyro4.message.MSG_RESULT, b"test", 42, 0, 1, hmac_key=b"test key") digest = data.hmac() self.assertTrue(len(digest) > 10) data = Message(Pyro4.message.MSG_RESULT, b"test", 42, 0, 1) with self.assertRaises(TypeError): data.hmac()
def _sendExceptionResponse(self, connection, seq, serializer_id, exc_value, tbinfo): """send an exception back including the local traceback info""" exc_value._pyroTraceback = tbinfo if sys.platform == "cli": util.fixIronPythonExceptionForPickle(exc_value, True) # piggyback attributes serializer = util.get_serializer_by_id(serializer_id) try: data, compressed = serializer.serializeData(exc_value) except: # the exception object couldn't be serialized, use a generic PyroError instead xt, xv, tb = sys.exc_info() msg = "Error serializing exception: %s. Original exception: %s: %s" % ( str(xv), type(exc_value), str(exc_value)) exc_value = errors.PyroError(msg) exc_value._pyroTraceback = tbinfo if sys.platform == "cli": util.fixIronPythonExceptionForPickle( exc_value, True) # piggyback attributes data, compressed = serializer.serializeData(exc_value) flags = Pyro4.message.FLAGS_EXCEPTION if compressed: flags |= Pyro4.message.FLAGS_COMPRESSED if Pyro4.config.LOGWIRE: log.debug( "daemon wiredata sending (error response): msgtype=%d flags=0x%x ser=%d seq=%d data=%r" % (Pyro4.message.MSG_RESULT, flags, serializer.serializer_id, seq, data)) msg = Message(Pyro4.message.MSG_RESULT, data, serializer.serializer_id, flags, seq) connection.send(msg.to_bytes())
def testRecvNoAnnotations(self): msg = Message(Pyro4.message.MSG_CONNECT, b"hello", 42, 0, 0) c = ConnectionMock() c.send(msg.to_bytes()) msg = Message.recv(c) self.assertEqual(0, len(c.received)) self.assertEqual(5, msg.data_size) self.assertEqual(b"hello", msg.data) self.assertEqual(0, msg.annotations_size) self.assertEqual(0, len(msg.annotations))
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 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 testRecvAnnotations(self): annotations = {"TEST": b"abcde"} msg = Message(Pyro4.message.MSG_CONNECT, b"hello", self.ser.serializer_id, 0, 0, annotations, b"secret") c = ConnectionMock() c.send(msg.to_bytes()) msg = Message.recv(c, hmac_key=b"secret") self.assertEqual(0, len(c.received)) self.assertEqual(5, msg.data_size) self.assertEqual(b"hello", msg.data) self.assertEqual(b"abcde", msg.annotations["TEST"]) self.assertIn("HMAC", msg.annotations)
def testAnnotations(self): annotations = {"TEST": b"abcde"} msg = Message(Pyro4.message.MSG_CONNECT, b"hello", self.ser.serializer_id, 0, 0, annotations, b"secret") data = msg.to_bytes() annotations_size = 4 + 2 + 20 + 4 + 2 + 5 self.assertEqual(msg.header_size + 5 + annotations_size, len(data)) self.assertEqual(annotations_size, msg.annotations_size) self.assertEqual(2, len(msg.annotations)) self.assertEqual(b"abcde", msg.annotations["TEST"]) mac = pyrohmac(b"hello", b"secret", annotations) self.assertEqual(mac, msg.annotations["HMAC"])
def testRecvAnnotations(self): annotations = { b"TEST": b"abcde" } msg = Message(Pyro4.message.MSG_CONNECT, b"hello", self.ser.serializer_id, 0, 0, annotations) c = ConnectionMock() c.send(msg.to_bytes()) msg = Message.recv(c) self.assertEquals(0, len(c.received)) self.assertEquals(5, msg.data_size) self.assertEquals(b"hello", msg.data) self.assertEquals(b"abcde", msg.annotations[b"TEST"]) self.assertTrue(b"HMAC" in msg.annotations)
def testMaxDataSize(self): msg = Message(Pyro4.message.MSG_CONNECT, b"hello", 42, 0, 0) msg.data_size = 0x7fffffff # still within 32 bits signed limits msg.to_bytes() msg.data_size = 0x80000000 # overflow, Pyro has a 2 gigabyte message size limitation with self.assertRaises(ValueError) as ex: msg.to_bytes() self.assertEqual("invalid message size (outside range 0..2Gb)", str(ex.exception)) msg.data_size = -42 with self.assertRaises(ValueError) as ex: msg.to_bytes() self.assertEqual("invalid message size (outside range 0..2Gb)", str(ex.exception))
def testAnnotations(self): annotations = {"TEST": b"abcde"} msg = Message(Pyro4.message.MSG_CONNECT, b"hello", self.ser.serializer_id, 0, 0, annotations) data = msg.to_bytes() annotations_size = 4 + 2 + 20 + 4 + 2 + 5 self.assertEqual(msg.header_size + 5 + annotations_size, len(data)) self.assertEqual(annotations_size, msg.annotations_size) self.assertEqual(2, len(msg.annotations)) self.assertEqual(b"abcde", msg.annotations["TEST"]) mac = pyrohmac(b"hello", annotations) self.assertEqual(mac, msg.annotations["HMAC"])
def _handshake(self, conn): """Perform connection handshake with new clients""" # For now, client is not sending anything. Just respond with a CONNECT_OK. # We need a minimal amount of data or the socket will remain blocked # on some systems... (messages smaller than 40 bytes) # Return True for successful handshake, False if something was wrong. # We default to the marshal serializer to send message payload of "ok" ser = util.get_serializer("marshal") data = ser.dumps("ok") msg = Message(Pyro4.message.MSG_CONNECTOK, data, ser.serializer_id, 0, 1) conn.send(msg.to_bytes()) return True
def testRecvAnnotations(self): annotations = {"TEST": b"abcde"} msg = Message(Pyro4.message.MSG_CONNECT, b"hello", self.ser.serializer_id, 0, 0, annotations) c = ConnectionMock() c.send(msg.to_bytes()) msg = Message.recv(c) self.assertEqual(0, len(c.received)) self.assertEqual(5, msg.data_size) self.assertEqual(b"hello", msg.data) self.assertEqual(b"abcde", msg.annotations["TEST"]) self.assertTrue("HMAC" in msg.annotations)
def testCompression(self): data = b"The quick brown fox jumps over the lazy dog."*10 compressed_data = zlib.compress(data) flags = Pyro4.message.FLAGS_COMPRESSED msg = Message(Pyro4.message.MSG_INVOKE, compressed_data, 42, flags, 1, hmac_key=b"secret") self.assertNotEqual(data, msg.data) data_size = msg.data_size self.assertLess(data_size, len(data)) msg.decompress_if_needed() self.assertEqual(data, msg.data) self.assertEqual(0, msg.flags) self.assertGreater(msg.data_size, data_size)
def testChecksum(self): msg = Message(Pyro4.message.MSG_RESULT, b"test", 42, 0, 1, hmac_key=b"secret") c = ConnectionMock() c.send(msg.to_bytes()) # corrupt the checksum bytes data = c.received data = data[:msg.header_size - 2] + b'\x00\x00' + data[msg.header_size:] c = ConnectionMock(data) try: Message.recv(c) self.fail("crash expected") except Pyro4.errors.ProtocolError as x: self.assertIn("checksum", str(x))
def connectionMade(self): """ Handshake - should replicate Pyro4.Daemon._handshake() """ log = logger.debug if self.state == "server": log("Connection made with Pyro4Protocol") log("... attempting handshake") ser = util.get_serializer("marshal") data = ser.dumps("ok") msg = Message(Pyro4.message.MSG_CONNECTOK, data, ser.serializer_id, 0, 1) self.transport.write(msg.to_bytes()) self.state = "header"
def testAnnotationsIdLength4(self): try: msg = Message(Pyro4.message.MSG_CONNECT, b"hello", self.ser.serializer_id, 0, 0, {"TOOLONG": b"abcde"}, b"secret") _ = msg.to_bytes() self.fail("should fail, too long") except Pyro4.errors.ProtocolError: pass try: msg = Message(Pyro4.message.MSG_CONNECT, b"hello", self.ser.serializer_id, 0, 0, {"QQ": b"abcde"}, b"secret") _ = msg.to_bytes() self.fail("should fail, too short") except Pyro4.errors.ProtocolError: pass
def testChecksum(self): msg = Message(Pyro4.message.MSG_RESULT, b"test", 42, 0, 1) c = ConnectionMock() c.send(msg.to_bytes()) # corrupt the checksum bytes data = c.received data = data[:msg.header_size - 2] + b'\x00\x00' + data[msg.header_size:] c = ConnectionMock(data) try: Message.recv(c) self.fail("crash expected") except Pyro4.errors.ProtocolError as x: self.assertTrue("checksum" in str(x))
def _pyroInvoke(self, methodname, vargs, kwargs, flags=0): """perform the remote method call communication""" if self._pyroConnection is None: # rebind here, don't do it from inside the invoke because deadlock will occur self.__pyroCreateConnection() serializer = util.get_serializer(Pyro4.config.SERIALIZER) data, compressed = serializer.serializeCall( self._pyroConnection.objectId, methodname, vargs, kwargs, compress=Pyro4.config.COMPRESSION) if compressed: flags |= Pyro4.message.FLAGS_COMPRESSED if methodname in self._pyroOneway: flags |= Pyro4.message.FLAGS_ONEWAY with self.__pyroLock: self._pyroSeq=(self._pyroSeq+1)&0xffff if Pyro4.config.LOGWIRE: log.debug("proxy wiredata sending: msgtype=%d flags=0x%x ser=%d seq=%d data=%r" % (Pyro4.message.MSG_INVOKE, flags, serializer.serializer_id, self._pyroSeq, data)) msg = Message(Pyro4.message.MSG_INVOKE, data, serializer.serializer_id, flags, self._pyroSeq) try: self._pyroConnection.send(msg.to_bytes()) del msg # invite GC to collect the object, don't wait for out-of-scope if flags & Pyro4.message.FLAGS_ONEWAY: return None # oneway call, no response data else: msg = Message.recv(self._pyroConnection, [Pyro4.message.MSG_RESULT]) if Pyro4.config.LOGWIRE: log.debug("proxy wiredata received: msgtype=%d flags=0x%x ser=%d seq=%d data=%r" % (msg.type, msg.flags, msg.serializer_id, msg.seq, msg.data)) self.__pyroCheckSequence(msg.seq) if msg.serializer_id != serializer.serializer_id: error = "invalid serializer in response: %d" % msg.serializer_id log.error(error) raise errors.ProtocolError(error) data = serializer.deserializeData(msg.data, compressed=msg.flags & Pyro4.message.FLAGS_COMPRESSED) if msg.flags & Pyro4.message.FLAGS_EXCEPTION: if sys.platform=="cli": util.fixIronPythonExceptionForPickle(data, False) raise data else: return data except (errors.CommunicationError, KeyboardInterrupt): # Communication error during read. To avoid corrupt transfers, we close the connection. # Otherwise we might receive the previous reply as a result of a new methodcall! # Special case for keyboardinterrupt: people pressing ^C to abort the client # may be catching the keyboardinterrupt in their code. We should probably be on the # safe side and release the proxy connection in this case too, because they might # be reusing the proxy object after catching the exception... self._pyroRelease() raise
def testProtocolVersion(self): version = Pyro4.constants.PROTOCOL_VERSION Pyro4.constants.PROTOCOL_VERSION = 0 # fake invalid protocol version number msg = Message(Pyro4.message.MSG_RESULT, b"", self.ser.serializer_id, 0, 1).to_bytes() Pyro4.constants.PROTOCOL_VERSION = version self.assertRaises(Pyro4.errors.ProtocolError, Message.from_header, msg)
def testCompression(self): data = b"The quick brown fox jumps over the lazy dog." * 10 compressed_data = zlib.compress(data) flags = Pyro4.message.FLAGS_COMPRESSED msg = Message(Pyro4.message.MSG_INVOKE, compressed_data, 42, flags, 1, hmac_key=b"secret") self.assertNotEqual(data, msg.data) data_size = msg.data_size self.assertLess(data_size, len(data)) msg.decompress_if_needed() self.assertEqual(data, msg.data) self.assertEqual(0, msg.flags) self.assertGreater(msg.data_size, data_size)
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 testHmac(self): try: hk = Pyro4.config.HMAC_KEY Pyro4.config.HMAC_KEY = b"test key" data = Message(Pyro4.message.MSG_RESULT, b"test", 42, 0, 1).to_bytes() c = ConnectionMock(data) finally: Pyro4.config.HMAC_KEY = hk # test checking of different hmacs try: Message.recv(c) self.fail("crash expected") except Pyro4.errors.SecurityError as x: self.assertTrue("hmac" in str(x)) c = ConnectionMock(data) # test that it works again when resetting the key try: hk = Pyro4.config.HMAC_KEY Pyro4.config.HMAC_KEY = b"test key" Message.recv(c) finally: Pyro4.config.HMAC_KEY = hk c = ConnectionMock(data) # test that it doesn't work when no key is set try: hk = Pyro4.config.HMAC_KEY Pyro4.config.HMAC_KEY = b"" Message.recv(c) self.fail("crash expected") except Pyro4.errors.SecurityError as x: self.assertTrue("hmac key config" in str(x)) finally: Pyro4.config.HMAC_KEY = hk
def __pyroCreateConnection(self, replaceUri=False): """ Connects this proxy to the remote Pyro daemon. Does connection handshake. Returns true if a new connection was made, false if an existing one was already present. """ with self.__pyroConnLock: if self._pyroConnection is not None: return False # already connected from Pyro4.naming import resolve # don't import this globally because of cyclic dependancy uri=resolve(self._pyroUri) # socket connection (normal or Unix domain socket) conn=None log.debug("connecting to %s", uri) connect_location=uri.sockname if uri.sockname else (uri.host, uri.port) with self.__pyroLock: try: if self._pyroConnection is not None: return False # already connected sock=socketutil.createSocket(connect=connect_location, reuseaddr=Pyro4.config.SOCK_REUSE, timeout=self.__pyroTimeout) conn=socketutil.SocketConnection(sock, uri.object) # Do handshake. For now, no need to send anything. (message type CONNECT is not yet used) msg = Message.recv(conn, None) # any trailing data (dataLen>0) is an error message, if any except Exception: x=sys.exc_info()[1] if conn: conn.close() err="cannot connect: %s" % x log.error(err) if isinstance(x, errors.CommunicationError): raise else: ce = errors.CommunicationError(err) ce.__cause__ = x raise ce else: if msg.type==Pyro4.message.MSG_CONNECTFAIL: error="connection rejected" if msg.data: serializer = util.get_serializer_by_id(msg.serializer_id) data = serializer.deserializeData(msg.data, compressed=msg.flags & Pyro4.message.FLAGS_COMPRESSED) error += ", reason: " + data conn.close() log.error(error) raise errors.CommunicationError(error) elif msg.type==Pyro4.message.MSG_CONNECTOK: self._pyroConnection=conn if replaceUri: self._pyroUri=uri log.debug("connected to %s", self._pyroUri) return True else: conn.close() err="connect: invalid msg type %d received" % msg.type log.error(err) raise errors.ProtocolError(err)
def testHmac(self): data = Message(Pyro4.message.MSG_RESULT, b"test", 42, 0, 1, hmac_key=b"test key").to_bytes() c = ConnectionMock(data) # test checking of different hmacs try: Message.recv(c, hmac_key=None) self.fail("crash expected") except Pyro4.errors.SecurityError as x: self.assertIn("hmac key config", str(x)) c = ConnectionMock(data) try: Message.recv(c, hmac_key=b"T3ST-K3Y") self.fail("crash expected") except Pyro4.errors.SecurityError as x: self.assertIn("hmac", str(x)) # test that it works again when providing the correct key c = ConnectionMock(data) msg = Message.recv(c, hmac_key=b"test key") self.assertEqual(b"test key", msg.hmac_key)
def testHmac(self): try: hk = Pyro4.config.HMAC_KEY Pyro4.config.HMAC_KEY = b"test key" data = Message(Pyro4.message.MSG_RESULT, b"test", 42, 0, 1).to_bytes() c = ConnectionMock(data) finally: Pyro4.config.HMAC_KEY = hk # test checking of different hmacs try: Message.recv(c) self.fail("crash expected") except Pyro4.errors.SecurityError as x: self.assertIn("hmac", str(x)) c = ConnectionMock(data) # test that it works again when resetting the key try: hk = Pyro4.config.HMAC_KEY Pyro4.config.HMAC_KEY = b"test key" Message.recv(c) finally: Pyro4.config.HMAC_KEY = hk c = ConnectionMock(data) # test that it doesn't work when no key is set try: hk = Pyro4.config.HMAC_KEY Pyro4.config.HMAC_KEY = b"" Message.recv(c) self.fail("crash expected") except Pyro4.errors.SecurityError as x: self.assertIn("hmac key config", str(x)) finally: Pyro4.config.HMAC_KEY = hk
def testAnnotationsIdLength4(self): try: msg = Message(Pyro4.message.MSG_CONNECT, b"hello", self.ser.serializer_id, 0, 0, {"TOOLONG": b"abcde"}) data = msg.to_bytes() self.fail("should fail, too long") except Pyro4.errors.ProtocolError: pass try: msg = Message(Pyro4.message.MSG_CONNECT, b"hello", self.ser.serializer_id, 0, 0, {"QQ": b"abcde"}) data = msg.to_bytes() self.fail("should fail, too short") except Pyro4.errors.ProtocolError: pass
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 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
def handleRequest(self, conn): """ Handle incoming Pyro request. Catches any exception that may occur and wraps it in a reply to the calling side, as to not make this server side loop terminate due to exceptions caused by remote invocations. """ request_flags = 0 request_seq = 0 request_serializer_id = util.MarshalSerializer.serializer_id wasBatched = False isCallback = False try: msg = Message.recv( conn, [Pyro4.message.MSG_INVOKE, Pyro4.message.MSG_PING]) request_flags = msg.flags request_seq = msg.seq request_serializer_id = msg.serializer_id if Pyro4.config.LOGWIRE: log.debug( "daemon wiredata received: msgtype=%d flags=0x%x ser=%d seq=%d data=%r" % (msg.type, msg.flags, msg.serializer_id, msg.seq, msg.data)) if msg.type == Pyro4.message.MSG_PING: # return same seq, but ignore any data (it's a ping, not an echo). Nothing is deserialized. msg = Message(Pyro4.message.MSG_PING, b"pong", msg.serializer_id, 0, msg.seq) if Pyro4.config.LOGWIRE: log.debug( "daemon wiredata sending: msgtype=%d flags=0x%x ser=%d seq=%d data=%r" % (msg.type, msg.flags, msg.serializer_id, msg.seq, msg.data)) conn.send(msg.to_bytes()) return if msg.serializer_id not in self.__serializer_ids: raise errors.ProtocolError( "message used serializer that is not accepted: %d" % msg.serializer_id) serializer = util.get_serializer_by_id(msg.serializer_id) objId, method, vargs, kwargs = serializer.deserializeCall( msg.data, compressed=msg.flags & Pyro4.message.FLAGS_COMPRESSED) del msg # invite GC to collect the object, don't wait for out-of-scope obj = self.objectsById.get(objId) if obj is not None: if kwargs and sys.version_info < (2, 6, 5) and os.name != "java": # Python before 2.6.5 doesn't accept unicode keyword arguments kwargs = dict((str(k), kwargs[k]) for k in kwargs) if request_flags & Pyro4.message.FLAGS_BATCH: # batched method calls, loop over them all and collect all results data = [] for method, vargs, kwargs in vargs: method = util.resolveDottedAttribute( obj, method, Pyro4.config.DOTTEDNAMES) try: result = method( *vargs, **kwargs ) # this is the actual method call to the Pyro object except Exception: xt, xv = sys.exc_info()[0:2] log.debug( "Exception occurred while handling batched request: %s", xv) xv._pyroTraceback = util.formatTraceback( detailed=Pyro4.config.DETAILED_TRACEBACK) if sys.platform == "cli": util.fixIronPythonExceptionForPickle( xv, True) # piggyback attributes data.append(futures._ExceptionWrapper(xv)) break # stop processing the rest of the batch else: data.append(result) wasBatched = True else: # normal single method call method = util.resolveDottedAttribute( obj, method, Pyro4.config.DOTTEDNAMES) if request_flags & Pyro4.message.FLAGS_ONEWAY and Pyro4.config.ONEWAY_THREADED: # oneway call to be run inside its own thread thread = threadutil.Thread(target=method, args=vargs, kwargs=kwargs) thread.setDaemon(True) thread.start() else: isCallback = getattr(method, "_pyroCallback", False) data = method( *vargs, **kwargs ) # this is the actual method call to the Pyro object else: log.debug("unknown object requested: %s", objId) raise errors.DaemonError("unknown object") if request_flags & Pyro4.message.FLAGS_ONEWAY: return # oneway call, don't send a response else: data, compressed = serializer.serializeData( data, compress=Pyro4.config.COMPRESSION) response_flags = 0 if compressed: response_flags |= Pyro4.message.FLAGS_COMPRESSED if wasBatched: response_flags |= Pyro4.message.FLAGS_BATCH if Pyro4.config.LOGWIRE: log.debug( "daemon wiredata sending: msgtype=%d flags=0x%x ser=%d seq=%d data=%r" % (Pyro4.message.MSG_RESULT, response_flags, serializer.serializer_id, request_seq, data)) msg = Message(Pyro4.message.MSG_RESULT, data, serializer.serializer_id, response_flags, request_seq) conn.send(msg.to_bytes()) except Exception: xt, xv = sys.exc_info()[0:2] if xt is not errors.ConnectionClosedError: log.debug("Exception occurred while handling request: %r", xv) if not request_flags & Pyro4.message.FLAGS_ONEWAY: # only return the error to the client if it wasn't a oneway call tblines = util.formatTraceback( detailed=Pyro4.config.DETAILED_TRACEBACK) self._sendExceptionResponse(conn, request_seq, request_serializer_id, xv, tblines) if isCallback or isinstance( xv, (errors.CommunicationError, errors.SecurityError)): raise # re-raise if flagged as callback, communication or security error.