Exemplo n.º 1
0
 def __init__(self):
     """
     Initializes a user database for holding QChat contact information
     """
     self.lock = threading.Lock()
     self.logger = QChatLogger(__name__)
     self.db = defaultdict(dict)
Exemplo n.º 2
0
    def __init__(self, name, cqc_connection, config):
        """
        Initialize a connection to the CQC server and sets up a socket for classical communications.
        :param name: str
            Name of the host (Must be one available by SimulaQron CQC).
        :param cqc_connection: `cqc.pythonLib.CQCConnection`
            The Classical Quantum Combiner Connection over which quantum communication occurs.
        :param config: dict
            JSON configuration for the connection.
        """
        # Lock on the connection
        self.lock = threading.Lock()

        # Logger
        self.logger = QChatLogger(__name__)

        # Host name the connection belongs to
        self.name = name

        # Listening configuration
        self.host = config['host']
        self.port = config['port']

        # Inbound message queue
        self.message_queue = []

        # Daemon threads
        self.cqc = cqc_connection
        self.listening_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.listening_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.classical_thread = DaemonThread(target=self.listen_for_classical)
Exemplo n.º 3
0
    def __init__(self,
                 name,
                 cqc_connection,
                 configFile=None,
                 allow_invalid_signatures=False):
        """
        Initializes a QChat Server that serves as the primary communication interface with other applications
        :param name: str
            Name of the host we want to be on the network
        :param cqc_connection: `~cqc.pythonLib.CQCConnection`
            Classical Quantum Combiner Connection used for quantum communications
        :param configFile: str
            Path to the configuration file that contains settings for the specified name
        :param allow_invalid_signatures: bool
            Process messages with faulty signatures
        """
        self.name = name
        self.logger = QChatLogger(__name__)
        self.configFile = configFile

        # This is the server's personal config
        self.config = self._load_server_config(self.name)

        # This is information for the root registry server
        self.root_config = self._load_server_config(self.config.get("root"))

        # RSA Signer for handling unauthenticated classical channels
        self.signer = QChatSigner()

        self._allow_invalid_signatures = allow_invalid_signatures

        # Connection to other applications
        self.connection = QChatConnection(name=name,
                                          cqc_connection=cqc_connection,
                                          config=self.config)

        # Storage of user/network information
        self.userDB = UserDB()

        # Load ourselves into our DB
        self.userDB.addUser(user=self.name,
                            pub=self.signer.get_pub(),
                            **self.connection.get_connection_info())

        # Start our inbound/outbound message handlers
        self.message_processor = DaemonThread(target=self.read_from_connection)

        # Register with the root registry
        self._register_with_root_server()

        # Storage of distributed qubit information
        self.qubit_history = defaultdict(list)

        # Inbound control messages for protocols
        self.control_message_queue = defaultdict(list)
Exemplo n.º 4
0
 def __init__(self, user, destination, server_url):
     """
     Initializes the RPC client
     :param user: str
         The user we wish to send a message as
     :param destination: str
         The peer we wish to send a message to
     :param server_url: str
         The RPCServer url to connect to, eg. http://127.0.0.1:6666
     """
     self.client = xmlrpc.client.ServerProxy(server_url)
     self.user = user
     self.destination = destination
     self._running = False
     self._message_reader = threading.Thread(target=self._read_messages)
     self.lock = threading.Lock()
     self.logger = QChatLogger("QChatCLIRPCClient-{}".format(user))
Exemplo n.º 5
0
 def __init__(self, user, host, port, client):
     """
     Initializes the RPC server
     :param user: str
         The name of the QChatServer
     :param host: str
         The host to receive RPC commands at
     :param port: int
         The port to receive RPC commands at
     :param client: `~qchat.client.QChatClient`
         The QChatClient to interact with
     """
     self.user = user
     self.host = host
     self.port = port
     self.clients[user] = client
     self._ensure_client_for(user)
     self.logger = QChatLogger("QChatClientRPCServer-{}".format(user))
     self.logger.debug("Starting server for {} at {}:{}".format(
         user, host, port))
Exemplo n.º 6
0
    def __init__(self, connection, relay_info):
        """
        Used to implement trusted/untrusted measurement devices for use in receiving BB84 states from source
        and performing measurements on the qubits
        :param connection: `~qchat.connection.QChatConnection`
            Connection to be used for classical and quantum communications
        :param relay_info: dict
            Relay information about how messages travel over other nodes
        """
        self.connection = connection
        self.logger = QChatLogger(__name__)

        # Connection information to the server providing the EPR pairs
        self.relay_host = relay_info["host"]
        self.relay_port = relay_info["port"]
Exemplo n.º 7
0
class QChatMailbox:
    """
    Implements a thread safe message storing mailbox
    """
    def __init__(self):
        self.lock = threading.Lock()
        self.messages = []
        self.logger = QChatLogger(__name__)

    def storeMessage(self, message):
        """
        Stores a message into the mailbox
        :param message: obj
            The message to store
        :return: None
        """
        self.logger.debug("New message in mailbox from {}".format(
            message.sender))
        with self.lock:
            self.messages.append(message)

    def getMessages(self):
        """
        Returns a list of the messages that are currently stored in the mailbox
        :return: list
            A list of the messages that are currently stored
        """
        self.logger.debug("Retrieving messages")
        with self.lock:
            return list(self.messages)

    def popMessages(self):
        """
        Pops all messages that are stored and returns them
        :return: list
            List of the stored messages that were removed
        """
        self.logger.debug("Popping messages")
        with self.lock:
            messages = list(self.messages)
            self.messages = []
            return messages
Exemplo n.º 8
0
class QChatConnection:
    def __init__(self, name, cqc_connection, config):
        """
        Initialize a connection to the CQC server and sets up a socket for classical communications.
        :param name: str
            Name of the host (Must be one available by SimulaQron CQC).
        :param cqc_connection: `cqc.pythonLib.CQCConnection`
            The Classical Quantum Combiner Connection over which quantum communication occurs.
        :param config: dict
            JSON configuration for the connection.
        """
        # Lock on the connection
        self.lock = threading.Lock()

        # Logger
        self.logger = QChatLogger(__name__)

        # Host name the connection belongs to
        self.name = name

        # Listening configuration
        self.host = config['host']
        self.port = config['port']

        # Inbound message queue
        self.message_queue = []

        # Daemon threads
        self.cqc = cqc_connection
        self.listening_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.listening_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.classical_thread = DaemonThread(target=self.listen_for_classical)

    def __del__(self):
        """
        Makes sure to close the socket used for classical communications.
        :return: None
        """
        if self.listening_socket:
            self.listening_socket.close()

    def get_connection_info(self):
        """
        Returns a dictionary containing host/port information for the socket used in classical communication.
        :return: dict
            Dictionary containing info.
        """
        info = {
            "connection": {
                "host": self.host,
                "port": self.port
            }
        }
        return info

    def listen_for_classical(self):
        """
        A daemon for handling incoming connections.
        :return: None
        """
        self.listening_socket.bind((self.host, self.port))
        while True:
            self.logger.debug("Listening for incoming connection")
            self.listening_socket.listen(1)
            conn, addr = self.listening_socket.accept()
            self.logger.debug("Got connection from {}".format(addr))
            self.start_handler(conn, addr)

    def start_handler(self, conn, addr):
        """
        Simple connection handler that passes work to thread
        :param conn: Connection information from socket
        :param addr: Address information from socket
        :return: None
        """
        t = threading.Thread(target=self._handle_connection, args=(conn, addr))
        t.start()

    def _handle_connection(self, conn, addr):
        """
        Receives incoming QChat Messages and verifies their structure before storing them
        so that they can be retrieved.
        :param conn: Connection information from sockets
        :param addr: Address information from sockets
        :return: None
        """
        # Verify the header structure
        header = conn.recv(HEADER_LENGTH)
        if header not in MessageFactory().message_mapping.keys():
            raise ConnectionError("Incorrect message header")

        # Verify the sender structe
        padded_sender = conn.recv(MAX_SENDER_LENGTH)
        if len(padded_sender) != MAX_SENDER_LENGTH:
            raise ConnectionError("Incorrect sender length")

        # Verify the sender info
        sender = str(padded_sender.replace(b'\x00', b''), 'utf-8')
        if len(sender) == 0:
            raise ConnectionError("Invalid sender")

        # Get the message size
        size = conn.recv(PAYLOAD_SIZE)
        if len(size) != PAYLOAD_SIZE:
            raise ConnectionError("Incorrect payload size")

        # Retrieve the message data
        data_length = int.from_bytes(size, 'big')
        message_data = b''
        while len(message_data) < data_length:
            data = conn.recv(1024)
            if not data:
                raise ConnectionError("Message data too short")
            message_data += data

        # Verify the length of the sent data
        if len(message_data) > data_length or conn.recv(1):
            raise ConnectionError("Message data too long")

        # Pass the message up
        self.logger.debug("Inserting message into queue")
        m = MessageFactory().create_message(header, sender, message_data)
        self._append_message_to_queue(m)
        conn.close()

    def _append_message_to_queue(self, message):
        """
        Appends message to the inbound message queue.
        :param message: bytes
            The message to be added to the queue.
        :return: None
        """
        with self.lock:
            self.message_queue.append(message)

    def _pop_message_from_queue(self):
        """
        Removes the oldest message from the head of the queue.
        :return: bytes
            The oldest message stored in the queue.
        """
        with self.lock:
            return self.message_queue.pop(0)

    def recv_message(self):
        """
        Method that receives a message if one exists in the queue.
        :return: bytes
            The message if one exists.  Otherwise None.
        """
        return self._pop_message_from_queue() if self.message_queue else None

    def send_message(self, host, port, message):
        """
        Connects and sends a message to the specified host:port
        :param host: str
            Hostname to send to
        :param port: int
            Port to send to
        :param message: bytes
            Bytes object message
        :return: None
        """
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((host, port))
        s.sendall(message)
        s.close()
        self.logger.debug("Sent message to {}:{}".format(host, port))
Exemplo n.º 9
0
class QChatCore:
    def __init__(self,
                 name,
                 cqc_connection,
                 configFile=None,
                 allow_invalid_signatures=False):
        """
        Initializes a QChat Server that serves as the primary communication interface with other applications
        :param name: str
            Name of the host we want to be on the network
        :param cqc_connection: `~cqc.pythonLib.CQCConnection`
            Classical Quantum Combiner Connection used for quantum communications
        :param configFile: str
            Path to the configuration file that contains settings for the specified name
        :param allow_invalid_signatures: bool
            Process messages with faulty signatures
        """
        self.name = name
        self.logger = QChatLogger(__name__)
        self.configFile = configFile

        # This is the server's personal config
        self.config = self._load_server_config(self.name)

        # This is information for the root registry server
        self.root_config = self._load_server_config(self.config.get("root"))

        # RSA Signer for handling unauthenticated classical channels
        self.signer = QChatSigner()

        self._allow_invalid_signatures = allow_invalid_signatures

        # Connection to other applications
        self.connection = QChatConnection(name=name,
                                          cqc_connection=cqc_connection,
                                          config=self.config)

        # Storage of user/network information
        self.userDB = UserDB()

        # Load ourselves into our DB
        self.userDB.addUser(user=self.name,
                            pub=self.signer.get_pub(),
                            **self.connection.get_connection_info())

        # Start our inbound/outbound message handlers
        self.message_processor = DaemonThread(target=self.read_from_connection)

        # Register with the root registry
        self._register_with_root_server()

        # Storage of distributed qubit information
        self.qubit_history = defaultdict(list)

        # Inbound control messages for protocols
        self.control_message_queue = defaultdict(list)

    def _load_server_config(self, name):
        """
        Obtains the hosts server configuration from the config file
        :param name: str
            Key in configuration where settings should be loaded from
        :return: dict
            The configuration loaded from the file
        """
        if self.configFile:
            config_path = self.configFile

        else:
            path = os.path.abspath(__file__)
            config_path = os.path.dirname(path) + "/config.json"

        self.logger.debug("Loading server config for {}".format(name))

        with open(config_path) as f:
            base_config = json.load(f)
            config = base_config.get(name)
            self.logger.debug("Config: {}".format(config))

        return config

    def _register_with_root_server(self):
        """
        Registers our application server with the root registry server
        :return: None
        """
        try:
            root_host = self.root_config["host"]
            root_port = self.root_config["port"]

            # No need to register with ourselves if we are the root registry
            if self.config["host"] == root_host and self.config[
                    "port"] == root_port:
                self.logger.debug("Am root server")
            else:
                self.sendRegistration(host=root_host, port=root_port)

        except Exception:
            self.logger.info(
                "Failed to register with root server, is it running?")

    def read_from_connection(self):
        """
        Processes inbound messages from the application connection
        :return: None
        """
        while not time.sleep(GLOBAL_SLEEP_TIME):
            message = self.connection.recv_message()
            if message:
                self.start_process_thread(message)

    def start_process_thread(self, message):
        """
        Forks off a thread for handling messages so that they can be processed in parallel
        :param message: `~qchat.messages.Message`
            The message we obtained from the application connection
        :return: None
        """
        t = threading.Thread(target=self.process_message, args=(message, ))
        t.start()

    def process_message(self, message):
        """
        The primary message handling entrypoint, performs signature verification/stripping before passing the
        message to a specific handler
        :param message: `~qchat.messages.Message`
            The inbound message from the application connection
        :return: None
        """
        self.logger.debug("Processing {} message from {}: {}".format(
            message.header, message.sender, message.data))

        # Verify the signature on the message for key message types
        if message.verify:
            if not self.userDB.hasUser(message.sender):
                self.requestUserInfo(message.sender)

            message, signature = self._strip_signature(message)
            if not self._allow_invalid_signatures:
                self._verify_message(message, signature)
            else:
                self.logger.warning("Will not verify message signature")

        # Strip unnecessary signature information should it not be necessary for the message type
        elif message.strip:
            message, _ = self._strip_signature(message)

        handler = self.proc_map.get(message.header,
                                    self._store_control_message)
        handler(message)

        self.logger.debug("Completed processing message")

    def _sign_message(self, message):
        """
        Internal method for signing outbound messages to assure authentication
        :param message: `~qchat.messages.Message`
            The message to be signed
        :return: `~qchat.messages.Message`
            The message with a signature attached
        """
        sig = self.signer.sign(message.encode_message())
        message.data["sig"] = sig.decode("ISO-8859-1")
        return message

    def _strip_signature(self, message):
        """
        Internal method for stripping signature data from a message that is unecessary to message handlers
        :param message: `~qchat.messages.Message`
            The message we want to strip
        :return: tuple
            A tuple of the message without the signature data, signature
        """
        signature = message.data.pop("sig").encode("ISO-8859-1")
        return message, signature

    def _verify_message(self, message, signature):
        """
        Internal method for verifying the signature provided with a message
        :param message: `~qchat.messages.Message`
            The message we want to verify
        :param signature: bytes
            The signature we want to verify
        :return: None
        """
        data = message.encode_message()

        # Use the stored public key for verification
        pub = self.userDB.getPublicKey(message.sender)

        if not QChatVerifier(pub).verify(data, signature):
            raise Exception("Obtained message with incorrect signature")

        self.logger.debug("Successfully verified signature")

    def _pass_message_data(self, message, handler):
        """
        Internal method for passing the message data as arguments to the message handlers
        :param message: `~qchat.messages.Message`
            The message to unpack arguments from
        :param handler: func
            The handler that will process the message
        :return: None
        """
        handler(**message.data)

    def _store_control_message(self, message):
        """
        Internal method for handling messages that do not have specific handlers
        :param message: `~qchat.messages.Message`
            The message to store
        :return: None
        """
        self.control_message_queue[message.sender].append(message)
        self.logger.debug("Stored message into control queue")

    def _get_registration_data(self):
        """
        Internal method for constructing this server's registration data
        :return: dict
            The constructed registration data
        """
        reg_data = {
            "user": self.name,
            "pub": self.getPublicKey().decode("ISO-8859-1")
        }
        reg_data.update(self.connection.get_connection_info())

        self.logger.debug(
            "Constructing registration data: {}".format(reg_data))

        return reg_data

    def hasUser(self, user):
        """
        Interface to the user database for checking if a user exists
        :param user: str
            The name of the user to check in the database
        :return: bool
            Whether user exists or not
        """
        return self.userDB.hasUser(user)

    def addUserInfo(self, user, **kwargs):
        """
        Adds arbitrary information to the user database for a user
        :param user: str
            The user we want to add to the database
        :param kwargs: dict
            The key=value pairs we want to store in the database
        :return: None
        """
        if user == "*":
            self.logger.debug("Get bulk user info!")
            for info in kwargs["info"]:
                user_name = info.pop("user")
                if not self.userDB.hasUser(user_name):
                    self.logger.debug("Adding to user {} info {}".format(
                        user_name, info))
                    self.userDB.addUser(user_name, **info)

        else:
            self.logger.debug("Adding to user {} info {}".format(user, kwargs))
            self.userDB.addUser(user, **kwargs)

    def getPublicInfo(self, user):
        """
        Returns the relevant public information for the application that is necessary for establishing
        RSA authenticated classical communication
        :param user: str
            The user we want the public information for
        :return: dict
            A dictionary containing the user's name, host/port info, and public key
        """
        pub_info = dict(self.userDB.getPublicUserInfo(user))
        return pub_info

    def getPublicKey(self):
        """
        Returns the server's public key
        :return: bytes
            Public key of the server
        """
        return self.userDB.getPublicKey(user=self.name)

    def getConnectionInfo(self, user):
        """
        Returns the connection information for the specified user
        :param user: str
            User we want connection information for
        :return: dict
            A dictionary containing the host/port information of the user
        """
        return self.userDB.getConnectionInfo(user)

    def sendRegistration(self, host, port):
        """
        Sends this server's registration to the specified host/port
        :param host: str
            Host of the registry
        :param port: int
            Port of the registry
        :return: None
        """
        message = RGSTMessage(sender=self.name,
                              message_data=self._get_registration_data())
        self.connection.send_message(host, port, message.encode_message())
        self.logger.debug("Sent registration to {}:{}".format(host, port))

    def requestUserInfo(self, user):
        """
        Requests the specified user's information from the root registry in the network
        :param user: str
            User we want to obtain information for
        :return: None
        """
        # Construct the request message
        request_message_data = {
            "user": user,
        }
        request_message_data.update(self.connection.get_connection_info())

        # Create the messag eobject and sign it
        m = GETUMessage(sender=self.name, message_data=request_message_data)
        m = self._sign_message(m)

        # Send the request to the root registry
        self.connection.send_message(self.root_config["host"],
                                     self.root_config["port"],
                                     m.encode_message())

        # Wait for a response from the root registry
        if user != "*":
            wait_start = time.time()
            while not self.userDB.hasUser(user):
                if time.time() - wait_start > 10:
                    raise Exception(
                        "Failed to get {} info from registry".format(user))

    def sendUserInfo(self, user, connection):
        """
        Sends the specified user's information to the server specified by connection
        :param user: str
            The user we want to provide information for
        :param connection: dict
            The host/port information of the receiving server
        :return: None
        """
        self.logger.debug("Sending {} info to {}".format(user, connection))

        # Construct and sign the message containing the requested information
        message = PUTUMessage(sender=self.name,
                              message_data=self.getPublicInfo(user))
        message = self._sign_message(message)
        self.connection.send_message(host=connection["host"],
                                     port=connection["port"],
                                     message=message.encode_message())

    def sendMessage(self, user, message):
        """
        Interface for sending a preconstructed message object to a user
        :param user: str
            The user to send the message to
        :param message: `~qchat.messages.Message`
            The Message object we want to send
        :return: None
        """
        # Ensure we know how to contact the user, if not resolve the information
        if not self.userDB.hasUser(user):
            self.requestUserInfo(user)

        # Get the connection information
        connection_info = self.userDB.getConnectionInfo(user)
        host = connection_info['host']
        port = connection_info['port']

        # Sign the message and send it via the connection
        message = self._sign_message(message)
        self.connection.send_message(host, port, message.encode_message())
Exemplo n.º 10
0
class UserDB:
    def __init__(self):
        """
        Initializes a user database for holding QChat contact information
        """
        self.lock = threading.Lock()
        self.logger = QChatLogger(__name__)
        self.db = defaultdict(dict)

    def _get_user(self, user):
        """
        Retrieves a user's data in the database
        :param user: str
            Name of the user
        :return: dict
            Stored data
        """
        return self.db.get(user)

    def hasUser(self, user):
        """
        Checks if the database has the specified user
        :param user: str
            The name of the user
        :return: bool
            Whether user exists in database or not
        """
        return self._get_user(user) is not None

    def getPublicKey(self, user):
        """
        Returns the stored public key of the specified user
        :param user: str
            The name of the user
        :return: bytes
            The public key data
        """
        info = self._get_user(user)
        if not info:
            raise DBException("User {} does not exist in the database!")
        return info.get('pub')

    def getMessageKey(self, user):
        """
        Retrieves the key used for encrypting/decrypting messages
        :param user: str
            The name of the user
        :return: bytes
            The key associated with the specified user
        """
        info = self._get_user(user)
        if not info:
            raise DBException("User {} does not exist in the database!")
        return info.get('message_key')

    def getConnectionInfo(self, user):
        """
        Retrieves connection information for the specified user
        :param user: str
            The name of the user
        :return: dict
            Contains connection details of the user
        """
        info = self._get_user(user)
        if not info:
            raise DBException("User {} does not exist in the database!")
        return info.get('connection')

    def deleteUserInfo(self, user, fields):
        """
        Deletes all specified fields of data for the user
        :param user: str
            Name of the user
        :param fields: list
            List of strings of the names of the fields to delete
        :return: None
        """
        self.logger.debug("Deleting user {} info {}".format(user, fields))
        info = self._get_user(user)
        if not info:
            raise DBException("User {} does not exist in the database!")
        for field in fields:
            info.pop(field)

    def deleteUser(self, user):
        """
        Deletes a user from the database
        :param user: str
            The name of the user
        :return: None
        """
        self.logger.debug("Deleting user {}".format(user))
        self.db.pop(user)

    def changeUserInfo(self, user, **kwargs):
        """
        Updates a user entry in the database
        :param user: str
            The name of the user
        :param kwargs: dict
            A dictionary of updates to merge for the user
        :return: None
        """
        self.logger.debug("Changing user {} with data {}".format(user, kwargs))
        if self.hasUser(user):
            self.db[user].update(kwargs)

    def addUser(self, user, **kwargs):
        """
        Adds a user into the database along with any initial data
        :param user: str
            The name of the user
        :param kwargs: dict
            The initial data to store for the user
        :return: None
        """
        self.logger.debug("Adding user {} with data {}".format(user, kwargs))
        self.db[user].update(kwargs)

    def getPublicUserInfo(self, user):
        """
        Returns the public information of a user including the connection details and public key
        :param user: str
            The name of the user
        :return: dict
            Contains public information of the user
        """
        if user == "*":
            public_info = []
            for user in self.db:
                info = {
                    "connection": self.getConnectionInfo(user),
                    "pub": self.getPublicKey(user)
                }

                info["pub"] = info["pub"].decode("ISO-8859-1")
                info["user"] = user

                public_info.append(info)

            public_info = {"user": "******", "info": public_info}

        else:
            public_info = {
                "connection": self.getConnectionInfo(user),
                "pub": self.getPublicKey(user)
            }

            public_info["pub"] = public_info["pub"].decode("ISO-8859-1")
            public_info["user"] = user

        return public_info
Exemplo n.º 11
0
 def __init__(self):
     self.lock = threading.Lock()
     self.messages = []
     self.logger = QChatLogger(__name__)
Exemplo n.º 12
0
class QChatCLIRPCClient:
    """
    Simple RPC client that sends messages to an RPCServer
    """
    def __init__(self, user, destination, server_url):
        """
        Initializes the RPC client
        :param user: str
            The user we wish to send a message as
        :param destination: str
            The peer we wish to send a message to
        :param server_url: str
            The RPCServer url to connect to, eg. http://127.0.0.1:6666
        """
        self.client = xmlrpc.client.ServerProxy(server_url)
        self.user = user
        self.destination = destination
        self._running = False
        self._message_reader = threading.Thread(target=self._read_messages)
        self.lock = threading.Lock()
        self.logger = QChatLogger("QChatCLIRPCClient-{}".format(user))

    def start(self):
        """
        Starts the RPC client and sends messages based on user input
        :return: None
        """
        print("Hello, this is {}".format(self.user))
        self._running = True
        self._message_reader.start()

        while self._running:
            input_text = input("\n[ {} ]: ".format(self.user))
            the_message = "{} @ {}".format(input_text, time.time())
            with self.lock:
                self.client.send_message(self.user, self.destination,
                                         the_message)

    def stop(self):
        """
        Stops the RPC Client
        :return: None
        """
        self.logger.info("Stopping the CLI RPC Client")
        self._running = False
        self._message_reader.join()

    def _read_messages(self):
        """
        Polls the RPCServer for messages that belong to the user running the RPC client
        :return: None
        """
        while self._running:
            try:
                with self.lock:
                    user_messages = self.client.get_messages(self.user)
                if user_messages:
                    print("\n")
                    for sender, messages in user_messages.items():
                        for message in messages:
                            print("[ {} ]: {}\n".format(sender, message))
                    print("[ {} ]: ".format(self.user), end="")

            except Exception:
                self.logger.exception("Failed getting messages for {}".format(
                    self.user))
                time.sleep(2)
            time.sleep(1)
Exemplo n.º 13
0
class QChatRPCServer:
    """
    An RPC Server that connects to a QChatServer
    """

    clients = {}

    def __init__(self, user, host, port, client):
        """
        Initializes the RPC server
        :param user: str
            The name of the QChatServer
        :param host: str
            The host to receive RPC commands at
        :param port: int
            The port to receive RPC commands at
        :param client: `~qchat.client.QChatClient`
            The QChatClient to interact with
        """
        self.user = user
        self.host = host
        self.port = port
        self.clients[user] = client
        self._ensure_client_for(user)
        self.logger = QChatLogger("QChatClientRPCServer-{}".format(user))
        self.logger.debug("Starting server for {} at {}:{}".format(
            user, host, port))

    def send_message(self, user, destination, message):
        """
        Listens for a send message RPC call and forwards the command to the underlying QChatClient
        :param user: str
            The name of the client to use
        :param destination: str
            The name of the peer to send the message to
        :param message: str
            The plaintext message to send
        :return: bool
            Whether message was sent or not
        """
        self._ensure_client_for(user)
        try:
            self.clients[user].sendQChatMessage(destination, message)
            return True

        except Exception:
            return False

    def get_messages(self, user):
        """
        RPC call for retrieving messages from the client
        :param user: str
            The client to retrieve messages for
        :return: dict
            A dictionary of message lists keyed by the sender
        """
        self._ensure_client_for(user)
        messages = {}
        self.logger.info("Fetching messages from {}".format(user))
        try:
            messages = dict(self.clients[user].getMessageHistory())
            if messages:
                self.logger.info("Received messages {} for user {}".format(
                    messages, user))

        except Exception:
            self.logger.exception(
                "Failed getting messages from {}".format(user))

        return messages

    def _ensure_client_for(self, user):
        """
        Checks that the specified user has a client that is running, if not sets one up
        :param user:
        :return:
        """
        if user not in self.clients:
            self.clients[user] = QChatClient(user)
            # wait for registration
            time.sleep(2)