def assert_and_kill_receiver(self, assertions, receiver: MessageReceiver): try: assertions() except AssertionError as e: self.fail(e) finally: receiver.kill()
def test_kill_receiver(self): """ Tests that a receiver can be open and appropriately killed. """ pub, priv = generate_contact_key_pair() receiver = MessageReceiver(self.port_range_min, private_key=priv, contacts=[]) time.sleep(0.1) receiver.kill()
def test_messaging_single_channel(self) -> None: """ Basic messaging test. """ receiver_public, receiver_private = generate_contact_key_pair() sender_public, sender_private = generate_contact_key_pair() sender_contact = Contact(id="sender", public_key=sender_public, host=self.localhost, port=self.port_range_min) receiver_contact = Contact(id="receiver", public_key=receiver_public, host=self.localhost, port=self.port_range_min) channel = "channel1" sender = MessageSender(receiver_contact) consumer = DebugConsumer() receiver = MessageReceiver(port=self.port_range_min, private_key=receiver_private, contacts=[sender_contact], notify_interval=self.notify_interval) receiver.register_consumer(channel, consumer) message = Message(channel, "test command", "test data") sender.send_message(message=message, sender_contact_id=sender_contact.id, private_key=sender_private) time.sleep(self.notify_interval * 2) def assertions(): assert len(consumer.messages) == 1 received_message_sender_id, received_message = consumer.messages[0] assert received_message_sender_id == sender_contact.id assert received_message == message self.assert_and_kill_receiver(assertions, receiver)
def test_wrong_receiver_public(self): """ Tests that sending message with wrong key results in MessageDeliveryError. """ receiver_public, receiver_private = generate_contact_key_pair() sender_public, sender_private = generate_contact_key_pair() wrong_public, wrong_private = generate_contact_key_pair() sender_contact = Contact(id="sender", public_key=sender_public, host=self.localhost, port=self.port_range_min) receiver_contact = Contact(id="receiver", public_key=wrong_public, host=self.localhost, port=self.port_range_min) channel = "channel1" sender = MessageSender(receiver_contact) consumer = DebugConsumer() receiver = MessageReceiver(port=self.port_range_min, private_key=receiver_private, contacts=[sender_contact], notify_interval=self.notify_interval) receiver.register_consumer(channel, consumer) message = Message(channel, "test command", "test data") sender.send_message(message=message, sender_contact_id=sender_contact.id, private_key=sender_private) time.sleep(self.notify_interval * 2) def assertions(): assert len(consumer.messages) == 0 self.assert_and_kill_receiver(assertions, receiver)
def __init__( self, self_contact: Contact, private_key: rsa.PrivateKey, contacts=None, receiver_notify_interval=1.0, contact_restore_timeout=3600, inactive_nodes_ping_interval=1799 ): """ Initializes a new address book. :param self_contact: contact of the owner of the address book :param private_key: private key of the message receiver :param contacts: list of known contacts :param receiver_notify_interval: interval at which the message receiver notifies of new messages :param contact_restore_timeout: timeout of pinging of inactive nodes before deletion :param inactive_nodes_ping_interval: interval for pinging inactive nodes """ if contacts is None: contacts = [] self.contacts = deepcopy(contacts) self._private_key = private_key self.receiver = MessageReceiver( port=self_contact.port, private_key=self._private_key, contacts=self.contacts, notify_interval=receiver_notify_interval ) self.receiver.register_consumer(channel=self._messaging_channel, message_consumer=self) self.self_contact = self_contact self._contact_restore_timeout = contact_restore_timeout self._inactive_nodes_ping_interval = inactive_nodes_ping_interval thread = threading.Thread(target=self._start_pinging_inactive_nodes) thread.daemon = True thread.start()
class AddressBook(MessageConsumer): """ Node address book, responsible for sharing new contacts and deleting inactive ones. """ _messaging_channel = 'network' def __init__(self, self_contact: Contact, private_key: rsa.PrivateKey, contacts=None, receiver_notify_interval=1.0, contact_restore_timeout=3600, inactive_nodes_ping_interval=1799): """ Initializes a new address book. :param self_contact: contact of the owner of the address book :param private_key: private key of the message receiver :param contacts: list of known contacts :param receiver_notify_interval: interval at which the message receiver notifies of new messages :param contact_restore_timeout: timeout of pinging of inactive nodes before deletion :param inactive_nodes_ping_interval: interval for pinging inactive nodes """ if contacts is None: contacts = [] self.contacts = deepcopy(contacts) self._private_key = private_key self.receiver = MessageReceiver( port=self_contact.port, private_key=self._private_key, contacts=self.contacts, notify_interval=receiver_notify_interval) self.receiver.register_consumer(channel=self._messaging_channel, message_consumer=self) self.self_contact = self_contact self._contact_restore_timeout = contact_restore_timeout self._inactive_nodes_ping_interval = inactive_nodes_ping_interval threading.Thread(target=self._start_pinging_inactive_nodes).start() def kill(self): self.receiver.kill() def _generate_add_contact_message(self, contact: Contact) -> Message: """ Generates an "add-contact" message. :param contact: contact to add """ return Message(channel=self._messaging_channel, command='add-contact', data=contact) def _add_contact(self, contact: Contact) -> None: """ Handles incoming "add-contacts" commands. :param contact: contact to add """ self.create_new_distributed_contact(contact) def _forward_contact(self, contact: Contact) -> None: """ Forwards a contact to all other known contacts. :param contact: contact to forward """ message = self._generate_add_contact_message(contact) for known_contact in self.contacts: # Prevent notifying a contact of themselves if known_contact.id == contact.id: continue # Prevent node notifying itself if known_contact.id == self.self_contact.id: continue self.send_message_to_contact(known_contact, message) def send_message_to_contact(self, recipient: Contact, message: Message) -> bool: """ Sends a message to a contact, and marks the link to the recipient as either up or down. :param recipient: recipient node's contact or recipient node's contact id :param message: message to send :return: True iff the delivery of the message was successful """ try: sender = MessageSender(recipient) sender.send_message(message, self.self_contact.id, self._private_key) self._set_link_state(True, recipient) return True except MessageDeliveryError: self._set_link_state(False, recipient) return False def _set_link_state(self, link_up: bool, contact: Contact) -> None: """ Sets the link with a node's state. :param link_up: state to set :param contact: contact to set the link's state of """ for known_contact in self.contacts: if known_contact.id == contact.id: if link_up: known_contact.link_up() else: known_contact.link_down() def _delete_contact(self, contact: Contact) -> None: """ Deletes a contact from the contact list. :param contact: contact to delete :return: """ for known_contact in self.contacts: if known_contact.id == contact.id: self.contacts.remove(known_contact) return def _generate_ping_message(self) -> Message: """ Generates a ping message :return: the generated ping message """ return Message(channel=self._messaging_channel, command="ping") def _start_pinging_inactive_nodes(self) -> None: """ Starts periodically pinging inactive nodes. """ while not self.receiver.kill_flag: time.sleep(self._inactive_nodes_ping_interval) if self.receiver.kill_flag: return for contact in self.contacts: if not contact.is_active(): ping_message = self._generate_ping_message() if not self.send_message_to_contact(contact, ping_message): current_timestamp = now() if current_timestamp - contact.first_failure > self._contact_restore_timeout: self._delete_contact(contact) def notify(self, message: Message, sender_id) -> None: """ Handles incoming messages. :param sender_id: id of the message sender :param message: message to handle """ if message.channel == self._messaging_channel: if message.command == 'add-contact': self._add_contact(message.data) def create_new_distributed_contact(self, contact: Contact) -> None: """ Adds new contact and notifies the network. :param contact: new contact """ if self._append_contact(contact): self._forward_contact(contact) def _append_contact(self, contact: Contact) -> bool: """ Appends a contact to the contacts list. :param contact: contact to append to the contacts list :return: true iff the contact list has changed as a result of the operation """ if contact.id == self.self_contact.id: return False for known_contact in self.contacts: if known_contact.id == contact.id: return False self.contacts.append(contact) return True
def test_messaging_multiple_channels(self): """ Messaging test with multiple channels. """ receiver_public, receiver_private = generate_contact_key_pair() sender_public, sender_private = generate_contact_key_pair() sender_contact = Contact(id="sender", public_key=sender_public, host=self.localhost, port=self.port_range_min) receiver_contact = Contact(id="receiver", public_key=receiver_public, host=self.localhost, port=self.port_range_min) channel1 = "channel1" channel2 = "channel2" sender = MessageSender(receiver_contact) consumer1 = DebugConsumer() consumer2 = DebugConsumer() receiver = MessageReceiver(port=self.port_range_min, private_key=receiver_private, contacts=[sender_contact], notify_interval=self.notify_interval) receiver.register_consumer(channel1, consumer1) receiver.register_consumer(channel2, consumer2) message1 = Message(channel1, "test command", "message 1") message2 = Message(channel2, "test command", "message 2") sender.send_message(message=message1, sender_contact_id=sender_contact.id, private_key=sender_private) sender.send_message(message=message2, sender_contact_id=sender_contact.id, private_key=sender_private) time.sleep(self.notify_interval * 2) def assertions(): assert len(consumer1.messages) == 1 assert len(consumer2.messages) == 1 received_message_sender_id1, received_message1 = consumer1.messages[ 0] received_message_sender_id2, received_message2 = consumer2.messages[ 0] assert received_message_sender_id1 == sender_contact.id assert received_message_sender_id2 == sender_contact.id assert received_message1 == message1 assert received_message2 == message2 self.assert_and_kill_receiver(assertions, receiver)