k98kurz

gossip.py

Jun 18th, 2022 (edited)
707
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 23.98 KB | None | 0 0
  1. from __future__ import annotations
  2. from dataclasses import dataclass
  3. from functools import reduce
  4. from hashlib import sha256
  5. from time import time
  6. from nacl.public import Box, SealedBox
  7. from nacl.signing import SigningKey, VerifyKey, SignedMessage
  8. from queue import SimpleQueue
  9. from random import randint
  10. from secrets import token_bytes
  11. from typing import Protocol
  12. import readline
  13. import struct
  14.  
  15.  
  16. """Proof-of-concept gossip protocol implementation. To use in a real
  17.    application, import the module; create new handlers as necessary for
  18.    the duck typed protocols SupportsSendAndDeliverMessage,
  19.    SupportsHandleMessage, and SupportsHandleAction; and register the
  20.    handlers as shown in main. Note that Node.from_seed is used only for
  21.    the active node(s) while Node.__init__ is can be used for neighbors
  22.    for which we know only the public key/address. Most models include
  23.    a data or metadata property for extensibility. The run_tick,
  24.    format_address, and action_count functions can be used without
  25.    modification. Debug message handling can be swapped out from print
  26.    to a custom function using deregister_debug_handler(print) and
  27.    register_debug_handler(custom_func), e.g. to write to a log file.
  28. """
  29.  
  30.  
  31. def license() -> str:
  32.     """Copyleft (c) 2022 k98kurz
  33.  
  34.        Permission to use, copy, modify, and/or distribute this software
  35.        for any purpose with or without fee is hereby granted, provided
  36.        that the above copyleft notice and this permission notice appear in
  37.        all copies.
  38.  
  39.        THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
  40.        WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
  41.        WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
  42.        AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
  43.        CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
  44.        OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  45.        NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  46.        CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  47.    """
  48.     return license.__doc__
  49.  
  50.  
  51. # global config and toggle/utility functions
  52. ENABLE_DEBUG = True
  53. DISPLAY_SHORT_ADDRESSES = True
  54. SIGN_MESSAGES = True
  55. ENCRYPT_MESSAGES = True
  56. MESSAGE_TTL = 300
  57. DEBUG_HANDLERS = [print]
  58.  
  59. def format_address(address: bytes) -> str:
  60.     global DISPLAY_SHORT_ADDRESSES
  61.     return address.hex()[:8] if DISPLAY_SHORT_ADDRESSES else address.hex()
  62.  
  63. def toggle_short_address() -> bool:
  64.     global DISPLAY_SHORT_ADDRESSES
  65.     DISPLAY_SHORT_ADDRESSES = not DISPLAY_SHORT_ADDRESSES
  66.     return DISPLAY_SHORT_ADDRESSES
  67.  
  68. def debug(msg: str):
  69.     """Pass debug messages to all debug message handlers."""
  70.     global ENABLE_DEBUG, DEBUG_HANDLERS
  71.     if ENABLE_DEBUG:
  72.         for d in DEBUG_HANDLERS:
  73.             d(msg)
  74.  
  75. def register_debug_handler(c: function) -> None:
  76.     """Register a new function for handling debug messages."""
  77.     if not callable(c):
  78.         raise TypeError('Can only register callables as debug handlers.')
  79.     global DEBUG_HANDLERS
  80.     if c not in DEBUG_HANDLERS:
  81.         DEBUG_HANDLERS.append(c)
  82.  
  83. def unregister_debug_handler(c: function) -> None:
  84.     """Unregister a function from handling debug messages."""
  85.     if not callable(c):
  86.         raise TypeError('Can only deregister callables as debug handlers.')
  87.     global DEBUG_HANDLERS
  88.     if c in DEBUG_HANDLERS:
  89.         DEBUG_HANDLERS.remove(c)
  90.  
  91. def toggle_debug() -> bool:
  92.     global ENABLE_DEBUG
  93.     ENABLE_DEBUG = not ENABLE_DEBUG
  94.     return ENABLE_DEBUG
  95.  
  96. def toggle_sign_all_messages() -> bool:
  97.     global SIGN_MESSAGES
  98.     SIGN_MESSAGES = not SIGN_MESSAGES
  99.     return SIGN_MESSAGES
  100.  
  101. def toggle_encrypt_messages() -> bool:
  102.     global ENCRYPT_MESSAGES
  103.     ENCRYPT_MESSAGES = not ENCRYPT_MESSAGES
  104.     return ENCRYPT_MESSAGES
  105.  
  106.  
  107. @dataclass
  108. class Connection:
  109.     """Connection model represent an edge connecting two Nodes together."""
  110.     nodes: set[Node]
  111.     data: dict
  112.  
  113.     def __init__(self, nodes: list[Node]) -> None:
  114.         if type(nodes) is not list or len(nodes) != 2:
  115.             raise Exception('a Connection must connect exactly 2 nodes')
  116.         self.nodes = set(nodes)
  117.         self.data = {}
  118.  
  119.     def __hash__(self) -> int:
  120.         """Enable inclusion in sets."""
  121.         node_list = list(self.nodes)
  122.         node_list.sort()
  123.         return hash(node_list[0].address + node_list[1].address)
  124.  
  125.  
  126. @dataclass
  127. class Message:
  128.     """Message model contains the source, destination, content, and
  129.        optional signature and metadata.
  130.    """
  131.     src: bytes
  132.     dst: bytes
  133.     ts: int
  134.     msg: bytes
  135.     sig: bytes
  136.     metadata: dict
  137.  
  138.     def __init__(self, src: bytes, dst: bytes, msg: bytes, ts: int = None, sig: bytes = None) -> None:
  139.         self.src = src
  140.         self.dst = dst
  141.         self.ts = int(time()) if ts is None else ts
  142.         self.msg = msg
  143.         self.sig = sig
  144.         self.metadata = {}
  145.  
  146.     def __repr__(self) -> str:
  147.         return f"{format_address(self.src)}->{format_address(self.dst)}: {format_address(sha256(self.msg).digest())}"
  148.  
  149.     def __bytes__(self) -> bytes:
  150.         return self.src + self.dst + self.msg
  151.  
  152.     def __hash__(self) -> int:
  153.         """Enable inclusion in sets."""
  154.         return hash(bytes(self))
  155.  
  156.     def pack(self) -> bytes:
  157.         """Pack the data with struct."""
  158.         if self.sig is not None:
  159.             fstr = '!32s32si64s' + str(len(self.msg)) + 's'
  160.             return struct.pack(fstr, self.dst, self.src, self.ts, self.sig, self.msg)
  161.         else:
  162.             fstr = '!32s32si' + str(len(self.msg)) + 's'
  163.             return struct.pack(fstr, self.dst, self.src, self.ts, self.msg)
  164.  
  165.     @classmethod
  166.     def unpack(cls, packed: bytes) -> Message:
  167.         """Unpack the data with struct."""
  168.         global SIGN_MESSAGES
  169.         if SIGN_MESSAGES:
  170.             fstr = '!32s32si64s' + str(len(packed) - 128) + 's'
  171.             (dst, src, ts, sig, msg) = struct.unpack(fstr, packed)
  172.             return Message(src, dst, msg, ts, sig)
  173.         else:
  174.             fstr = '!32s32si' + str(len(packed) - 64) + 's'
  175.             (dst, src, ts, msg) = struct.unpack(fstr, packed)
  176.             return Message(src, dst, msg, ts)
  177.  
  178.     def sign(self, skey: SigningKey) -> SignedMessage:
  179.         """Generate a signature for the message."""
  180.         sig = skey.sign(bytes(self))
  181.         self.sig = sig[:64]
  182.         return sig
  183.  
  184.     def verify(self) -> bool:
  185.         """Verify the message signature"""
  186.         try:
  187.             vkey = VerifyKey(self.src)
  188.             sig = SignedMessage(self.sig + bytes(self))
  189.             vkey.verify(sig)
  190.             return True
  191.         except:
  192.             return False
  193.  
  194.     def encrypt(self, skey: SigningKey) -> None:
  195.         """Encrypt the message by the sender."""
  196.         if bytes(skey.verify_key) != self.src:
  197.             raise ValueError('Must use the skey of the sender to encrypt.')
  198.  
  199.         privk = skey.to_curve25519_private_key()
  200.         pubk = VerifyKey(self.dst).to_curve25519_public_key()
  201.         box = Box(privk, pubk)
  202.         self.msg = bytes(box.encrypt(self.msg))
  203.  
  204.     def decrypt(self, skey: SigningKey) -> None:
  205.         """Decrypt the message by the receiver."""
  206.         if bytes(skey.verify_key) != self.dst:
  207.             raise ValueError('Must use the skey of the receiver to decrypt.')
  208.  
  209.         privk = skey.to_curve25519_private_key()
  210.         pubk = VerifyKey(self.src).to_curve25519_public_key()
  211.         box = Box(privk, pubk)
  212.         self.msg = box.decrypt(self.msg)
  213.  
  214.     def seal(self) -> None:
  215.         """Encrypt using ephemeral ECDHE."""
  216.         sealed_box = SealedBox(VerifyKey(self.dst).to_curve25519_public_key())
  217.         self.msg = sealed_box.encrypt(self.msg)
  218.  
  219.     def unseal(self, skey: SigningKey) -> None:
  220.         """Decrypt using ephemeral ECDHE."""
  221.         if bytes(skey.verify_key) != self.dst:
  222.             raise ValueError('Must use the skey of the receiver to decrypt.')
  223.  
  224.         privk = skey.to_curve25519_private_key()
  225.         sealed_box = SealedBox(privk)
  226.         self.msg = sealed_box.decrypt(self.msg)
  227.  
  228.  
  229. @dataclass
  230. class Action:
  231.     """Action model contains the name and data for an action a Node will
  232.        take by passing to the registered action handler.
  233.    """
  234.     name: str
  235.     data: dict
  236.  
  237.     def __init__(self, name: str, data: dict) -> None:
  238.         self.name = name
  239.         self.data = data
  240.  
  241.  
  242. class SupportsSendAndDeliverMessage(Protocol):
  243.     """Duck type protocol for message sender."""
  244.     def send(self, msg: Message) -> None:
  245.         ...
  246.  
  247.     def deliver(self) -> None:
  248.         ...
  249.  
  250.  
  251. class SupportsHandleMessage(Protocol):
  252.     """Duck type protocol for incoming message handler."""
  253.     def handle(self, msg: Message) -> None:
  254.         ...
  255.  
  256.  
  257. class SupportsHandleAction(Protocol):
  258.     """Duck type protocol for action handler."""
  259.     def handle(self, action: dict) -> None:
  260.         ...
  261.  
  262.  
  263. @dataclass
  264. class Node:
  265.     """The core model representing a Node and handling its Connections,
  266.        Actions, and Messages. Invoke with Node(address) for neighbors
  267.        and Node.from_seed(seed) for an active node. Optional data
  268.        property for extensibility. The address is the public key bytes
  269.        of the node for when SIGN_MESSAGES is set.
  270.    """
  271.     address: bytes
  272.     msgs_seen: set[bytes]
  273.     connections: set[Connection]
  274.     data: dict
  275.     _seed: bytes
  276.     _skey: SigningKey
  277.     _vkey: VerifyKey
  278.     _inbound: SimpleQueue
  279.     _outbound: SimpleQueue
  280.     _actions: SimpleQueue
  281.     _message_sender: SupportsSendAndDeliverMessage
  282.     _message_handler: SupportsHandleMessage
  283.     _action_handler: SupportsHandleAction
  284.  
  285.     def __init__(self, address: bytes) -> None:
  286.         """Create a node from its address (public key bytes)."""
  287.         self.address = address
  288.         self.msgs_seen = set()
  289.         self.connections = set()
  290.         self.data = {}
  291.         self._vkey = VerifyKey(address)
  292.         self._seed = None
  293.         self._skey = None
  294.         self._inbound = SimpleQueue()
  295.         self._outbound = SimpleQueue()
  296.         self._actions = SimpleQueue()
  297.  
  298.     @classmethod
  299.     def from_seed(cls, seed: bytes):
  300.         """Create a node from a seed filling out _skey."""
  301.         skey = SigningKey(seed)
  302.         node = cls(bytes(skey.verify_key))
  303.         node._skey = skey
  304.         node._seed = seed
  305.         return node
  306.  
  307.     def __hash__(self) -> int:
  308.         """Enable inclusion in sets."""
  309.         return hash(self.address)
  310.  
  311.     def __lt__(self, other: Node) -> bool:
  312.         return self.address < other.address
  313.  
  314.     def __repr__(self) -> str:
  315.         if self._seed is not None:
  316.             return "{'address': '" + format_address(self.address) + "','seed':'" + self._seed.hex() + "}"
  317.         else:
  318.             return "{'address': '" + format_address(self.address) + "'}"
  319.  
  320.     def register_message_sender(self, sndr: SupportsSendAndDeliverMessage) -> None:
  321.         """Register the message sender."""
  322.         if not hasattr(sndr, 'send') or not callable(sndr.send):
  323.             raise TypeError('sndr must fulfill SupportsSendAndDeliverMessage duck type')
  324.         self._message_sender = sndr
  325.  
  326.     def register_message_handler(self, hndlr: SupportsHandleMessage) -> None:
  327.         """Register the incoming message handler."""
  328.         if not hasattr(hndlr, 'handle') or not callable(hndlr.handle):
  329.             raise TypeError('hndlr must fulfill SupportsHandleMessage duck type')
  330.         self._message_handler = hndlr
  331.  
  332.     def register_action_handler(self, hndlr: SupportsHandleAction) -> None:
  333.         """Register the action handler."""
  334.         if not hasattr(hndlr, 'handle') or not callable(hndlr.handle):
  335.             raise TypeError('hndlr must fulfill SupportsHandleAction duck type')
  336.         self._action_handler = hndlr
  337.  
  338.     def add_connection(self, connection: Connection) -> None:
  339.         """Add the specified connection."""
  340.         if not isinstance(connection, Connection):
  341.             raise TypeError('connection must be a Connection')
  342.         self.connections.add(connection)
  343.  
  344.     def drop_connection(self, connection: Connection) -> None:
  345.         """Drop the specified connection."""
  346.         if not isinstance(connection, Connection):
  347.             raise TypeError('connection must be a Connection')
  348.         self.connections.remove(connection)
  349.  
  350.     def count_connections(self) -> int:
  351.         return len(self.connections)
  352.  
  353.     def receive_message(self, message: Message):
  354.         """Queue up an incoming message if its signature is valid or
  355.            ignored.
  356.        """
  357.         if not isinstance(message, Message):
  358.             raise TypeError('message must be a Message')
  359.  
  360.         global SIGN_MESSAGES, ENCRYPT_MESSAGES, MESSAGE_TTL
  361.  
  362.         if int(time()) > (message.ts + MESSAGE_TTL):
  363.             debug("Node.receive_message: old message discarded")
  364.         elif message.sig is not None:
  365.             if message.verify():
  366.                 if ENCRYPT_MESSAGES:
  367.                     message.unseal(self._skey)
  368.                 self._inbound.put(message)
  369.             else:
  370.                 debug("Node.receive_message: message signature failed verification")
  371.         elif SIGN_MESSAGES:
  372.             debug("Node.receive_message: unsigned message rejected")
  373.         else:
  374.             if ENCRYPT_MESSAGES:
  375.                 message.unseal(self._skey)
  376.             self._inbound.put(message)
  377.  
  378.     def send_message(self, dst: bytes, msg: bytes):
  379.         """Queue up an outgoing message. Sign if necessary and possible."""
  380.         if type(dst) is not bytes:
  381.             raise TypeError("dst must be bytes")
  382.         if type(msg) is not bytes:
  383.             raise TypeError("msg must be bytes")
  384.  
  385.         message = Message(self.address, dst, msg)
  386.  
  387.         global ENCRYPT_MESSAGES
  388.         if ENCRYPT_MESSAGES:
  389.             message.seal()
  390.  
  391.         if self._skey is not None:
  392.             message.sign(self._skey)
  393.  
  394.         if len(self.connections):
  395.             if len([c for c in self.connections if dst in [n.address for n in c.nodes]]):
  396.                 self._outbound.put(message)
  397.             else:
  398.                 debug("cannot deliver message due to lack of connection")
  399.         else:
  400.             self._outbound.put(message)
  401.  
  402.     def queue_action(self, act: Action) -> None:
  403.         """Queue an action to be processed by the action handler."""
  404.         if not isinstance(act, Action):
  405.             raise TypeError('act must be an Action')
  406.         self._actions.put(act)
  407.  
  408.     def process(self):
  409.         """Process actions for this node once."""
  410.         if self._outbound.qsize() > 0 and self._message_sender is not None:
  411.             self._message_sender.send(self._outbound.get())
  412.         if self._inbound.qsize() > 0 and self._message_handler is not None:
  413.             self._message_handler.handle(self._inbound.get())
  414.         if self._actions.qsize() > 0 and self._action_handler is not None:
  415.             self._action_handler.handle(self._actions.get())
  416.  
  417.     def action_count(self):
  418.         """Count the size of pending messages and actions."""
  419.         return self._outbound.qsize() + self._inbound.qsize() + self._actions.qsize()
  420.  
  421.  
  422. @dataclass
  423. class MessageSender:
  424.     """Example message sender that does not use a network stack."""
  425.     nodes: set[Node]
  426.     message_queue: SimpleQueue
  427.     dead_letters: list
  428.     data: dict
  429.  
  430.     def __init__(self) -> None:
  431.         self.nodes = set()
  432.         self.message_queue = SimpleQueue()
  433.         self.dead_letters = []
  434.         self.data = {}
  435.  
  436.     def register_node(self, node: Node) -> None:
  437.         """Register the specified node for message delivery."""
  438.         if not isinstance(node, Node):
  439.             raise TypeError('node must be a Node')
  440.         self.nodes.add(node)
  441.         self.check_dead_letters(node)
  442.  
  443.     def register_nodes(self, nodes: list[Node]) -> None:
  444.         """Register the specified list of nodes for message delivery."""
  445.         if not isinstance(nodes, list):
  446.             raise TypeError('nodes must be a list of Nodes')
  447.         for n in nodes:
  448.             self.register_node(n)
  449.  
  450.     def send(self, msg: Message) -> None:
  451.         """Queue up the specified message for delivery."""
  452.         if not isinstance(msg, Message):
  453.             raise TypeError('msg must be a Message')
  454.         debug(f"MessageSender.send(): {msg}")
  455.         self.message_queue.put(msg)
  456.  
  457.     def check_queue(self) -> int:
  458.         """Return pending message queue size."""
  459.         return self.message_queue.qsize()
  460.  
  461.     def deliver(self) -> None:
  462.         """Deliver all pending messages that can be delivered."""
  463.         while self.check_queue() > 0:
  464.             msg = self.message_queue.get()
  465.             found = False
  466.  
  467.             for n in self.nodes:
  468.                 if n.address == msg.dst:
  469.                     n.receive_message(msg)
  470.                     found = True
  471.                     debug(f'MessageSender.deliver(): delivered {format_address(sha256(msg.msg).digest())} to {format_address(n.address)}')
  472.  
  473.             if not found:
  474.                 self.dead_letters.append(msg)
  475.                 debug('MessageSender.deliver(): dead letter')
  476.  
  477.     def check_dead_letters(self, node: Node) -> None:
  478.         """Goes through the dead letters to try to deliver to a newly
  479.            registered node.
  480.        """
  481.         if not isinstance(node, Node):
  482.             raise TypeError('node must be a Node')
  483.  
  484.         delivered = []
  485.         for i, l in enumerate(self.dead_letters):
  486.             if l.dst == node.address:
  487.                 node.receive_message(l)
  488.                 delivered.append(l)
  489.         self.dead_letters[:] = [l for l in self.dead_letters if l not in delivered]
  490.  
  491.  
  492. @dataclass
  493. class MessageHandler:
  494.     """Example message handler. Optional data property for extensibility."""
  495.     nodes: set[Node]
  496.     data: dict
  497.  
  498.     def __init__(self) -> None:
  499.         self.nodes = set()
  500.         self.data = {}
  501.  
  502.     def register_node(self, node: Node) -> None:
  503.         """Register the specified node for handling incoming messages."""
  504.         if not isinstance(node, Node):
  505.             raise TypeError('node must be a Node')
  506.         self.nodes.add(node)
  507.  
  508.     def register_nodes(self, nodes: list[Node]) -> None:
  509.         """Register the specified list of nodes for handling incoming
  510.            messages.
  511.        """
  512.         if not isinstance(nodes, list):
  513.             raise TypeError('nodes must be a list of Nodes')
  514.         for n in nodes:
  515.             self.register_node(n)
  516.  
  517.     def handle(self, msg: Message) -> None:
  518.         """Handle an incoming message."""
  519.         if not isinstance(msg, Message):
  520.             raise TypeError('msg must be a Message')
  521.  
  522.         debug(f'MessageHandler.handle(): {msg}')
  523.  
  524.         if msg.dst in [n.address for n in self.nodes]:
  525.             n = [n for n in self.nodes if n.address == msg.dst][0]
  526.             n.queue_action(Action('store_and_forward', {"msg": msg.msg}))
  527.         else:
  528.             debug('MessageHandler.handle(): message dropped')
  529.  
  530.  
  531. @dataclass
  532. class ActionHandler:
  533.     """Example action handler."""
  534.     node: Node
  535.     other_nodes: set[Node]
  536.  
  537.     def __init__(self, node: Node, other_nodes: list[Node]) -> None:
  538.         self.node = node
  539.         self.other_nodes = set(other_nodes)
  540.  
  541.     def handle(self, act: Action) -> None:
  542.         """Handle an action. Limited to store_and_forward action."""
  543.         if act.name == 'store_and_forward':
  544.             if sha256(act.data['msg']).digest() not in self.node.msgs_seen:
  545.                 # store
  546.                 self.node.msgs_seen.add(sha256(act.data['msg']).digest())
  547.                 debug(f"ActionHandler.handle(): store_and_forward [{act.data['msg'].hex()}]")
  548.  
  549.                 # forward
  550.                 if self.node.count_connections() > 0:
  551.                     # forward to all connected nodes
  552.                     for c in self.node.connections:
  553.                         n = [n for n in c.nodes if n is not self.node][0]
  554.                         self.node.send_message(n.address, act.data['msg'])
  555.                 else:
  556.                     # forward to 2 nodes at random
  557.                     n1 = list(self.other_nodes)[randint(0, len(self.other_nodes)-1)]
  558.                     n2 = list(self.other_nodes)[randint(0, len(self.other_nodes)-1)]
  559.                     self.node.send_message(n1.address, act.data['msg'])
  560.                     self.node.send_message(n2.address, act.data['msg'])
  561.             else:
  562.                 debug(f"ActionHandler.handle(): store_and_forward skipped for seen message")
  563.  
  564.  
  565. def run_tick(nodes: list[Node], msg_sender: MessageSender):
  566.     """Run the process for all nodes, then deliver all pending messages."""
  567.     for n in nodes:
  568.         n.process()
  569.     msg_sender.deliver()
  570.  
  571.  
  572. def action_count(nodes: list[Node]):
  573.     """Returns a count of all pending actions and messages."""
  574.     if not isinstance(nodes, list):
  575.         raise TypeError('nodes must be list of Nodes')
  576.     return reduce(lambda c, n: c + n.action_count(), nodes, 0)
  577.  
  578.  
  579. def main():
  580.     # create some nodes
  581.     nodes = [Node.from_seed(token_bytes(32)) for i in range(16)]
  582.  
  583.     # create handlers
  584.     msg_handler = MessageHandler()
  585.     msg_handler.register_nodes(nodes)
  586.     msg_sender = MessageSender()
  587.     msg_sender.register_nodes(nodes)
  588.  
  589.     # register handlers
  590.     for n in nodes:
  591.         n.register_message_handler(msg_handler)
  592.         n.register_message_sender(msg_sender)
  593.         n.register_action_handler(ActionHandler(n, [on for on in nodes if on is not n]))
  594.  
  595.     # flag for exit
  596.     end_signal = False
  597.  
  598.     # access global variables
  599.     global SIGN_MESSAGES, ENCRYPT_MESSAGES
  600.  
  601.     while not end_signal:
  602.         data = input("$: ")
  603.         command = data.split(' ')[0].strip()
  604.         data = ' '.join(data.split(' ')[1:]).strip()
  605.  
  606.         if command in ('quit', 'q'):
  607.             end_signal = True
  608.         elif command in ('list', 'nodes', 'l', 'n', 'ln'):
  609.             for n in nodes:
  610.                 print(f"{format_address(n.address)}: {[format_address(m) for m in n.msgs_seen]}")
  611.         elif command in ('listcon', 'connections', 'lc'):
  612.             connections = set()
  613.             for n in nodes:
  614.                 connections = connections.union(n.connections)
  615.             for c in connections:
  616.                 cnodes = list(c.nodes)
  617.                 print(f"{format_address(cnodes[0].address)} - {format_address(cnodes[1].address)}")
  618.         elif command in ('c', 'connect'):
  619.             for n in nodes:
  620.                 others = [o for o in nodes if o is not n]
  621.                 for i in range(3):
  622.                     o = others[randint(0, len(others)-1)]
  623.                     n.add_connection(Connection([n, o]))
  624.                     o.add_connection(Connection([n, o]))
  625.         elif command in ('message', 'm'):
  626.             src = nodes[randint(0, len(nodes)-1)]
  627.             message = Message(src.address, src.address, bytes(data, 'utf-8'))
  628.             if ENCRYPT_MESSAGES:
  629.                 message.seal()
  630.             if SIGN_MESSAGES:
  631.                 message.sign(src._skey)
  632.             src.receive_message(message)
  633.         elif command in ('d', 'debug'):
  634.             print("debug enabled" if toggle_debug() else "debug disabled")
  635.         elif command in ('s', 'short'):
  636.             print("short addresses enabled" if toggle_short_address() else "short addresses disabled")
  637.         elif command in ('r', 'run'):
  638.             while action_count(nodes) > 0:
  639.                 run_tick(nodes, msg_sender)
  640.         elif command in ('h', 'help', '?'):
  641.             print("options:\t[l|ln|nodes|list] to list nodes and messages seen by each")
  642.             print("\t\t[m|message] {str} to send a message")
  643.             print("\t\t[c|connect] to connect nodes together randomly")
  644.             print("\t\t[lc|listcon|connections] list all connections")
  645.             print("\t\t[q|quit] to end")
  646.             print("\t\t[h|help|?] display this text")
  647.             print("\t\t[d|debug] to toggle debug messages")
  648.             print("\t\t[s|short] to toggle displaying short address format")
  649.             print("\t\t[r|run] to run until no pending actions remain")
  650.             print("\t\tanything else to process a tick")
  651.         else:
  652.             run_tick(nodes, msg_sender)
  653.  
  654.  
  655. if __name__ == '__main__':
  656.     main()
  657.  
Add Comment
Please, Sign In to add comment