class Peer: def __init__(self, server_ip, server_port, is_root=False, root_address=None): """ The Peer object constructor. Code design suggestions: 1. Initialise a Stream object for our Peer. 2. Initialise a PacketFactory object. 3. Initialise our UserInterface for interaction with user commandline. 4. Initialise a Thread for handling reunion daemon. Warnings: 1. For root Peer we need a NetworkGraph object. 2. In root Peer start reunion daemon as soon as possible. 3. In client Peer we need to connect to the root of the network, Don't forget to set this connection as a register_connection. :param server_ip: Server IP address for this Peer that should be pass to Stream. :param server_port: Server Port address for this Peer that should be pass to Stream. :param is_root: Specify that is this Peer root or not. :param root_address: Root IP/Port address if we are a client. :type server_ip: str :type server_port: int :type is_root: bool :type root_address: tuple """ self._is_root = is_root self.stream = Stream(server_ip, server_port) self.parent = None self.packets = [] self.neighbours = [] self.flagg = True self.reunion_accept = True self.reunion_daemon_thread = threading.Thread( target=self.run_reunion_daemon) self.reunion_sending_time = time.time() self.reunion_pending = False self._user_interface = UserInterface() self.packet_factory = PacketFactory() if self._is_root: self.network_nodes = [] self.registered_nodes = [] self.network_graph = NetworkGraph( GraphNode((server_ip, str(server_port).zfill(5)))) self.reunion_daemon_thread.start() else: self.root_address = root_address self.stream.add_node(root_address, set_register_connection=True) def start_user_interface(self): """ For starting UserInterface thread. :return: """ print("Starting UI") print('Available commands: ' 'Register', 'Advertise', 'SendMessage') self._user_interface.start() def handle_user_interface_buffer(self): """ In every interval we should parse user command that buffered from our UserInterface. All of the valid commands are listed below: 1. Register: With this command client send a Register Request packet to the root of the network. 2. Advertise: Send an Advertise Request to the root of the network for finding first hope. 3. SendMessage: The following string will be add to a new Message packet and broadcast through network. Warnings: 1. Ignore irregular commands from user. 2. Don't forget to clear our UserInterface buffer. :return: """ available_commands = ['Register', 'Advertise', 'SendMessage'] # # print("user interface handler ", self._user_interface.buffer) for buffer in self._user_interface.buffer: if len(buffer) == 0: continue print('User interface handler buffer:', buffer) if buffer.split(' ', 1)[0] == available_commands[0]: # print("Handling buffer/register in UI") self.stream.add_message_to_out_buff( self.root_address, self.packet_factory.new_register_packet( "REQ", self.stream.get_server_address(), self.root_address).get_buf()) elif buffer.split(' ', 1)[0] == available_commands[1]: # print("Handling buffer/advertise in UI") self.stream.add_message_to_out_buff( self.root_address, self.packet_factory.new_advertise_packet( "REQ", self.stream.get_server_address()).get_buf()) elif buffer.split(' ', 1)[0] == available_commands[2]: # print("Handling buffer/SendMessage in UI") self.send_broadcast_packet( self.packet_factory.new_message_packet( buffer.split(' ', 1)[1], self.stream.get_server_address()).get_buf()) else: print('Unknown Command!!!') self._user_interface.buffer = [] def run(self): """ Main loop of the program. Code design suggestions: 1. Parse server in_buf of the stream. 2. Handle all packets were received from our Stream server. 3. Parse user_interface_buffer to make message packets. 4. Send packets stored in nodes buffer of our Stream object. 5. ** sleep the current thread for 2 seconds ** Warnings: 1. At first check reunion daemon condition; Maybe we have a problem in this time and so we should hold any actions until Reunion acceptance. 2. In every situations checkout Advertise Response packets; even is Reunion in failure mode or not :return: """ print("Running the peer...") while True: time.sleep(2) temp_array = self.stream.read_in_buf() if not self.reunion_accept: print("Reunion failure") for b in self.stream.read_in_buf(): # print("In main while: ", b) p = self.packet_factory.parse_buffer(b) # print("In for: ", p.get_type()) if p.get_type() == 2: self.handle_packet(p) temp_array.remove(b) # self.stream.send_out_buf_messages(only_register=True) break continue for b in temp_array: # print("In main while: ", b) p = self.packet_factory.parse_buffer(b) self.handle_packet(p) # self.packets.remove(p) self.stream.clear_in_buff() self.handle_user_interface_buffer() # print("Main while before user_interface handler") # self.send_broadcast_packets() self.stream.send_out_buf_messages() def run_reunion_daemon(self): """ In this function we will handle all Reunion actions. Code design suggestions: 1. Check if we are the network root or not; The actions are identical. 2. If it's the root Peer, in every interval check the latest Reunion packet arrival time from every nodes; If time is over for the node turn it off (Maybe you need to remove it from our NetworkGraph). 3. If it's a non-root peer split the actions by considering whether we are waiting for Reunion Hello Back Packet or it's the time to send new Reunion Hello packet. Warnings: 1. If we are root of the network in the situation that want to turn a node off, make sure that you will not advertise the nodes sub-tree in our GraphNode. 2. If we are a non-root Peer, save the time when you have sent your last Reunion Hello packet; You need this time for checking whether the Reunion was failed or not. 3. For choosing time intervals you should wait until Reunion Hello or Reunion Hello Back arrival, pay attention that our NetworkGraph depth will not be bigger than 8. (Do not forget main loop sleep time) 4. Suppose that you are a non-root Peer and Reunion was failed, In this time you should make a new Advertise Request packet and send it through your register_connection to the root; Don't forget to send this packet here, because in Reunion Failure mode our main loop will not work properly and everything will got stock! :return: """ if self._is_root: while True: for n in self.network_graph.nodes: if time.time( ) > n.latest_reunion_time + 36 and n is not self.network_graph.root: print("We have lost a node!", n.address) for child in n.children: # TODO Turn all sub-tree off; not only children. self.network_graph.turn_off_node(child.address) self.network_graph.turn_off_node(n.address) self.network_graph.remove_node(n.address) # TODO Handle this section time.sleep(2) else: while True: if not self.reunion_pending: self.reunion_accept = True time.sleep(4) packet = self.packet_factory.new_reunion_packet( "REQ", self.stream.get_server_address(), [self.stream.get_server_address()]) self.stream.add_message_to_out_buff( self.parent.get_server_address(), packet.get_buf()) self.reunion_pending = True self.reunion_sending_time = time.time() self.flagg = True else: if time.time( ) > self.reunion_sending_time + 38 and self.flagg: print("Ooops, Reunion was failed.") self.reunion_accept = False advertise_packet = self.packet_factory.new_advertise_packet( "REQ", self.stream.get_server_address()) self.stream.add_message_to_out_buff( self.root_address, advertise_packet.get_buf()) self.flagg = False self.stream.send_out_buf_messages(only_register=True) # Reunion failed. # TODO Make sure that parent will completely detach from our clients def send_broadcast_packet(self, broadcast_packet): """ For setting broadcast packets buffer into Nodes out_buff. Warnings: 1. Don't send Message packets through register_connections. :param broadcast_packet: The packet that should be broadcast through network. :type broadcast_packet: Packet :return: """ for n in self.stream.nodes: # print(n.get_standard_server_address()) # print(self.root_address) # if n.get_standard_server_address() != self.root_address: if not n.is_register_connection: self.stream.add_message_to_out_buff(n.get_server_address(), broadcast_packet) def handle_packet(self, packet): """ This function act as a wrapper for other handle_###_packet methods to handle the packet. Code design suggestion: 1.It's better to check packet validation right now; For example: Validation of the packet length. :param packet: The arrived packet that should be handled. :type packet Packet """ if packet.get_length() != len(packet.get_body()): # print("packet.get_length() = ", packet.get_length(), packet.get_body(), packet.get_source_server_ip(),packet.get_source_server_port()) return print('Packet Length is incorrect.') # print("Handling the packet...") if packet.get_version() == 1: if packet.get_type() == 1: self.__handle_register_packet(packet) elif packet.get_type() == 2: self.__handle_advertise_packet(packet) elif packet.get_type() == 3: self.__handle_join_packet(packet) elif packet.get_type() == 4: self.__handle_message_packet(packet) elif packet.get_type() == 5: self.__handle_reunion_packet(packet) else: print('Unexpected type:\t', packet.get_type()) # self.packets.remove(packet) def __check_registered(self, source_address): """ If the Peer is root of the network we need to find that is a node registered or not. :param source_address: Unknown IP/Port address. :type source_address: tuple :return: """ for n in self.registered_nodes: if n.get_address() == source_address: return True return False def __handle_advertise_packet(self, packet): """ For advertising peers in network, It is peer discovery message. Request: We should act as root of the network and reply with a neighbour address in a new Advertise Response packet. Response: When a Advertise Response packet type arrived we should update our parent peer and send a Join packet to the new parent. Code design suggestion: 1. Start the Reunion daemon thread when the first Advertise Response packet received. 2. When an Advertise Response message arrived make a new Join packet immediately for advertised address. Warnings: 1. Don't forget to ignore Advertise Request packets when you are non-root peer. 2. The addresses which still haven't registered to the network can not request any peer discovery message. 3. Maybe it's not the first time that source of the packet send Advertise Request message. This will happen in rare situations like Reunion Failure. Pay attention that don't advertise the address in packet sender sub-tree. 4. When an Advertise Response packet arrived update our Peer parent for sending Reunion Packets. :param packet: Arrived register packet :type packet Packet :return: """ # print("Handling advertisement packet...") if packet.get_body()[0:3] == 'REQ': if not self._is_root: # return print("Register request packet send to a non root node!") return # print("Packet is in Request type") if not self.__check_registered(packet.get_source_server_address()): # print(packet.get_source_server_address(), " trying to advertise before registering.") return # TODO Here we should check that is the node was advertised in past then update our GraphNode neighbor = self.__get_neighbour(packet.get_source_server_address()) # print("Neighbor: \t", neighbor) p = self.packet_factory.new_advertise_packet( type='RES', source_server_address=self.stream.get_server_address(), neighbor=neighbor) server_address = packet.get_source_server_address() node = self.network_graph.find_node(server_address[0], server_address[1]) if node is not None: self.network_graph.turn_on_node(server_address) # # else: # self.network_graph.add_node(node, neighbor.address) # self.network_graph.add_node(server_address[0], server_address[1], neighbor) # print("We want to add this to the node: ", packet.get_source_server_address()) self.network_nodes.append( SemiNode(packet.get_source_server_ip(), packet.get_source_server_port())) self.stream.add_message_to_out_buff( packet.get_source_server_address(), p.get_buf()) # print() elif packet.get_body()[0:3] == 'RES': # print("Packet is in Response type") self.stream.add_node( (packet.get_body()[3:18], packet.get_body()[18:23])) if self.parent: if self.stream.get_node_by_server( self.parent.server_ip, self.parent.server_port) in self.stream.nodes: self.stream.remove_node(self.parent) self.parent = self.stream.get_node_by_server( packet.get_body()[3:18], packet.get_body()[18:23]) addr = self.stream.get_server_address() join_packet = self.packet_factory.new_join_packet(addr) self.stream.add_message_to_out_buff( self.parent.get_server_address(), join_packet.get_buf()) self.reunion_pending = False if not self.reunion_daemon_thread.isAlive(): # print("Reunion thread started") self.reunion_daemon_thread.start() else: raise print('Unexpected Type.') def __handle_register_packet(self, packet): """ For registration a new node to the network at first we should make a Node with stream.add_node for'sender' and save it. Code design suggestion: 1.For checking whether an address is registered since now or not you can use SemiNode object except Node. Warnings: 1. Don't forget to ignore Register Request packets when you are non-root peer. :param packet: Arrived register packet :type packet Packet :return: """ # print("Handling register packet body: ", packet.get_body()) pbody = packet.get_body() if pbody[0:3] == 'REQ': # print("Packet is in Request type") if not self._is_root: raise print('Register request packet send to a root node!') else: if self.__check_registered(packet.get_source_server_address()): print(packet.get_source_server_address(), 'Trying to register again') return res = self.packet_factory.new_register_packet( type='RES', source_server_address=self.stream.get_server_address(), address=self.stream.get_server_address()) self.stream.add_node((packet.get_source_server_ip(), packet.get_source_server_port()), set_register_connection=True) # self.stream.add_client(pbody[3:18], pbody[18:23]) self.registered_nodes.append( SemiNode(packet.get_body()[3:18], packet.get_body()[18:23])) self.stream.add_message_to_out_buff( packet.get_source_server_address(), res.get_buf()) # self.stream.add_message_to_out_buf((pbody[3:18], pbody[18:23]), res) if pbody[0:3] == 'RES': print("Register Response sent") if pbody[3:6] == "ACK": print('ACK! Registered accomplished') else: raise Exception( 'Root did not send ack in the register response packet!') def __check_neighbour(self, address): """ Checks is the address in our neighbours array or not. :param address: Unknown address :type address: tuple :return: Whether is address in our neighbours or not. :rtype: bool """ if address in self.neighbours: return True print(self.parent.get_server_address(), " ", address) if address == self.parent.get_server_address(): return True return False def __handle_message_packet(self, packet): """ Only broadcast message to the other nodes. Warnings: 1. Do not forget to ignore messages from unknown sources. 2. Make sure that you are not sending a message to a register_connection. :param packet: :type packet Packet :return: """ # print("Handling message packet...") # print("The message was just arrived is: ", packet.get_body(), " and source of the packet is: ", # packet.get_source_server_address()) new_packet = self.packet_factory.new_message_packet( packet.get_body(), self.stream.get_server_address()) # for n in self.stream.nodes: # print("From here:\t", n.get_server_address(), " ", n.is_register_connection) if not self.__check_neighbour(packet.get_source_server_address()): # print("The message is from an unknown source.") return for n in self.stream.nodes: if not n.is_register_connection: if n.get_server_address() != packet.get_source_server_address( ): # print("Node considered to send message to: ", n.get_server_address()) self.stream.add_message_to_out_buff( n.get_server_address(), new_packet.get_buf()) print('Message sent to', n.get_server_address) def __handle_reunion_packet(self, packet): """ In this function we should handle Reunion packet was just arrived. Reunion Hello: If you are root Peer you should answer with a new Reunion Hello Back packet. At first extract all addresses in the packet body and append them in descending order to the new packet. You should send the new packet to the first address in arrived packet. If you are a non-root Peer append your IP/Port address to the end of the packet and send it to your parent. Reunion Hello Back: Check that you are the end node or not; If not only remove your IP/Port address and send packet to the next address, otherwise you received your response from root and everything is fine. Warnings: 1. Every time adding or removing an address from packet don't forget to update Entity Number field. 2. If you are the root, update last Reunion Hello arrival packet from the sender node and turn it on. 3. If you are the end node, update your Reunion mode from pending to acceptance. :param packet: :return: """ # print("Handling reunion packet") if packet.get_body()[0:3] == "REQ": # print("Packet is in Request type") if self._is_root: number_of_entity = int(packet.get_body()[3:5]) node_array = [] ip_and_ports = packet.get_body()[5:] sender_ip = ip_and_ports[(number_of_entity - 1) * 20:(number_of_entity - 1) * 20 + 15] sender_port = ip_and_ports[(number_of_entity - 1) * 20 + 15:(number_of_entity - 1) * 20 + 20] for i in range(number_of_entity): ip = ip_and_ports[i * 20:i * 20 + 15] port = ip_and_ports[i * 20 + 15:i * 20 + 20] node_array.insert(0, (ip, port)) self.network_graph.turn_on_node( packet.get_source_server_address()) node = self.network_graph.find_node( packet.get_source_server_ip(), packet.get_source_server_port()) node.latest_reunion_time = time.time() p = self.packet_factory.new_reunion_packet( type='RES', source_address=self.stream.get_server_address(), nodes_array=node_array) self.stream.add_message_to_out_buff((sender_ip, sender_port), p.get_buf()) else: number_of_entity = int(packet.get_body()[3:5]) node_array = [] ip_and_ports = packet.get_body()[5:] for i in range(number_of_entity): ip = ip_and_ports[i * 20:i * 20 + 15] port = ip_and_ports[i * 20 + 15:i * 20 + 20] node_array.append((ip, port)) self_address = self.stream.get_server_address() node_array.append((self_address[0], self_address[1])) p = self.packet_factory.new_reunion_packet( type='REQ', source_address=self.stream.get_server_address(), nodes_array=node_array) parent_address = self.parent.get_server_address() self.stream.add_message_to_out_buff(parent_address, p.get_buf()) elif packet.get_body()[0:3] == 'RES': # print("Packet is in Response type") number_of_entity = int(packet.get_body()[3:5]) # print("Entity number is: ", number_of_entity) node_array = [] ip_and_ports = packet.get_body()[5:] first_ip = ip_and_ports[0:15] first_port = ip_and_ports[15:20] self_address = self.stream.get_server_address() # print("Packet address: ", ip_and_ports[:20], ' address: ', self_address) if first_ip == self_address[0] and first_port == self_address[1]: if number_of_entity == 1: print('Reunion Hello Back Packet Received') self.reunion_pending = False return sender_ip = ip_and_ports[20:35] sender_port = ip_and_ports[35:40] for i in range(1, number_of_entity): ip = ip_and_ports[i * 20:i * 20 + 15] port = ip_and_ports[i * 20 + 15:i * 20 + 20] node_array.append((ip, port)) p = self.packet_factory.new_reunion_packet( type='RES', source_address=self.stream.get_server_address(), nodes_array=node_array) self.stream.add_message_to_out_buff((sender_ip, sender_port), p.get_buf()) else: raise print('Unexpected type') def __handle_join_packet(self, packet): """ When a Join packet received we should add new node to our nodes array. In reality there is a security level that forbid joining every nodes to our network. :param packet: Arrived register packet. :type packet Packet :return: """ print('Join packet sent') self.stream.add_node(packet.get_source_server_address()) self.neighbours.append(packet.get_source_server_address()) pass def __get_neighbour(self, sender): """ Finds the best neighbour for the 'sender' from network_nodes array. This function only will call when you are a root peer. Code design suggestion: 1.Use your NetworkGraph find_live_node to find the best neighbour. :param sender: Sender of the packet :return: The specified neighbor for the sender; The format is like ('192.168.001.001', '05335'). """ return self.network_graph.find_live_node(sender).address
class Peer: DAEMON_THREAD_WAIT_TIME = 4 MAXIMUM_WAIT_TIME = 2 * 2 * 8 + 4 def __init__(self, server_ip, server_port, is_root=False, root_address=None): """ The Peer object constructor. Code design suggestions: 1. Initialise a Stream object for our Peer. 2. Initialise a PacketFactory object. 3. Initialise our UserInterface for interaction with user commandline. 4. Initialise a Thread for handling reunion daemon. Warnings: 1. For root Peer, we need a NetworkGraph object. 2. In root Peer, start reunion daemon as soon as possible. 3. In client Peer, we need to connect to the root of the network, Don't forget to set this connection as a register_connection. :param server_ip: Server IP address for this Peer that should be pass to Stream. :param server_port: Server Port address for this Peer that should be pass to Stream. :param is_root: Specify that is this Peer root or not. :param root_address: Root IP/Port address if we are a client. :type server_ip: str :type server_port: int :type is_root: bool :type root_address: tuple """ self.address = (Node.parse_ip(server_ip), Node.parse_port(str(server_port))) self.root_address = None if root_address is None else Node.parse_address( root_address) self.stream = Stream(server_ip, server_port, root_address) self.packet_factory = PacketFactory() self.ui = UserInterface() self.ui.daemon = True self.is_root = is_root self.parent_address = None self.children = [] self.network_graph = None self.registered_peers = None self.waiting_for_hello_back = False self.last_sent_hello_time = None self.reunion_daemon = threading.Thread(target=self.run_reunion_daemon) self.reunion_daemon.daemon = True if is_root: root_graph_node = GraphNode(self.address) root_graph_node.depth = 0 self.network_graph = NetworkGraph(root_graph_node) self.registered_peers = dict() self.last_received_hello_times = dict() self.reunion_daemon.start() elif root_address is not None: self.stream.add_node(root_address, set_register_connection=True) self.start_user_interface() print('Peer initialized.') def start_user_interface(self): """ For starting UserInterface thread. :return: """ self.ui.start() def handle_user_interface_buffer(self): """ In every interval, we should parse user command that buffered from our UserInterface. All of the valid commands a re listed below: 1. Register: With this command, the client send a Register Request packet to the root of the network. 2. Advertise: Send an Advertise Request to the root of the network for finding first hope. 3. SendMessage: The following string will be added to a new Message packet and broadcast through the network. Warnings: 1. Ignore irregular commands from the user. 2. Don't forget to clear our UserInterface buffer. :return: """ i = 0 ui_buffer_snapshot_size = len(self.ui.buffer) while i < ui_buffer_snapshot_size: cmd = self.ui.buffer[i] if cmd == 'sendMessage': msg = self.ui.buffer[i + 1] broadcast_packet = self.packet_factory.new_message_packet( msg, self.address) self.send_broadcast_packet(broadcast_packet) i += 2 if self.is_root: continue if cmd == 'register': register_packet = self.packet_factory.new_register_packet( Packet.BODY_REQ, self.address, self.address) print(register_packet.get_buf()) self.stream.add_message_to_out_buff(self.root_address, register_packet.get_buf(), is_register_node=True) # print('register packet created') elif cmd == 'advertise': advertise_packet = self.packet_factory.new_advertise_packet( Packet.BODY_REQ, self.address) print('sending', advertise_packet.get_buf()) self.stream.add_message_to_out_buff(self.root_address, advertise_packet.get_buf(), is_register_node=True) elif cmd == 'suicide': exit(1) i += 1 self.ui.buffer = self.ui.buffer[ui_buffer_snapshot_size:] def run(self): """ The main loop of the program. Code design suggestions: 1. Parse server in_buf of the stream. 2. Handle all packets were received from our Stream server. 3. Parse user_interface_buffer to make message packets. 4. Send packets stored in nodes buffer of our Stream object. 5. ** sleep the current thread for 2 seconds ** Warnings: 1. At first check reunion daemon condition; Maybe we have a problem in this time and so we should hold any actions until Reunion acceptance. 2. In every situation checkout Advertise Response packets; even is Reunion in failure mode or not :return: """ # TODO warnings handling while True: self.handle_user_interface_buffer() stream_in_buff_snapshot = self.stream.read_in_buf() snapshot_size = len(stream_in_buff_snapshot) if snapshot_size != 0: print(stream_in_buff_snapshot) for message in stream_in_buff_snapshot: packet = self.packet_factory.parse_buffer(message) # print('packet:', packet.get_buf()) self.handle_packet(packet) self.stream.clear_in_buff(snapshot_size) self.stream.send_out_buf_messages() time.sleep(2) def run_reunion_daemon(self): """ In this function, we will handle all Reunion actions. Code design suggestions: 1. Check if we are the network root or not; The actions are identical. 2. If it's the root Peer, in every interval check the latest Reunion packet arrival time from every node; If time is over for the node turn it off (Maybe you need to remove it from our NetworkGraph). 3. If it's a non-root peer split the actions by considering whether we are waiting for Reunion Hello Back Packet or it's the time to send new Reunion Hello packet. Warnings: 1. If we are the root of the network in the situation that we want to turn a node off, make sure that you will not advertise the nodes sub-tree in our GraphNode. 2. If we are a non-root Peer, save the time when you have sent your last Reunion Hello packet; You need this time for checking whether the Reunion was failed or not. 3. For choosing time intervals you should wait until Reunion Hello or Reunion Hello Back arrival, pay attention that our NetworkGraph depth will not be bigger than 8. (Do not forget main loop sleep time) 4. Suppose that you are a non-root Peer and Reunion was failed, In this time you should make a new Advertise Request packet and send it through your register_connection to the root; Don't forget to send this packet here, because in the Reunion Failure mode our main loop will not work properly and everything will be got stock! :return: """ while True: if self.is_root: for peer_address, last_time in list( self.last_received_hello_times.items()): elapsed_time = time.time() - last_time if elapsed_time > self.MAXIMUM_WAIT_TIME: self.network_graph.remove_node(peer_address) elif self.parent_address is not None: if not self.waiting_for_hello_back: hello_packet = self.packet_factory.new_reunion_packet( Packet.BODY_REQ, self.address, [self.address]) self.stream.add_message_to_out_buff( self.parent_address, hello_packet.get_buf()) self.last_sent_hello_time = time.time() self.waiting_for_hello_back = True else: elapsed_time = time.time() - self.last_sent_hello_time if elapsed_time > self.MAXIMUM_WAIT_TIME: advertise_packet = self.packet_factory.new_advertise_packet( Packet.BODY_REQ, self.address, self.address) self.stream.add_message_to_out_buff( self.root_address, advertise_packet.get_buf(), is_register_node=True) self.stream.remove_node( self.stream.get_node_by_server( self.parent_address[0], self.parent_address[1])) self.parent_address = None for child in self.children: self.stream.remove_node( self.stream.get_node_by_server( child[0], child[1])) self.waiting_for_hello_back = False time.sleep(self.DAEMON_THREAD_WAIT_TIME) def send_broadcast_packet(self, broadcast_packet): """ For setting broadcast packets buffer into Nodes out_buff. Warnings: 1. Don't send Message packets through register_connections. :param broadcast_packet: The packet that should be broadcast through the network. :type broadcast_packet: Packet :return: """ print('Sending new broadcast message: ', broadcast_packet.get_body()) for child in self.children: self.stream.add_message_to_out_buff(child, broadcast_packet.get_buf()) if not self.is_root: self.stream.add_message_to_out_buff(self.parent_address, broadcast_packet.get_buf()) def handle_packet(self, packet): """ This function act as a wrapper for other handle_###_packet methods to handle the packet. Code design suggestion: 1. It's better to check packet validation right now; For example Validation of the packet length. :param packet: The arrived packet that should be handled. :type packet Packet """ packet_type = packet.get_type() if packet.get_version() != Packet.VERSION: print('Error in packet: incorrect version') return if packet_type not in [ Packet.REGISTER, Packet.ADVERTISE, Packet.JOIN, Packet.MESSAGE, Packet.REUNION ]: print('Error in packet: Unknown type') return if packet.get_length() != len(packet.get_body()): print('Error in packet: inconsistent body length') print('body length in header:', packet.get_length()) print('real body length:', len(packet.get_body())) return print(time.time(), end=' ') if packet_type == Packet.REGISTER: print('register packet received') self.__handle_register_packet(packet) elif packet_type == Packet.ADVERTISE: print('advertise packet received') self.__handle_advertise_packet(packet) elif packet_type == Packet.JOIN: print('join packet received') self.__handle_join_packet(packet) elif packet_type == Packet.MESSAGE: print('message packet received') self.__handle_message_packet(packet) elif packet_type == Packet.REUNION: print('reunion packet received') self.__handle_reunion_packet(packet) else: print('unknown packet type') def __check_registered(self, source_address): """ If the Peer is the root of the network we need to find that is a node registered or not. :param source_address: Unknown IP/Port address. :type source_address: tuple :return: """ if self.registered_peers is not None: if str( (Node.parse_ip(source_address[0]), Node.parse_port(source_address[1]))) in self.registered_peers: return True return False def __handle_advertise_packet(self, packet): """ For advertising peers in the network, It is peer discovery message. Request: We should act as the root of the network and reply with a neighbour address in a new Advertise Response packet. Response: When an Advertise Response packet type arrived we should update our parent peer and send a Join packet to the new parent. Code design suggestion: 1. Start the Reunion daemon thread when the first Advertise Response packet received. 2. When an Advertise Response message arrived, make a new Join packet immediately for the advertised address. Warnings: 1. Don't forget to ignore Advertise Request packets when you are a non-root peer. 2. The addresses which still haven't registered to the network can not request any peer discovery message. 3. Maybe it's not the first time that the source of the packet sends Advertise Request message. This will happen in rare situations like Reunion Failure. Pay attention, don't advertise the address to the packet sender sub-tree. 4. When an Advertise Response packet arrived update our Peer parent for sending Reunion Packets. :param packet: Arrived advertise packet :type packet Packet :return: """ body_str = packet.get_body() if self.is_root: if len(body_str) != 3 or body_str != Packet.BODY_REQ: return if not self.__check_registered(packet.get_source_server_address()): print( 'Peer that has sent request advertise has not registered before.' ) return neighbour_address = self.__get_neighbour( packet.get_source_server_address()) print('neighbour for', packet.get_source_server_address(), 'is', neighbour_address) response_packet = self.packet_factory.new_advertise_packet( Packet.BODY_RES, packet.get_source_server_address(), Node.parse_address(neighbour_address)) message = response_packet.get_buf() self.stream.add_message_to_out_buff( packet.get_source_server_address(), message, is_register_node=True) self.network_graph.add_node(packet.get_source_server_ip(), packet.get_source_server_port(), neighbour_address) self.network_graph.turn_on_node(packet.get_source_server_address()) self.last_received_hello_times[ packet.get_source_server_address()] = time.time() else: if len(body_str) != 23 or body_str[:3] != Packet.BODY_RES: return parent_ip = body_str[3:18] parent_port = body_str[18:23] self.parent_address = (parent_ip, parent_port) self.stream.add_node(self.parent_address) join_packet = self.packet_factory.new_join_packet(self.address) message = join_packet.get_buf() self.stream.add_message_to_out_buff(self.parent_address, message) self.waiting_for_hello_back = False if not self.reunion_daemon.is_alive(): self.reunion_daemon.start() def __handle_register_packet(self, packet): """ For registration a new node to the network at first we should make a Node with stream.add_node for'sender' and save it. Code design suggestion: 1.For checking whether an address is registered since now or not you can use SemiNode object except Node. Warnings: 1. Don't forget to ignore Register Request packets when you are a non-root peer. :param packet: Arrived register packet :type packet Packet :return: """ if not self.is_root: return body_str = packet.get_body() if len(body_str) != 23: print('register request packet length is not 23') body_type = body_str[:3] if body_type == Packet.BODY_REQ: source_ip = body_str[3:18] source_port = body_str[18:23] source_address = (source_ip, source_port) if self.__check_registered(source_address): print('peer is already been registered!') return self.registered_peers[str(source_address)] = True self.stream.add_node(source_address, set_register_connection=True) response_packet = self.packet_factory.new_register_packet( Packet.BODY_RES, source_address) message = response_packet.get_buf() self.stream.add_message_to_out_buff(source_address, message, is_register_node=True) print('registered peers', self.registered_peers) else: print('register body type is not REQ') def __check_neighbour(self, address): """ It checks if the address in our neighbours array or not. :param address: Unknown address :type address: tuple :return: Whether is address in our neighbours or not. :rtype: bool """ return address == self.parent_address or address in self.children def __handle_message_packet(self, packet): """ Only broadcast message to the other nodes. Warnings: 1. Do not forget to ignore messages from unknown sources. 2. Make sure that you are not sending a message to a register_connection. :param packet: Arrived message packet :type packet Packet :return: """ if not self.__check_neighbour(packet.get_source_server_address()): print( 'received message from unknown source, not found in neighbours' ) return if self.stream.get_node_by_server(packet.get_source_server_ip(), packet.get_source_server_port() ) not in self.stream.nodes.values(): print('source not found in stream nodes') return message = packet.get_body() print('New message received from', packet.get_source_server_address(), ':', message) message_packet = self.packet_factory.new_message_packet( message, self.address) for child in self.children: if child != packet.get_source_server_address(): self.stream.add_message_to_out_buff(child, message_packet.get_buf()) if not self.is_root and self.parent_address != packet.get_source_server_address( ): self.stream.add_message_to_out_buff(self.parent_address, message_packet.get_buf()) def __handle_reunion_packet(self, packet): """ In this function we should handle Reunion packet was just arrived. Reunion Hello: If you are root Peer you should answer with a new Reunion Hello Back packet. At first extract all addresses in the packet body and append them in descending order to the new packet. You should send the new packet to the first address in the arrived packet. If you are a non-root Peer append your IP/Port address to the end of the packet and send it to your parent. Reunion Hello Back: Check that you are the end node or not; If not only remove your IP/Port address and send the packet to the next address, otherwise you received your response from the root and everything is fine. Warnings: 1. Every time adding or removing an address from packet don't forget to update Entity Number field. 2. If you are the root, update last Reunion Hello arrival packet from the sender node and turn it on. 3. If you are the end node, update your Reunion mode from pending to acceptance. :param packet: Arrived reunion packet :return: """ body_str = packet.get_body() num_of_entries = int(body_str[3:5]) if num_of_entries != len(body_str[5:]) // 20: return path_peers_str = body_str[5:] path_peers = [] for i in range(0, len(path_peers_str), 20): ip = path_peers_str[i:i + 15] port = path_peers_str[i + 15:i + 20] path_peers.append((ip, port)) if self.is_root: if body_str[0:3] != Packet.BODY_REQ: return self.last_received_hello_times[ packet.get_source_server_address()] = time.time() path_peers.reverse() hello_back_packet = self.packet_factory.new_reunion_packet( Packet.BODY_RES, self.address, path_peers) message = hello_back_packet.get_buf() self.stream.add_message_to_out_buff(path_peers[0], message) else: if body_str[0:3] == Packet.BODY_REQ: path_peers.append(self.address) hello_packet = self.packet_factory.new_reunion_packet( Packet.BODY_REQ, packet.get_source_server_address(), path_peers) message = hello_packet.get_buf() self.stream.add_message_to_out_buff(self.parent_address, message) elif body_str[0:3] == Packet.BODY_RES: if path_peers[0] != self.address: return if len(path_peers) == 1 and self.waiting_for_hello_back: self.waiting_for_hello_back = False return path_peers = path_peers[1:] hello_back_packet = self.packet_factory.new_reunion_packet( Packet.BODY_RES, packet.get_source_server_address(), path_peers) message = hello_back_packet.get_buf() self.stream.add_message_to_out_buff(path_peers[0], message) def __handle_join_packet(self, packet): """ When a Join packet received we should add a new node to our nodes array. In reality, there is a security level that forbids joining every node to our network. :param packet: Arrived register packet. :type packet Packet :return: """ body_str = packet.get_body() if len(body_str) != 4 or body_str != Packet.BODY_JOIN: return source_address = packet.get_source_server_address() self.stream.add_node(source_address) self.children.append(source_address) # if len(self.children) > 2: # raise Exception('protection forgotten. excessive child!') def __get_neighbour(self, sender): """ Finds the best neighbour for the 'sender' from the network_nodes array. This function only will call when you are a root peer. Code design suggestion: 1. Use your NetworkGraph find_live_node to find the best neighbour. :param sender: Sender of the packet :return: The specified neighbour for the sender; The format is like ('192.168.001.001', '05335'). """ return self.network_graph.find_live_node(sender).address
class Peer: def __init__(self, server_ip, server_port, is_root=False, root_address=None): """ The Peer object constructor. Code design suggestions: 1. Initialise a Stream object for our Peer. --- done 2. Initialise a PacketFactory object. ----- done 3. Initialise our UserInterface for interaction with user commandline. ----- done 4. Initialise a Thread for handling reunion daemon. ------- done Warnings: 1. For root Peer, we need a NetworkGraph object. ----- done 2. In root Peer, start reunion daemon as soon as possible. ------ done 3. In client Peer, we need to connect to the root of the network, Don't forget to set this connection as a register_connection. ------ done :param server_ip: Server IP address for this Peer that should be pass to Stream. :param server_port: Server Port address for this Peer that should be pass to Stream. :param is_root: Specify that is this Peer root or not. :param root_address: Root IP/Port address if we are a client. :type server_ip: str :type server_port: int :type is_root: bool :type root_address: tuple """ if root_address: root_address = (root_address[0], str(root_address[1]).zfill(5)) server_port = str(server_port).zfill(5) self.is_root = is_root self.is_client_connected = False self.client_predecessor_address = tuple() self.successors_address = [] self.client_is_waiting_for_helloback = False self.register_node = None self.client_reunion_timeout = False # TODO increase self.client_timeout_threshold = 10 self.root_timeout_threshold = 10 self.client_last_hello_time = 0 self.nodes_for_root = {} # {(address) : last_time_hello_came} self.address = (server_ip, server_port) self.stream = Stream(server_ip, server_port) self.user_interface = UserInterface() if self.is_root: graph_node_root = GraphNode(self.address) self.network_graph = NetworkGraph(graph_node_root) reunion_thread = threading.Thread(target=self.run_reunion_daemon) reunion_thread.start() else: self.root_address = root_address print("Set root address! " + str(self.root_address)) self.stream.add_node(self.root_address, True) def start_user_interface(self): """ For starting UserInterface thread. :return: """ print('Starting User Interface') self.user_interface.run() def handle_user_interface_buffer(self): """ In every interval, we should parse user command that buffered from our UserInterface. All of the valid commands are listed below: 1. Register: With this command, the client send a Register Request packet to the root of the network. 2. Advertise: Send an Advertise Request to the root of the network for finding first hope. 3. SendMessage: The following string will be added to a new Message packet and broadcast through the network. Warnings: 1. Ignore irregular commands from the user. 2. Don't forget to clear our UserInterface buffer. :return: """ for message in self.user_interface.buffer: print('handling : ' + message) if message == 'Register': reg_packet = PacketFactory.new_register_packet( "REQ", self.address, self.address) self.send_packet(reg_packet, self.root_address) self.stream.send_out_buf_messages(only_register=True) elif message == 'Advertise': advertise_packet = PacketFactory.new_advertise_packet( type="REQ", source_server_address=self.address) self.send_advertise_packet(advertise_packet) self.stream.send_out_buf_messages(only_register=True) elif message.startswith('SendMessage'): to_be_sent = message.split()[1] message_packet = PacketFactory.new_message_packet( message=to_be_sent, source_server_address=self.address) self.send_broadcast_packet(message_packet) else: continue self.user_interface.buffer.clear() def run(self): """ The main loop of the program. Code design suggestions: 1. Parse server in_buf of the stream. ---- done 2. Handle all packets were received from our Stream server. ---- done 3. Parse user_interface_buffer to make message packets. ---- done 4. Send packets stored in nodes buffer of our Stream object. ---- done 5. ** sleep the current thread for 2 seconds ** -------- done Warnings: 1. At first check reunion daemon condition; Maybe we have a problem in this time and so we should hold any actions until Reunion acceptance. ------- done 2. In every situation checkout Advertise Response packets; even if Reunion in failure mode or not ------- done :return: """ while True: if not self.is_root: if self.is_client_connected: input_buffer = self.stream.read_in_buf() for buf in input_buffer: packet = Packet(buf) print('gonna print a received packet! ') print(packet.__dict__) self.handle_packet(packet) self.stream.clear_in_buff( ) # TODO buffer messages that use unvailable addreses unavailable_addreses = self.stream.send_out_buf_messages() if self.root_address in unavailable_addreses: self.is_client_connected = False for add in unavailable_addreses: if add in self.successors_address: self.successors_address.remove(add) else: input_buffer = self.stream.read_in_buf() for buf in input_buffer: packet = Packet(buf) if packet.type == PacketType.ADVERTISE: self.__handle_advertise_packet(packet) self.stream.clear_in_buff() self.handle_user_interface_buffer() else: input_buffer = self.stream.read_in_buf() for buf in input_buffer: packet = Packet(buf) print('gonna print a received packet! ') print(packet.__dict__) self.handle_packet(packet) self.stream.clear_in_buff() self.handle_user_interface_buffer() unavailable_addreses = self.stream.send_out_buf_messages() for add in unavailable_addreses: if add in self.successors_address: self.successors_address.remove(add) time.sleep(2) def run_reunion_daemon(self): """ In this function, we will handle all Reunion actions. Code design suggestions: 1. Check if we are the network root or not; The actions are not identical. 2. If it's the root Peer, in every interval check the latest Reunion packet arrival time from every node; If time is over for the node turn it off (Maybe you need to remove it from our NetworkGraph). 3. If it's a non-root peer split the actions by considering whether we are waiting for Reunion Hello Back Packet or it's the time to send new Reunion Hello packet. Warnings: 1. If we are the root of the network in the situation that we want to turn a node off, make sure that you will not advertise the nodes sub-tree in our GraphNode. 2. If we are a non-root Peer, save the time when you have sent your last Reunion Hello packet; You need this time for checking whether the Reunion was failed or not. 3. For choosing time intervals you should wait until Reunion Hello or Reunion Hello Back arrival, pay attention that our NetworkGraph depth will not be bigger than 8. (Do not forget main loop sleep time) 4. Suppose that you are a non-root Peer and Reunion was failed, In this time you should make a new Advertise Request packet and send it through your register_connection to the root; Don't forget to send this packet here, because in the Reunion Failure mode our main loop will not work properly and everything will be got stock! --- done :return: """ while True: if self.is_root: now = time.time() to_be_deleted = [] for peer_address, latest_reunion_msg in self.nodes_for_root.items( ): passed_time = now - latest_reunion_msg if passed_time > self.root_timeout_threshold: print( "I've waited more than enough! where is my hello from " + str(peer_address)) to_be_deleted.append(peer_address) self.network_graph.turn_off_node(peer_address) for peer_address in to_be_deleted: self.nodes_for_root.pop(peer_address) self.network_graph.remove_node(peer_address) if peer_address in self.successors_address: self.successors_address.remove(peer_address) else: if self.client_predecessor_address: if not self.client_is_waiting_for_helloback: reunion_packet = PacketFactory.new_reunion_packet( "REQ", self.address, [self.address]) print("created hello packet! gonna send it! ") self.forward_hello(packet=reunion_packet, is_mine=True) self.client_last_hello_time = time.time() self.client_is_waiting_for_helloback = True elif time.time( ) - self.client_last_hello_time >= self.client_timeout_threshold: print( "I've waited more than enough! where is my helloback " ) self.client_is_waiting_for_helloback = False self.is_client_connected = False self.client_predecessor_address = None adv_pckt = PacketFactory.new_advertise_packet( "REQ", self.address) self.send_advertise_packet(adv_pckt) time.sleep(4) def send_packet(self, packet, address): packet = self.change_header(packet) self.stream.add_message_to_out_buff(address, packet) def send_broadcast_packet(self, broadcast_packet): """ For setting broadcast packets buffer into Nodes out_buff. Warnings: 1. Don't send Message packets through register_connections. :param broadcast_packet: The packet that should be broadcast through the network. :type broadcast_packet: Packet :return: """ sender_address = broadcast_packet.get_source_server_address() broadcast_packet = self.change_header(broadcast_packet) if self.is_root and broadcast_packet.type != PacketType.REUNION: for address in self.successors_address: if address != sender_address: self.stream.add_message_to_out_buff( address, broadcast_packet) elif self.is_root and broadcast_packet.type == PacketType.REUNION: dest_addr = broadcast_packet.body[-20:] dest_addr = (dest_addr[:15], dest_addr[15:]) if dest_addr in self.successors_address: print('Finally sending hello back to ' + str(dest_addr)) self.stream.add_message_to_out_buff(dest_addr, broadcast_packet) elif broadcast_packet.type == PacketType.MESSAGE: all_addreses = [self.client_predecessor_address ] + self.successors_address for address in all_addreses: if address != sender_address: print('Going to broadcast a Message! ' + ' with body ' + str(broadcast_packet.get_body())) self.stream.add_message_to_out_buff( address, broadcast_packet) else: return def handle_packet(self, packet): """ This function act as a wrapper for other handle_###_packet methods to handle the packet. Code design suggestion: 1. It's better to check packet validation right now; For example Validation of the packet length. :param packet: The arrived packet that should be handled. :type packet Packet """ if packet.type == PacketType.REGISTER: self.__handle_register_packet(packet) elif packet.type == PacketType.MESSAGE: self.__handle_message_packet(packet) elif packet.type == PacketType.ADVERTISE: self.__handle_advertise_packet(packet) elif packet.type == PacketType.JOIN: self.__handle_join_packet(packet) elif packet.type == PacketType.REUNION: self.__handle_reunion_packet(packet) else: return def __check_registered(self, source_address): """ If the Peer is the root of the network, we need to find that is a node registered or not. :param source_address: Unknown IP/Port address. :type source_address: tuple :return: """ if not self.is_root: return False node = self.network_graph.find_node(source_address[0], source_address[1]) return not node is None def __handle_advertise_packet(self, packet): """ For advertising peers in the network, It is peer discovery message. Request: We should act as the root of the network and reply with a neighbour address in a new Advertise Response packet. Response: When an Advertise Response packet type arrived we should update our parent peer and send a Join packet to the new parent. Code design suggestion: 1. Start the Reunion daemon thread when the first Advertise Response packet received. 2. When an Advertise Response message arrived, make a new Join packet immediately for the advertised address. Warnings: 1. Don't forget to ignore Advertise Request packets when you are a non-root peer. 2. The addresses which still haven't registered to the network can not request any peer discovery message. 3. Maybe it's not the first time that the source of the packet sends Advertise Request message. This will happen in rare situations like Reunion Failure. Pay attention, don't advertise the address to the packet sender sub-tree. 4. When an Advertise Response packet arrived update our Peer parent for sending Reunion Packets. :param packet: Arrived register packet :type packet Packet :return: """ if self.is_root: sender_address = packet.get_source_server_address() neighbour = self.__get_neighbour(sender_address) self.network_graph.add_node(sender_address[0], sender_address[1], neighbour.address) print("Someone has requested a neighbour") print( f'gave {neighbour.address} to {sender_address} as a neighbour') if self.__check_registered( sender_address) and neighbour is not None: adv_packet = PacketFactory.new_advertise_packet( "RES", self.address, neighbour=neighbour.address) self.send_packet(adv_packet, sender_address) elif packet.body.startswith('RES'): reunion_thread = threading.Thread(target=self.run_reunion_daemon) reunion_thread.start() join_pckt = PacketFactory.new_join_packet(self.address) self.client_predecessor_address = (packet.body[-20:-5], packet.body[-5:]) print("I've found a father! " + str(self.client_predecessor_address)) self.stream.add_node(self.client_predecessor_address) self.send_packet(join_pckt, self.client_predecessor_address) self.is_client_connected = True else: return def __handle_register_packet(self, packet): """ For registration a new node to the network at first we should make a Node with stream.add_node for'sender' and save it. Code design suggestion: 1.For checking whether an address is registered since now or not you can use SemiNode object except Node. Warnings: 1. Don't forget to ignore Register Request packets when you are a non-root peer. :param packet: Arrived register packet :type packet Packet :return: """ if not self.is_root: return else: sender = packet.get_source_server_address() if sender not in self.nodes_for_root: self.stream.add_node(sender) self.nodes_for_root.update({sender: time.time()}) else: return def __handle_message_packet(self, packet): """ Only broadcast message to the other nodes. Warnings: 1. Do not forget to ignore messages from unknown sources. 2. Make sure that you are not sending a message to a register_connection. :param packet: Arrived message packet :type packet Packet :return: """ self.send_broadcast_packet(packet) def __handle_reunion_packet(self, packet): """ In this function we should handle Reunion packet was just arrived. Reunion Hello: If you are root Peer you should answer with a new Reunion Hello Back packet. At first extract all addresses in the packet body and append them in descending order to the new packet. You should send the new packet to the first address in the arrived packet. If you are a non-root Peer append your IP/Port address to the end of the packet and send it to your parent. Reunion Hello Back: Check that you are the end node or not; If not only remove your IP/Port address and send the packet to the next address, otherwise you received your response from the root and everything is fine. Warnings: 1. Every time adding or removing an address from packet don't forget to update Entity Number field. 2. If you are the root, update last Reunion Hello arrival packet from the sender node and turn it on. 3. If you are the end node, update your Reunion mode from pending to acceptance. :param packet: Arrived reunion packet :return: """ if self.is_root: sender_address = packet.get_first_address_hello_packet() print('Hello from ' + str(sender_address)) self.nodes_for_root[sender_address] = time.time() self.network_graph.turn_on_node(sender_address) self.send_helloback(packet) else: if packet.is_reunion_hello(): print('Forwarding Hello Packet from ' + str(packet.get_first_address_hello_packet())) self.forward_hello(packet) else: if not self.helloback_is_mine(packet): self.forward_helloback(packet) else: print('I received hello back!') self.client_is_waiting_for_helloback = False def __handle_join_packet(self, packet): """ When a Join packet received we should add a new node to our nodes array. In reality, there is a security level that forbids joining every node to our network. :param packet: Arrived register packet. :type packet Packet :return: """ address = packet.get_source_server_address() print(f'received join from {address}') if address not in self.successors_address and len( self.successors_address) < 2: self.successors_address.append(address) self.stream.add_node(address) def __get_neighbour(self, sender): """ Finds the best neighbour for the 'sender' from the network_nodes array. This function only will call when you are a root peer. Code design suggestion: 1. Use your NetworkGraph find_live_node to find the best neighbour. :param sender: Sender of the packet :return: The specified neighbour for the sender; The format is like ('192.168.001.001', '05335'). """ return self.network_graph.find_live_node(sender) def send_helloback(self, packet): if not self.is_root: return print('Sending Hello back') packet.body = 'RES' + packet.body[3:] packet.source_ip = self.address[0] packet.source_port = self.address[1] packet = Packet(packet.get_buf()) self.send_broadcast_packet(packet) def send_advertise_packet(self, advertise_packet): print('Sending advertise packet!') self.send_packet(advertise_packet, self.root_address) self.stream.send_out_buf_messages(only_register=True) def __check_neighbour(self, address): """ It checks if the address is in our neighbours array or not. :param address: Unknown address :type address: tuple :return: Whether is address in our neighbours or not. :rtype: bool """ return address == self.client_predecessor_address or address in self.successors_address def helloback_is_mine(self, packet): first_address = packet.get_first_address_hello_packet() return first_address == self.address def change_header(self, packet): packet.source_ip, packet.source_port = self.address[0], self.address[1] packet = Packet(packet.get_buf()) return packet def forward_hello(self, packet, is_mine=False): if is_mine: self.stream.add_message_to_out_buff( self.client_predecessor_address, packet) else: print('Forwarding a Hello that is not mine') packet = self.change_header(packet) packet.body += str(self.address[0]) + str(self.address[1]) new_number_of_elements = int(packet.body[3:5]) + 1 packet.body = 'REQ' + str(new_number_of_elements).zfill( 2) + packet.body[5:] packet = Packet(packet.get_buf()) self.stream.add_message_to_out_buff( self.client_predecessor_address, packet) def forward_helloback(self, packet): packet = self.change_header(packet) packet.body = packet.body[:-20] number_of_entries = str(int(packet.body[3:5]) - 1).zfill(2) packet.body = 'RES' + number_of_entries + packet.body[5:] packet = Packet(packet.get_buf()) fw_address = packet.body[-20:] fw_address = (fw_address[:15], fw_address[15:]) if fw_address in self.successors_address: self.stream.add_message_to_out_buff(fw_address, packet) else: return
class Peer: def __init__(self, server_ip, server_port, is_root=False, root_address=None): """ The Peer object constructor. Code design suggestions: 1. Initialise a Stream object for our Peer. 2. Initialise a PacketFactory object. 3. Initialise our UserInterface for interaction with user commandline. 4. Initialise a Thread for handling reunion daemon. Warnings: 1. For root Peer, we need a NetworkGraph object. 2. In root Peer, start reunion daemon as soon as possible. 3. In client Peer, we need to connect to the root of the network, Don't forget to set this connection as a register_connection. :param server_ip: Server IP address for this Peer that should be pass to Stream. :param server_port: Server Port address for this Peer that should be pass to Stream. :param is_root: Specify that is this Peer root or not. :param root_address: Root IP/Port address if we are a client. :type server_ip: str :type server_port: int :type is_root: bool :type root_address: tuple """ # self.root_address = (SemiNode.parse_ip(root_address[0]),SemiNode.parse_port(root_address[1])) self.root_address = root_address self.stream = Stream(server_ip, server_port) self.packet_factory = PacketFactory() self.user_interfarce = UserInterface() self.server_ip = SemiNode.parse_ip(server_ip) self.server_port = SemiNode.parse_port(str(server_port)) self.is_root = is_root self.flag = False # self.root_address = (SemiNode.parse_ip(root_address[0]), SemiNode.parse_port(root_address[1])) self.neighbours = [] if self.is_root: print("from root in init") self.root_node = GraphNode(self.stream.get_server_address()) self.network_graph = NetworkGraph(self.root_node) self.reunions_arrival_time = dict() else: print("from peer in init") self.stream.add_node(root_address, set_register_connection=True) # self.graph_node = GraphNode((server_ip, server_port)) self.reunion_mode = None self.last_reunion_sent_time = None self.t = threading.Thread(target=self.run_reunion_daemon) def start_user_interface(self): """ For starting UserInterface thread. :return: """ self.user_interfarce.start() t = threading.Thread(target=self.handle_user_interface_buffer) t.start() def handle_user_interface_buffer(self): """ In every interval, we should parse user command that buffered from our UserInterface. All of the valid commands are listed below: 1. Register: With this command, the client send a Register Request packet to the root of the network. 2. Advertise: Send an Advertise Request to the root of the network for finding first hope. 3. SendMessage: The following string will be added to a new Message packet and broadcast through the network. Warnings: 1. Ignore irregular commands from the user. 2. Don't forget to clear our UserInterface buffer. :return: """ while 1: if "".join(self.user_interfarce.buffer) == 'Register': print("Register") register_packet = self.packet_factory.new_register_packet( "REQ", self.stream.get_server_address(), self.root_address) self.stream.add_message_to_out_buff(self.root_address, register_packet.get_buf()) self.stream.print_out_buffs() elif "".join(self.user_interfarce.buffer) == 'Advertise': print("Advertise") advertise_packet = self.packet_factory.new_advertise_packet( "REQ", self.stream.get_server_address()) self.stream.add_message_to_out_buff(self.root_address, advertise_packet.get_buf()) self.stream.print_out_buffs() elif "".join(self.user_interfarce.buffer)[:11] == 'SendMessage': print("SendMessage") print("".join(self.user_interfarce.buffer)[11:]) message_packet = self.packet_factory.new_message_packet( "".join(self.user_interfarce.buffer)[11:], self.stream.get_server_address()) print(message_packet.get_buf()) self.send_broadcast_packet(message_packet) else: if len(self.user_interfarce.buffer) != 0: print("command not supported or empty") self.user_interfarce.buffer.clear() time.sleep(0.5) def run(self): """ The main loop of the program. Code design suggestions: 1. Parse server in_buf of the stream. 2. Handle all packets were received from our Stream server. 3. Parse user_interface_buffer to make message packets. 4. Send packets stored in nodes buffer of our Stream object. 5. ** sleep the current thread for 2 seconds ** Warnings: 1. At first check reunion daemon condition; Maybe we have a problem in this time and so we should hold any actions until Reunion acceptance. 2. In every situation checkout Advertise Response packets; even is Reunion in failure mode or not :return: """ while True: if self.is_root or ( not self.is_root and not (self.reunion_mode == "pending" and datetime.now() - self.last_reunion_sent_time > timedelta(seconds=4))): for buffer in self.stream.read_in_buf(): packet = self.packet_factory.parse_buffer(buffer) self.handle_packet(packet) self.stream.clear_in_buff() # TODO: user interface buffer parse if not self.flag: self.start_user_interface() self.flag = True # print(self.stream._server_in_buf) # print(self.stream.print_out_buffs()) print(self.stream.send_out_buf_messages()) elif not self.is_root and self.reunion_mode == "pending" and datetime.now( ) - self.last_reunion_sent_time > timedelta(seconds=4): for buffer in self.stream.read_in_buf(): packet = self.packet_factory.parse_buffer(buffer) if packet.get_type() == 2 and packet.get_res_or_req( ) == "RES": self.__handle_advertise_packet(packet) time.sleep(5) pass def run_reunion_daemon(self): """ In this function, we will handle all Reunion actions. Code design suggestions: 1. Check if we are the network root or not; The actions are identical. 2. If it's the root Peer, in every interval check the latest Reunion packet arrival time from every node; If time is over for the node turn it off (Maybe you need to remove it from our NetworkGraph). 3. If it's a non-root peer split the actions by considering whether we are waiting for Reunion Hello Back Packet or it's the time to send new Reunion Hello packet. Warnings: 1. If we are the root of the network in the situation that we want to turn a node off, make sure that you will not advertise the nodes sub-tree in our GraphNode. 2. If we are a non-root Peer, save the time when you have sent your last Reunion Hello packet; You need this time for checking whether the Reunion was failed or not. 3. For choosing time intervals you should wait until Reunion Hello or Reunion Hello Back arrival, pay attention that our NetworkGraph depth will not be bigger than 8. (Do not forget main loop sleep time) 4. Suppose that you are a non-root Peer and Reunion was failed, In this time you should make a new Advertise Request packet and send it through your register_connection to the root; Don't forget to send this packet here, because in the Reunion Failure mode our main loop will not work properly and everything will be got stock! :return: """ # TODO: this part is definitely gonna cause a lot of bugs,im not sure with the delays and 4 seconds while True: if self.is_root: for address, last_reunion_time in self.reunions_arrival_time.items( ): if (datetime.now() - last_reunion_time) > timedelta(seconds=16): self.network_graph.turn_off_node(address) self.network_graph.turn_off_subtree(address) self.network_graph.remove_node(address) else: if self.reunion_mode == "acceptance": if (datetime.now() - self.last_reunion_sent_time) >= timedelta( seconds=4): nodes_array = [(self.server_ip, self.server_port)] new_packet = self.packet_factory.new_reunion_packet( "REQ", (self.server_ip, self.server_port), nodes_array) self.stream.add_message_to_out_buff( self.stream.get_parent_node().get_server_address(), new_packet.get_buf()) self.last_reunion_sent_time = datetime.now() self.reunion_mode = "pending" elif self.reunion_mode == "pending": if (datetime.now() - self.last_reunion_sent_time) > timedelta( seconds=16): advertise_packet = self.packet_factory.new_advertise_packet( "REQ", (self.server_ip, self.server_port)) self.stream.add_message_to_out_buff( self.root_address, advertise_packet.get_buf()) def send_broadcast_packet(self, broadcast_packet): """ For setting broadcast packets buffer into Nodes out_buff. Warnings: 1. Don't send Message packets through register_connections. :param broadcast_packet: The packet that should be broadcast through the network. :type broadcast_packet: Packet :return: """ print("Send broadcast message: " + str(broadcast_packet.get_buf())) message = broadcast_packet.get_buf() self.stream.broadcast_to_none_registers( message, self.stream.get_server_address()) def handle_packet(self, packet): """ This function act as a wrapper for other handle_###_packet methods to handle the packet. Code design suggestion: 1. It's better to check packet validation right now; For example Validation of the packet length. :param packet: The arrived packet that should be handled. :type packet Packet """ type = packet.get_type() if packet.get_version() != 1: print("unsupported version", file=sys.stderr) raise ValueError if type < 1 or type > 5: print("unknown packet type", file=sys.stderr) raise ValueError if type == 1: self.__handle_register_packet(packet) elif type == 2: self.__handle_advertise_packet(packet) elif type == 3: self.__handle_join_packet(packet) elif type == 4: self.__handle_message_packet(packet) elif type == 5: self.__handle_reunion_packet(packet) def __check_registered(self, source_address): """ If the Peer is the root of the network we need to find that is a node registered or not. :param source_address: Unknown IP/Port address. :type source_address: tuple :return: """ if self.is_root: if self.stream.get_node_by_server(source_address[0], source_address[1]): if self.stream.get_node_by_server( source_address[0], source_address[1]).is_register(): return True def __handle_advertise_packet(self, packet): """ For advertising peers in the network, It is peer discovery message. Request: We should act as the root of the network and reply with a neighbour address in a new Advertise Response packet. Response: When an Advertise Response packet type arrived we should update our parent peer and send a Join packet to the new parent. Code design suggestion: 1. Start the Reunion daemon thread when the first Advertise Response packet received. 2. When an Advertise Response message arrived, make a new Join packet immediately for the advertised address. Warnings: 1. Don't forget to ignore Advertise Request packets when you are a non-root peer. 2. The addresses which still haven't registered to the network can not request any peer discovery message. 3. Maybe it's not the first time that the source of the packet sends Advertise Request message. This will happen in rare situations like Reunion Failure. Pay attention, don't advertise the address to the packet sender sub-tree. 4. When an Advertise Response packet arrived update our Peer parent for sending Reunion Packets. :param packet: Arrived register packet :type packet Packet :return: """ if packet.get_res_or_req() == "REQ": if self.is_root: print("advertise REQ in root:" + str(packet.get_buf())) if self.__check_registered(packet.get_source_server_address()): parent = self.__get_neighbour( packet.get_source_server_address()) new_packet = self.packet_factory.new_advertise_packet( "RES", self.stream.get_server_address(), parent) print("packet from advertise root" + str(new_packet.get_buf())) self.stream.add_message_to_out_buff( packet.get_source_server_address(), new_packet.get_buf()) self.network_graph.add_node( packet.get_source_server_ip(), packet.get_source_server_port(), parent) else: if not self.is_root: print("advertise RES in peer:" + str(packet.get_buf())) buff = packet.get_buf()[23:] buff = str(buff) buff = buff[2:] buff = buff[:len(buff) - 1] ip = buff[:15] port = buff[15:20] print(ip, port) self.stream.add_parent_node(server_address=(ip, int(port))) self.stream.get_parent_node().add_message_to_out_buff( PacketFactory.new_join_packet( self.stream.get_server_address()).get_buf()) print("join", self.stream.get_parent_node().out_buff) self.stream.get_parent_node().send_message() print("hogyttg") # self.t.run() print("adkofg") def __handle_register_packet(self, packet): """ For registration a new node to the network at first we should make a Node with stream.add_node for'sender' and save it. Code design suggestion: 1.For checking whether an address is registered since now or not you can use SemiNode object except Node. Warnings: 1. Don't forget to ignore Register Request packets when you are a non-root peer. :param packet: Arrived register packet :type packet Packet :return: """ # TODO:check this again if self.is_root: print("register in root:" + str(packet.get_buf())) if not self.__check_registered(packet.get_source_server_address()): print("node address ", packet.get_source_server_address()) self.stream.add_node(packet.get_source_server_address(), set_register_connection=True) response_packet = self.packet_factory.new_register_packet( "RES", self.stream.get_server_address()) self.stream.add_message_to_out_buff( packet.get_source_server_address(), response_packet.get_buf()) # else: # if packet.get_res_or_req() == "RES": #advertise_packet = self.packet_factory.new_advertise_packet("REQ", self.stream.get_server_address()) #self.stream.add_message_to_out_buff(packet.get_source_server_address(), advertise_packet) def __check_neighbour(self, address): """ It checks is the address in our neighbours array or not. :param address: Unknown address :type address: tuple :return: Whether is address in our neighbours or not. :rtype: bool """ print("neighbour checked!") if self.stream.get_node_by_server(address[0], address[1]): if not (self.stream.get_node_by_server(address[0], address[1]).is_register()): return True pass #Tested def __handle_message_packet(self, packet): """ Only broadcast message to the other nodes. Warnings: 1. Do not forget to ignore messages from unknown sources. 2. Make sure that you are not sending a message to a register_connection. :param packet: Arrived message packet :type packet Packet :return: """ print("from handle message: " + str(packet.get_buf())) body = str(packet.get_buf()[20:]) new_packet = self.packet_factory.new_message_packet( body[2:len(body) - 1], self.stream.get_server_address()) print( self.stream.broadcast_to_none_registers( new_packet.get_buf(), packet.get_source_server_address())) #Tested packet builds not send! def __handle_reunion_packet(self, packet): """ In this function we should handle Reunion packet was just arrived. Reunion Hello: If you are root Peer you should answer with a new Reunion Hello Back packet. At first extract all addresses in the packet body and append them in descending order to the new packet. You should send the new packet to the first address in the arrived packet. If you are a non-root Peer append your IP/Port address to the end of the packet and send it to your parent. Reunion Hello Back: Check that you are the end node or not; If not only remove your IP/Port address and send the packet to the next address, otherwise you received your response from the root and everything is fine. Warnings: 1. Every time adding or removing an address from packet don't forget to update Entity Number field. 2. If you are the root, update last Reunion Hello arrival packet from the sender node and turn it on. 3. If you are the end node, update your Reunion mode from pending to acceptance. :param packet: Arrived reunion packet :return: """ # TODO: update number of entire res = packet.get_res_or_req() body = str(packet.get_buf()[25:]) ips_str = body[2:len(body) - 1] ips = [] ports = [] for i in range(0, len(ips_str), 20): ips.append(ips_str[i:i + 15]) ports.append(ips_str[i + 15:i + 20]) if res == "REQ": if self.is_root: print("reunion REQ in root:" + str(packet.get_buf())) # updating reunions arrival time adds = [] for i in range(len(ips)): adds.append((SemiNode.parse_ip(ips[i]), SemiNode.parse_port(ports[i]))) for address in adds: self.reunions_arrival_time[address] = datetime.now() reversed_ips = ips[::-1] reversed_ports = ports[::-1] nodes_array = [] for i in range(len(reversed_ips)): nodes_array.append((reversed_ips[i], reversed_ports[i])) new_packet = self.packet_factory.new_reunion_packet( "RES", self.stream.get_server_address(), nodes_array) node_address = (SemiNode.parse_ip(nodes_array[0][0]), SemiNode.parse_port(nodes_array[0][1])) print(new_packet.get_buf()) self.stream.add_message_to_out_buff(node_address, new_packet) else: print("reunion REQ in peer:" + str(packet.get_buf())) ips.append(self.stream.get_server_address()[0]) ports.append(self.stream.get_server_address()[1]) nodes_array = [] for i in range(len(ips)): nodes_array.append((ips[i], ports[i])) new_packet = self.packet_factory.new_reunion_packet( "REQ", self.stream.get_server_address(), nodes_array) print(new_packet.get_buf()) parent_address = self.stream.get_parent_node( ).get_server_address() self.stream.add_message_to_out_buff(parent_address, new_packet) elif res == "RES": print("reunion RES in peer:" + str(packet.get_buf())) if ips[len(ips) - 1] == self.stream.get_server_address()[0] \ and \ ports[len(ports) - 1] == self.stream.get_server_address()[1]: ips.pop(len(ips) - 1) ports.pop(len(ports) - 1) nodes_array = [] for i in range(len(ips)): nodes_array.append((ips[i], ports[i])) new_packet = self.packet_factory.new_reunion_packet( "REQ", self.stream.get_server_address(), nodes_array) if len(ips) == 0: self.reunion_mode = "acceptance" print("accepted") else: node_address = (SemiNode.parse_ip(nodes_array[0][0]), SemiNode.parse_port(nodes_array[0][1])) print(new_packet.get_buf()) self.stream.add_message_to_out_buff( node_address, new_packet) #Tested def __handle_join_packet(self, packet): """ When a Join packet received we should add a new node to our nodes array. In reality, there is a security level that forbids joining every node to our network. :param packet: Arrived register packet. :type packet Packet :return: """ print("recv join: " + str(packet.get_buf())) self.stream.add_node(packet.get_source_server_address()) def __get_neighbour(self, sender): """ Finds the best neighbour for the 'sender' from the network_nodes array. This function only will call when you are a root peer. Code design suggestion: 1. Use your NetworkGraph find_live_node to find the best neighbour. :param sender: Sender of the packet :return: The specified neighbour for the sender; The format is like ('192.168.001.001', '05335'). """ return self.network_graph.find_live_node(sender)
class Peer(threading.Thread): def __init__(self, server_ip, server_port, is_root=False, root_address=None): """ The Peer object constructor. Code design suggestions: 1. Initialise a Stream object for our Peer. 2. Initialise a PacketFactory object. 3. Initialise our UserInterface for interaction with user commandline. 4. Initialise a Thread for handling reunion daemon. Warnings: 1. For root Peer, we need a NetworkGraph object. 2. In root Peer, start reunion daemon as soon as possible. 3. In client Peer, we need to connect to the root of the network, Don't forget to set this connection as a register_connection. :param server_ip: Server IP address for this Peer that should be pass to Stream. :param server_port: Server Port address for this Peer that should be pass to Stream. :param is_root: Specify that is this Peer root or not. :param root_address: Root IP/Port address if we are a client. :type server_ip: str :type server_port: int :type is_root: bool :type root_address: tuple """ super().__init__() self.server_ip = Node.parse_ip(server_ip) self.server_port = Node.parse_port(str(server_port)) self.server_address = (self.server_ip, self.server_port) self.stream = Stream(self.server_ip, self.server_port) self.UI = UserInterface() self.is_root = is_root self.left_child_address, self.right_child_address, self.parent_address = None, None, None if self.is_root: self.registered_peers_address = {} self.root_ip, self.root_port = self.server_ip, self.server_port self.root_node = GraphNode((self.root_ip, self.root_port)) self.graph = NetworkGraph(self.root_node) else: self.root_ip = Node.parse_ip(root_address[0]) self.root_port = Node.parse_ip(str(root_address[1])) self.root_address = (self.root_ip, self.root_port) self.running = True self.registered = False self.advertised = False self.joined = False self.reunion_on_fly = False self.reunion_timer = 0 self.counter = 0 self.timer_interval = 0.2 self.UI.start() self.start() def start_user_interface(self): """ For starting UserInterface thread. :return: """ self.print_function("F**K KHAJE POOR") def handle_user_interface_buffer(self): """ In every interval, we should parse user command that buffered from our UserInterface. All of the valid commands are listed below: 1. Register: With this command, the client send a Register Request packet to the root of the network. 2. Advertise: Send an Advertise Request to the root of the network for finding first hope. 3. SendMessage: The following string will be added to a new Message packet and broadcast through the network. Warnings: 1. Ignore irregular commands from the user. 2. Don't forget to clear our UserInterface buffer. :return: """ for command in self.UI.buffer: self.handle_user_command(command) self.UI.clear_buffer() def handle_user_command(self, command): """ :param command: :type command: str :return: """ command = command.lower() if command.startswith("register"): if self.registered: self.print_function( "You have been registered before and F**K KHAJE POOR") else: register_req_packet = PacketFactory.new_register_packet( type='REQ', source_server_address=self.server_address, address=self.root_address) # Adding root node to peer's stream self.stream.add_node(server_address=self.root_address, set_register_connection=True) self.stream.add_message_to_out_buff( address=self.root_address, message=register_req_packet.buf) self.print_function("{} sent register request".format( self.server_address)) elif command.startswith("advertise"): if not self.registered: self.print_function( "{} must register first to advertise".format( str(self.server_address))) elif self.advertised: self.print_function("You have advertised before") else: advertise_req_packet = PacketFactory.new_advertise_packet( type='REQ', source_server_address=self.server_address) self.stream.add_message_to_out_buff( address=self.root_address, message=advertise_req_packet.buf) self.print_function("{} sent advertise packet".format( str(self.server_address))) elif command.startswith("message"): if self.joined: message = command[8:] broadcast_message_packet = PacketFactory.new_message_packet( message=message, source_server_address=self.server_address) self.print_function("{} is broadcasting message: {}".format( str(self.server_address), message)) self.send_broadcast_packet(broadcast_message_packet, "F**K KHAJE POOR") else: self.print_function( "You must join the network before broadcasting a message") elif command.startswith('disconnect'): self.running = False else: self.print_function("Unknown command received: {}".format(command)) def run(self): """ The main loop of the program. Code design suggestions: 1. Parse server in_buf of the stream. 2. Handle all packets were received from our Stream server. 3. Parse user_interface_buffer to make message packets. 4. Send packets stored in nodes buffer of our Stream object. 5. ** sleep the current thread for 2 seconds ** Warnings: 1. At first check reunion daemon condition; Maybe we have a problem in this time and so we should hold any actions until Reunion acceptance. 2. In every situation checkout Advertise Response packets; even is Reunion in failure mode or not :return: """ while True: if self.is_root: pass else: pass if self.counter > 0 or True: received_bufs = self.stream.read_in_buf() received_packets = [ PacketFactory.parse_buffer(buf) for buf in received_bufs ] for packet in received_packets: self.handle_packet(packet) self.stream.clear_in_buff() self.stream.send_out_buf_messages() self.handle_user_interface_buffer() if self.reunion_timer == 0 and self.joined and not self.reunion_on_fly: self.reunion_on_fly = True reunion_req_packet = PacketFactory.new_reunion_packet( type='REQ', source_address=self.server_address, nodes_array=[self.server_address]) self.stream.add_message_to_out_buff( address=self.parent_address, message=reunion_req_packet.buf) self.print_function("Sent Reunion Packet to Parent: " + str(self.parent_address)) if not self.reunion_on_fly and self.reunion_timer > 0: self.reunion_timer -= self.timer_interval * 10 self.counter += self.timer_interval * 10 if self.counter == 4 * 10: self.counter = 0 if self.is_root: for gnode in self.graph.nodes: gnode.reunion_timer += self.timer_interval * 10 time.sleep(self.timer_interval) def run_reunion_daemon(self): """ In this function, we will handle all Reunion actions. Code design suggestions: 1. Check if we are the network root or not; The actions are identical. 2. If it's the root Peer, in every interval check the latest Reunion packet arrival time from every node; If time is over for the node turn it off (Maybe you need to remove it from our NetworkGraph). 3. If it's a non-root peer split the actions by considering whether we are waiting for Reunion Hello Back Packet or it's the time to send new Reunion Hello packet. Warnings: 1. If we are the root of the network in the situation that we want to turn a node off, make sure that you will not advertise the nodes sub-tree in our GraphNode. 2. If we are a non-root Peer, save the time when you have sent your last Reunion Hello packet; You need this time for checking whether the Reunion was failed or not. 3. For choosing time intervals you should wait until Reunion Hello or Reunion Hello Back arrival, pay attention that our NetworkGraph depth will not be bigger than 8. (Do not forget main loop sleep time) 4. Suppose that you are a non-root Peer and Reunion was failed, In this time you should make a new Advertise Request packet and send it through your register_connection to the root; Don't forget to send this packet here, because in the Reunion Failure mode our main loop will not work properly and everything will be got stock! :return: """ self.print_function("F**K KHAJE POOR") def send_broadcast_packet(self, broadcast_packet, exclude_address): """ For setting broadcast packets buffer into Nodes out_buff. Warnings: 1. Don't send Message packets through register_connections. :param broadcast_packet: The packet that should be broadcast through the network. :type broadcast_packet: Packet :return: """ if self.parent_address is not None and self.parent_address != exclude_address: self.stream.add_message_to_out_buff(self.parent_address, broadcast_packet.buf) if self.left_child_address is not None and self.left_child_address != exclude_address: self.stream.add_message_to_out_buff(self.left_child_address, broadcast_packet.buf) if self.right_child_address is not None and self.right_child_address != exclude_address: self.stream.add_message_to_out_buff(self.right_child_address, broadcast_packet.buf) def handle_packet(self, packet): """ This function act as a wrapper for other handle_###_packet methods to handle the packet. Code design suggestion: 1. It's better to check packet validation right now; For example Validation of the packet length. :param packet: The arrived packet that should be handled. :type packet Packet """ packet_type = packet.get_type() # if self.is_root: # self.print_function("root received type {} message".format(packet_type)) if packet_type == 1: self.__handle_register_packet(packet) elif packet_type == 2: self.__handle_advertise_packet(packet) elif packet_type == 3: self.__handle_join_packet(packet) elif packet_type == 4: self.__handle_message_packet(packet) elif packet_type == 5: self.__handle_reunion_packet(packet) else: self.print_function("Unknown type of packet received {}".format( packet.get_body())) def __check_registered(self, source_address): """ If the Peer is the root of the network we need to find that is a node registered or not. :param source_address: Unknown IP/Port address. :type source_address: tuple :return: """ if source_address in self.registered_peers_address: return True return False def __handle_advertise_packet(self, packet): """ For advertising peers in the network, It is peer discovery message. Request: We should act as the root of the network and reply with a neighbour address in a new Advertise Response packet. Response: When an Advertise Response packet type arrived we should update our parent peer and send a Join packet to the new parent. Code design suggestion: 1. Start the Reunion daemon thread when the first Advertise Response packet received. 2. When an Advertise Response message arrived, make a new Join packet immediately for the advertised address. Warnings: 1. Don't forget to ignore Advertise Request packets when you are a non-root peer. 2. The addresses which still haven't registered to the network can not request any peer discovery message. 3. Maybe it's not the first time that the source of the packet sends Advertise Request message. This will happen in rare situations like Reunion Failure. Pay attention, don't advertise the address to the packet sender sub-tree. 4. When an Advertise Response packet arrived update our Peer parent for sending Reunion Packets. :param packet: Arrived register packet :type packet Packet :return: """ if self.is_root: self.__handle_advertise_packet_root(packet) else: self.__handle_advertise_packet_client(packet) def __handle_advertise_packet_root(self, packet): if packet.get_body()[:3] == "REQ": packet_source_address = packet.get_source_server_address() if not self.__check_registered(packet_source_address): self.print_function( "Advertise request received from unregistered client {}". format(str(packet_source_address))) else: if self.graph.find_node(packet_source_address[0], packet_source_address[1]) is not None: self.print_function( "Advertise request received from a client in graph {}". format(str(packet_source_address))) else: father_node = self.graph.find_live_node("F**K KHAJE POOR") print("Father found", father_node.address) self.graph.add_node(ip=packet_source_address[0], port=packet_source_address[1], father_address=father_node.address) self.graph.turn_on_node(packet_source_address) register_response_packet = PacketFactory.new_advertise_packet( type='RES', source_server_address=self.server_address, neighbour=father_node.address) self.stream.add_message_to_out_buff( address=packet_source_address, message=register_response_packet.buf) self.registered_peers_address[packet_source_address] = 1 self.print_function( "Advertise request received from {} and register response sent" .format(packet_source_address)) else: self.print_function( "Root received advertise response. Wtf? F**K KHAJE POOR") def __handle_advertise_packet_client(self, packet): if packet.get_body()[:3] == 'RES': parent_address = (packet.get_body()[3:18], packet.get_body()[18:23]) self.advertised = True if parent_address != self.root_address: self.stream.add_node(server_address=parent_address, set_register_connection=False) join_packet = PacketFactory.new_join_packet( source_server_address=self.server_address) self.stream.add_message_to_out_buff(address=parent_address, message=join_packet.buf) self.parent_address = parent_address self.joined = True self.print_function( "Advertise response received from root. Client {} sent join request to {}" .format(str(self.server_address), str(parent_address))) else: self.print_function( "Client received advertise request. Wtf? F**K KHAJE POOR") def __handle_register_packet(self, packet): """ For registration a new node to the network at first we should make a Node with stream.add_node for'sender' and save it. Code design suggestion: 1.For checking whether an address is registered since now or not you can use SemiNode object except Node. Warnings: 1. Don't forget to ignore Register Request packets when you are a non-root peer. :param packet: Arrived register packet :type packet Packet :return: """ if self.is_root: self.__handle_register_packet_root(packet) else: self.__handle_register_packet_client(packet) def __handle_register_packet_root(self, packet): if packet.get_body()[:3] == "REQ": packet_source_address = packet.get_source_server_address() if self.__check_registered(packet_source_address): self.print_function( "Duplicate register request received from {}".format( str(packet_source_address))) else: self.stream.add_node(server_address=packet_source_address, set_register_connection=True) register_response_packet = PacketFactory.new_register_packet( type='RES', source_server_address=self.server_address) self.stream.add_message_to_out_buff( address=packet.get_source_server_address(), message=register_response_packet.buf) self.registered_peers_address[packet_source_address] = 1 self.print_function( "Register request received from {} and register response sent" .format(packet_source_address)) else: self.print_function( "Root received register response. Wtf? F**K KHAJE POOR") def __handle_register_packet_client(self, packet): if packet.get_body()[:3] == "RES": self.registered = True self.print_function("Client {} received register response".format( self.server_address)) else: self.print_function( "Client received register request. Wtf? F**K KHAJE POOR") def __check_neighbour(self, address): """ It checks is the address in our neighbours array or not. :param address: Unknown address :type address: tuple :return: Whether is address in our neighbours or not. :rtype: bool """ return address == self.parent_address or address == self.left_child_address or address == self.right_child_address def __handle_message_packet(self, packet): """ Only broadcast message to the other nodes. Warnings: 1. Do not forget to ignore messages from unknown sources. 2. Make sure that you are not sending a message to a register_connection. :param packet: Arrived message packet :type packet Packet :return: """ source_address = packet.get_source_server_address() if self.__check_neighbour(source_address): self.print_function("{} received broadcast message: {}".format( self.server_address, packet.get_body())) broadcast_message_packet = PacketFactory.new_message_packet( message=packet.get_body(), source_server_address=self.server_address) self.send_broadcast_packet(broadcast_message_packet, exclude_address=source_address) else: self.print_function( "Broadcast packet received from KHAJE POOR. wut??") def __handle_reunion_packet(self, packet): """ In this function we should handle Reunion packet was just arrived. Reunion Hello: If you are root Peer you should answer with a new Reunion Hello Back packet. At first extract all addresses in the packet body and append them in descending order to the new packet. You should send the new packet to the first address in the arrived packet. If you are a non-root Peer append your IP/Port address to the end of the packet and send it to your parent. Reunion Hello Back: Check that you are the end node or not; If not only remove your IP/Port address and send the packet to the next address, otherwise you received your response from the root and everything is fine. Warnings: 1. Every time adding or removing an address from packet don't forget to update Entity Number field. 2. If you are the root, update last Reunion Hello arrival packet from the sender node and turn it on. 3. If you are the end node, update your Reunion mode from pending to acceptance. :param packet: Arrived reunion packet :return: """ source_address = packet.get_source_server_address() if not self.__check_neighbour(source_address): self.print_function( "Reunion packet received from KHAJE POOR. wut??") def __handle_reunion_packet_root(self, packet): body = packet.get_body() if body[:3] == "REQ": packet_source_address = packet.get_source_server_address() if self.graph.find_node(packet_source_address[0], packet_source_address[1]) is None: self.print_function( "Root received reunion from {} which is not in graph". format(packet_source_address)) nodes_address_list = [] n_nodes = int(body[3:5]) for i in range(n_nodes): ip = body[5 + 20 * i:5 + 15 + 20 * i] port = body[20 + 20 * i:20 + 5 + 20 * i] nodes_address_list.append((ip, port)) reunion_sender_node = self.graph.find_node( nodes_address_list[0][0], nodes_address_list[0][1]) self.print_function( "Root received Reunion from {} and reunion response sent". format(str(reunion_sender_node))) nodes_address_list.reverse() reunion_sender_node.reunion_timer = 0 reunion_response_packet = PacketFactory.new_reunion_packet( type="RES", source_address=self.root_address, nodes_array=nodes_address_list) self.stream.add_message_to_out_buff( packet_source_address, message=reunion_response_packet.buf) else: self.print_function( "Root received reunion response. Wtf? F**K KHAJE POOR") def __handle_reunion_packet_client(self, packet): body = packet.get_body() if not self.joined: self.print_function( "{} has no parent to redirect reunion request to".format( self.server_address)) return if body[:3] == "REQ": nodes_address_list = [] n_nodes = int(body[3:5]) for i in range(n_nodes): ip = body[5 + 20 * i:5 + 15 + 20 * i] port = body[20 + 20 * i:20 + 5 + 20 * i] nodes_address_list.append((ip, port)) reunion_sender_node = self.graph.find_node( nodes_address_list[0][0], nodes_address_list[0][1]) nodes_address_list.append(self.server_address) reunion_response_packet = PacketFactory.new_reunion_packet( type="REQ", source_address=self.server_address, nodes_array=nodes_address_list) self.stream.add_message_to_out_buff( self.parent_address, message=reunion_response_packet.buf) self.print_function( "{} received Reunion from {} and reunion request sent to parent" .format(str(self.server_address), str(reunion_sender_node))) else: n_nodes = int(body[3:5]) if n_nodes == 1: target_ip = body[5:20] target_port = body[20:25] if (target_ip, target_port) == self.server_address: self.reunion_on_fly = False self.reunion_timer = 4 * 10 else: nodes_address_list = [] n_nodes = int(body[3:5]) for i in range(1, n_nodes): nodes_address_list.append((body[5 + 20 * i:20 + 20 * i], body[20 + 20 * i:25 + 20 * i])) reunion_response_packet = PacketFactory.new_reunion_packet( type='RES', source_address=self.server_address, nodes_array=nodes_address_list) target_ip = body[25:40] target_port = body[40:45] target_address = (target_ip, target_port) self.stream.add_message_to_out_buff( target_address, reunion_response_packet.buf) self.print_function( "{} redirected reunion response to {}".format( self.server_address, target_address)) def __handle_join_packet(self, packet): """ When a Join packet received we should add a new node to our nodes array. In reality, there is a security level that forbids joining every node to our network. :param packet: Arrived register packet. :type packet Packet :return: """ join_request_source_address = packet.get_source_server_address() if self.right_child_address == join_request_source_address or self.left_child_address == join_request_source_address: self.print_function( "{} received join request from {} but they have been joined before" .format(self.server_address, join_request_source_address)) if self.right_child_address is None: self.right_child_address = join_request_source_address elif self.left_child_address is None: self.left_child_address = join_request_source_address else: self.print_function( "Client {} received join from {} but has no free room for a new child" .format(self.server_address, join_request_source_address)) return self.print_function("{} received join request from {}".format( self.server_address, join_request_source_address)) if not self.is_root: self.stream.add_node(join_request_source_address, set_register_connection=False) def __get_neighbour(self, sender): """ Finds the best neighbour for the 'sender' from the network_nodes array. This function only will call when you are a root peer. Code design suggestion: 1. Use your NetworkGraph find_live_node to find the best neighbour. :param sender: Sender of the packet :return: The specified neighbour for the sender; The format is like ('192.168.001.001', '05335'). """ pass def print_function(self, message): print(message) self.UI.peer_log.log += "{}:\n\t{}\n".format(datetime.datetime.now(), message)
class Peer: def __init__(self, server_ip: str, server_port: int, is_root: bool = False, root_address: Address = None, command_line=True) -> None: """ The Peer object constructor. Code design suggestions: 1. Initialise a Stream object for our Peer. 2. Initialise a PacketFactory object. 3. Initialise our UserInterface for interaction with user commandline. 4. Initialise a Thread for handling reunion daemon. Warnings: 1. For root Peer, we need a NetworkGraph object. 2. In root Peer, start reunion daemon as soon as possible. 3. In client Peer, we need to connect to the root of the network, Don't forget to set this connection as a register_connection. :param server_ip: Server IP address for this Peer that should be pass to Stream. :param server_port: Server Port address for this Peer that should be pass to Stream. :param is_root: Specify that is this Peer root or not. :param root_address: Root IP/Port address if we are a client. :type server_ip: str :type server_port: int :type is_root: bool :type root_address: Address """ self.server_ip = parse_ip(server_ip) self.server_port = server_port self.address = (self.server_ip, self.server_port) self.is_root = is_root self.root_address = root_address self.reunion_daemon = ReunionThread(self.run_reunion_daemon) self.reunion_mode = ReunionMode.ACCEPTANCE self.registered: List[SemiNode] = [] self.parent_address: Address = None self.children_addresses: List[Address] = [] self.stream = Stream(server_ip, server_port) self.user_interface = UserInterface() self.last_hello_back_time = None # When you received your last hello back from root self.last_hello_time = None # When you sent your last hello to root if is_root: self.network_graph = NetworkGraph( GraphNode((self.server_ip, self.server_port))) self.reunion_daemon.start() elif command_line: self.start_user_interface() def start_user_interface(self) -> None: """ For starting UserInterface thread. :return: """ if not self.is_root: log('UserInterface started.') self.user_interface.start() def handle_user_interface_buffer(self) -> None: """ In every interval, we should parse user command that buffered from our UserInterface. All of the valid commands are listed below: 1. Register: With this command, the client send a Register Request packet to the root of the network. 2. Advertise: Send an Advertise Request to the root of the network for finding first hope. 3. SendMessage: The following string will be added to a new Message packet and broadcast through the network. Warnings: 1. Ignore irregular commands from the user. 2. Don't forget to clear our UserInterface buffer. :return: """ user_interface_buffer = self.user_interface.buffer for command in user_interface_buffer: if command.lower() == 'register': self.handle_register_command() elif command.lower() == 'advertise': self.handle_advertise_command() elif command.lower().startswith('sendmessage'): self.handle_message_command(command) else: log('Are you on drugs?') self.user_interface.clear_buffer() def handle_register_command(self) -> None: self.__register() def __register(self) -> None: if self.stream.add_node(self.root_address, set_register_connection=True): register_packet = PacketFactory.new_register_packet( RegisterType.REQ, self.address) self.stream.add_message_to_out_buff(self.root_address, register_packet, want_register=True) log(f'Register packet added to out buff of Node({self.root_address}).' ) def handle_advertise_command(self) -> None: advertise_packet = PacketFactory.new_advertise_packet( AdvertiseType.REQ, self.address) self.stream.add_message_to_out_buff(self.root_address, advertise_packet, want_register=True) log(f'Advertise packet added to out buff of Node({self.root_address}).' ) def handle_message_command(self, command: str) -> None: message = command[12:] broadcast_packet = PacketFactory.new_message_packet( message, self.address) self.send_broadcast_packet(broadcast_packet) def run(self): """ The main loop of the program. Code design suggestions: 1. Parse server in_buf of the stream. 2. Handle all packets were received from our Stream server. 3. Parse user_interface_buffer to make message packets. 4. Send packets stored in nodes buffer of our Stream object. 5. ** sleep the current thread for 2 seconds ** Warnings: 1. At first check reunion daemon condition; Maybe we have a problem in this time and so we should hold any actions until Reunion acceptance. 2. In every situation checkout Advertise Response packets; even is Reunion in failure mode or not :return: """ try: while True: in_buff = self.stream.read_in_buf() for message in in_buff: packet = PacketFactory.parse_buffer(message) self.handle_packet(packet) self.stream.clear_in_buff() self.handle_user_interface_buffer() self.stream.send_out_buf_messages( self.reunion_mode == ReunionMode.FAILED) time.sleep(2) except KeyboardInterrupt: log('KeyboardInterrupt') try: sys.exit(0) except SystemExit: os._exit(0) def run_reunion_daemon(self): """ In this function, we will handle all Reunion actions. Code design suggestions: 1. Check if we are the network root or not; The actions are identical. 2. If it's the root Peer, in every interval check the latest Reunion packet arrival time from every node; If time is over for the node turn it off (Maybe you need to remove it from our NetworkGraph). 3. If it's a non-root peer split the actions by considering whether we are waiting for Reunion Hello Back Packet or it's the time to send new Reunion Hello packet. Warnings: 1. If we are the root of the network in the situation that we want to turn a node off, make sure that you will not advertise the nodes sub-tree in our GraphNode. 2. If we are a non-root Peer, save the time when you have sent your last Reunion Hello packet; You need this time for checking whether the Reunion was failed or not. 3. For choosing time intervals you should wait until Reunion Hello or Reunion Hello Back arrival, pay attention that our NetworkGraph depth will not be bigger than 8. (Do not forget main loop sleep time) 4. Suppose that you are a non-root Peer and Reunion was failed, In this time you should make a new Advertise Request packet and send it through your register_connection to the root; Don't forget to send this packet here, because in the Reunion Failure mode our main loop will not work properly and everything will be got stock! :return: """ while True: if self.is_root: self.__run_root_reunion_daemon() else: self.__run_non_root_reunion_daemon() time.sleep(4) def __run_root_reunion_daemon(self): graph_nodes = self.network_graph.nodes for graph_node in graph_nodes: if graph_node.address == self.address: continue time_passed_since_last_hello = time.time() - graph_node.last_hello log(f'Time passed since last hello from Node({graph_node.address}): {time_passed_since_last_hello}' ) if time_passed_since_last_hello > MAX_HELLO_INTERVAL: self.stream.remove_node( self.stream.get_node_by_address(graph_node.address[0], graph_node.address[1])) self.network_graph.remove_node(graph_node.address) def __run_non_root_reunion_daemon(self): time_between_last_hello_and_last_hello_back = self.last_hello_time - self.last_hello_back_time log(f'Time between last hello and last hello back: {time_between_last_hello_and_last_hello_back}' ) if self.last_hello_time - self.last_hello_back_time > MAX_PENDING_TIME: log('Seems like we are disconnected from the root. Trying to reconnect...' ) self.reunion_mode = ReunionMode.FAILED self.__handle_advertise_command() # Send new Advertise packet time.sleep(3) else: log(f'Sending new Reunion Hello packet.') packet = PacketFactory.new_reunion_packet(ReunionType.REQ, self.address, [self.address]) self.stream.add_message_to_out_buff(self.parent_address, packet) self.last_hello_time = time.time() def send_broadcast_packet(self, broadcast_packet: Packet) -> None: """ For setting broadcast packets buffer into Nodes out_buff. Warnings: 1. Don't send Message packets through register_connections. :param broadcast_packet: The packet that should be broadcast through the network. :type broadcast_packet: Packet :return: """ for neighbor_address in [ *self.children_addresses, self.parent_address ]: if neighbor_address: self.stream.add_message_to_out_buff(neighbor_address, broadcast_packet) log(f'Message packet added to out buff of Node({neighbor_address}).' ) def handle_packet(self, packet): """ This function act as a wrapper for other handle_###_packet methods to handle the packet. Code design suggestion: 1. It's better to check packet validation right now; For example Validation of the packet length. :param packet: The arrived packet that should be handled. :type packet Packet """ if not self.__validate_received_packet(packet): return packet_type = packet.get_type() log(f'Packet of type {packet_type.name} received.') if self.reunion_mode == ReunionMode.FAILED: if packet_type == PacketType.ADVERTISE: self.__handle_advertise_packet(packet) return if packet_type == PacketType.MESSAGE: self.__handle_message_packet(packet) elif packet_type == PacketType.ADVERTISE: self.__handle_advertise_packet(packet) elif packet_type == PacketType.JOIN: self.__handle_join_packet(packet) elif packet_type == PacketType.REGISTER: self.__handle_register_packet(packet) elif packet_type == PacketType.REUNION: self.__handle_reunion_packet(packet) @staticmethod def __validate_received_packet(packet: Packet) -> bool: if packet.get_length() != len(packet.get_body()): return False # TODO: More conditions return True def __check_registered(self, source_address: Address) -> bool: """ If the Peer is the root of the network we need to find that is a node registered or not. :param source_address: Unknown IP/Port address. :type source_address: Address :return: """ source_ip, source_port = source_address source_node = SemiNode(source_ip, source_port) return source_node in self.registered def __handle_advertise_packet(self, packet: Packet): """ For advertising peers in the network, It is peer discovery message. Request: We should act as the root of the network and reply with a neighbour address in a new Advertise Response packet. Response: When an Advertise Response packet type arrived we should update our parent peer and send a Join packet to the new parent. Code design suggestion: 1. Start the Reunion daemon thread when the first Advertise Response packet received. 2. When an Advertise Response message arrived, make a new Join packet immediately for the advertised address. Warnings: 1. Don't forget to ignore Advertise Request packets when you are a non-root peer. 2. The addresses which still haven't registered to the network can not request any peer discovery message. 3. Maybe it's not the first time that the source of the packet sends Advertise Request message. This will happen in rare situations like Reunion Failure. Pay attention, don't advertise the address to the packet sender sub-tree. 4. When an Advertise Response packet arrived update our Peer parent for sending Reunion Packets. :param packet: Arrived register packet :type packet Packet :return: """ advertise_type = self.__identify_advertise_type(packet) if self.is_root and advertise_type == AdvertiseType.REQ: self.__handle_advertise_request(packet) elif (not self.is_root) and advertise_type == AdvertiseType.RES: self.__handle_advertise_response(packet) def __identify_advertise_type(self, packet: Packet) -> AdvertiseType: advertise_type = packet.get_body()[:3] return AdvertiseType(advertise_type) def __handle_advertise_request(self, packet: Packet) -> None: sender_address = packet.get_source_server_address() sender_semi_node = SemiNode(sender_address[0], sender_address[1]) if sender_semi_node not in self.registered: log(f'Advertise Request from unregistered source({sender_address}).' ) return advertised_address = self.__get_neighbour(sender_address) log(f'Advertising Node({advertised_address}) to Node({sender_address}).' ) advertise_response_packet = PacketFactory.new_advertise_packet( AdvertiseType.RES, self.address, advertised_address) self.stream.add_message_to_out_buff(sender_address, advertise_response_packet, want_register=True) # Add to network_graph self.network_graph.add_node(sender_semi_node.get_ip(), sender_semi_node.get_port(), advertised_address) def __handle_advertise_response(self, packet: Packet) -> None: self.last_hello_time = time.time() self.last_hello_back_time = time.time() parent_address = packet.get_advertised_address() log(f'Trying to join Node({parent_address})...') self.parent_address = parent_address join_packet = PacketFactory.new_join_packet(self.address) self.stream.add_node( parent_address) # Add a non_register Node to stream to the parent log(f'Join Request added to out buf on Node({parent_address}).') self.stream.add_message_to_out_buff(parent_address, join_packet) self.reunion_mode = ReunionMode.ACCEPTANCE if not self.reunion_daemon.is_alive(): self.reunion_daemon.start() def __handle_register_packet(self, packet: Packet): """ For registration a new node to the network at first we should make a Node with stream.add_node for'sender' and save it. Code design suggestion: 1.For checking whether an address is registered since now or not you can use SemiNode object except Node. Warnings: 1. Don't forget to ignore Register Request packets when you are a non-root peer. :param packet: Arrived register packet :type packet Packet :return: """ register_type = self.__identify_register_type(packet) if self.is_root and register_type == RegisterType.REQ: new_node = SemiNode(packet.get_source_server_ip(), packet.get_source_server_port()) if new_node in self.registered: return self.registered.append(new_node) sender_address = packet.get_source_server_address() self.stream.add_node(sender_address, set_register_connection=True) register_response_packet = PacketFactory.new_register_packet( RegisterType.RES, self.address) self.stream.add_message_to_out_buff(sender_address, register_response_packet, want_register=True) elif register_type == RegisterType.RES: log('Register request ACKed by root. You are now registered.') def __identify_register_type(self, packet: Packet) -> RegisterType: register_type = packet.get_body()[:3] return RegisterType(register_type) def __check_neighbour(self, address: Address) -> bool: """ It checks is the address in our neighbours array or not. :param address: Unknown address :type address: Address :return: Whether is address in our neighbours or not. :rtype: bool """ is_from_children = False for child_address in self.children_addresses: is_from_children = is_from_children or (child_address == address) is_from_parent = (address == self.parent_address) return is_from_parent or is_from_children def __handle_message_packet(self, packet: Packet): """ Only broadcast message to the other nodes. Warnings: 1. Do not forget to ignore messages from unknown sources. 2. Make sure that you are not sending a message to a register_connection. :param packet: Arrived message packet :type packet Packet :return: """ log(f'New message arrived: {packet.get_body()}') sender_address = packet.get_source_server_address() updated_packet = PacketFactory.new_message_packet( packet.get_body(), self.address) if self.__check_neighbour(sender_address): # From known source for neighbor_address in [ *self.children_addresses, self.parent_address ]: if neighbor_address is not None and neighbor_address != sender_address: self.stream.add_message_to_out_buff( neighbor_address, updated_packet) def __handle_reunion_packet(self, packet: Packet): """ In this function we should handle Reunion packet was just arrived. Reunion Hello: If you are root Peer you should answer with a new Reunion Hello Back packet. At first extract all addresses in the packet body and append them in descending order to the new packet. You should send the new packet to the first address in the arrived packet. If you are a non-root Peer append your IP/Port address to the end of the packet and send it to your parent. Reunion Hello Back: Check that you are the end node or not; If not only remove your IP/Port address and send the packet to the next address, otherwise you received your response from the root and everything is fine. Warnings: 1. Every time adding or removing an address from packet don't forget to update Entity Number field. 2. If you are the root, update last Reunion Hello arrival packet from the sender node and turn it on. 3. If you are the end node, update your Reunion mode from pending to acceptance. :param packet: Arrived reunion packet :return: """ reunion_type = self.__identify_reunion_type(packet) if self.is_root and reunion_type == ReunionType.REQ: self.__update_last_reunion(packet) self.__respond_to_reunion(packet) else: if reunion_type == ReunionType.REQ: self.__pass_reunion_hello(packet) else: self.__handle_reunion_hello_back(packet) def __update_last_reunion(self, packet: Packet): sender_address = packet.get_addresses()[0] next_node = packet.get_addresses()[-1] self.network_graph.keep_alive(sender_address) log(f'New Hello from Node({sender_address}).') log(f'HelloBack added to out buf of Node({next_node})') def __respond_to_reunion(self, packet: Packet): reversed_addresses = packet.get_addresses_in_reverse() response_packet = PacketFactory.new_reunion_packet( ReunionType.RES, self.address, reversed_addresses) next_node_address = reversed_addresses[0] self.stream.add_message_to_out_buff(next_node_address, response_packet) def __identify_reunion_type(self, packet: Packet) -> ReunionType: reunion_type = packet.get_body()[:3] return ReunionType(reunion_type) def __pass_reunion_hello(self, packet: Packet): new_addresses = self.__format_reunion_hello_addresses_on_pass(packet) request_packet = PacketFactory.new_reunion_packet( ReunionType.REQ, self.address, new_addresses) self.stream.add_message_to_out_buff(self.parent_address, request_packet) def __format_reunion_hello_addresses_on_pass( self, packet: Packet) -> List[Address]: addresses = packet.get_addresses() addresses.append(self.address) return addresses def __handle_reunion_hello_back(self, packet: Packet): if packet.get_addresses()[-1] == self.address: # It's our hello back! self.last_hello_back_time = time.time() log('We received our HelloBack.') else: self.__pass_reunion_hello_back(packet) def __pass_reunion_hello_back(self, packet: Packet): new_addresses = packet.get_addresses()[1:] next_node_address = new_addresses[0] log(f'HelloBack packet passed down to Node({next_node_address}).') passed_packet = PacketFactory.new_reunion_packet( ReunionType.RES, self.address, new_addresses) self.stream.add_message_to_out_buff(next_node_address, passed_packet) def __handle_join_packet(self, packet: Packet): """ When a Join packet received we should add a new node to our nodes array. In reality, there is a security level that forbids joining every node to our network. :param packet: Arrived register packet. :type packet Packet :return: """ new_member_address = packet.get_source_server_address() log(f'New JOIN packet from Node({new_member_address}).') self.stream.add_node(new_member_address) self.children_addresses.append(new_member_address) def __get_neighbour(self, sender: Address) -> Address: """ Finds the best neighbour for the 'sender' from the network_nodes array. This function only will call when you are a root peer. Code design suggestion: 1. Use your NetworkGraph find_live_node to find the best neighbour. :param sender: Sender of the packet :return: The specified neighbour for the sender; The format is like ('192.168.001.001', 5335). """ if self.is_root: return self.network_graph.find_live_node(sender)
class Peer(threading.Thread): def __init__(self, server_ip, server_port, is_root, ui, root_address=None): """ The Peer object constructor. Code design suggestions: 1. Initialise a Stream object for our Peer. 2. Initialise a PacketFactory object. 3. Initialise our UserInterface for interaction with user commandline. 4. Initialise a Thread for handling reunion daemon. Warnings: 1. For root Peer, we need a NetworkGraph object. 2. In root Peer, start reunion daemon as soon as possible. 3. In client Peer, we need to connect to the root of the network, Don't forget to set this connection as a register_connection. :param server_ip: Server IP address for this Peer that should be pass to Stream. :param server_port: Server Port address for this Peer that should be pass to Stream. :param is_root: Specify that is this Peer root or not. :param root_address: Root IP/Port address if we are a client. :type server_ip: str :type server_port: int :type is_root: bool :type root_address: tuple """ super().__init__() self.ui_buffer = queue.Queue() self.stream = Stream(server_ip, server_port, ui) self.ip = Node.parse_ip(server_ip) self.port = Node.parse_port(str(server_port)) self.address = (self.ip, int(self.port)) self.setDaemon(True) self.ui = ui self.is_root = is_root self.root_address = self.address if not is_root: self.root_address = root_address self.children = [] self.is_reunion_running = False self.parent = None self.is_connected = False # self.reunion_timer = ReunionTimer(self) self.graph = NetworkGraph(GraphNode(self.root_address)) if not self.is_root: print('making the shit') self.stream.add_node(self.root_address, True) self.r_h_b_received = True self.since_last = 0 self.timer = Timer(self) self.timer.start() self.stop = False def start_user_interface(self): """ For starting UserInterface thread. :return: """ # in our code userinterface starts this shit pass def add_command(self, command): self.ui_buffer.put(command) def handle_user_interface_buffer(self): """ In every interval, we should parse user command that buffered from our UserInterface. All of the valid commands are listed below: 1. Register: With this command, the client send a Register Request packet to the root of the network. 2. Advertise: Send an Advertise Request to the root of the network for finding first hope. 3. SendMessage: The following string will be added to a new Message packet and broadcast through the network. Warnings: 1. Ignore irregular commands from the user. 2. Don't forget to clear our UserInterface buffer. :return: """ while not self.ui_buffer.empty(): command = self.ui_buffer.get() command = str(command) s = (command.split(' ')) if s[0] == 'send_message': print('send message') self.send_message(command[13:]) pass elif s[0] == 'join': print('join') self.send_join() pass elif s[0] == 'register': print('send register') self.send_register() pass elif s[0] == 'advertise': print('advertise') self.send_advertise() elif s[0] == 'regi': print('registered:') for reged in self.graph.registered: print(reged.get_address()[0]) print(reged.get_address()[1]) elif s[0] == 're': self.send_reunion() pass elif s[0] == 'par': print(self.parent) elif s[0] == 'ch': for address in self.children: print(address) pass def send_advertise(self): if not self.is_root: pkt = PacketFactory.new_advertise_packet('REQ', self.address) self.stream.add_message_to_out_buff(self.root_address, pkt) pass def send_register(self): if not self.is_root: pkt = PacketFactory.new_register_packet('REQ', self.address, self.address) # self.ui.display_pkt(PacketFactory.parse_buffer(pkt)) self.stream.add_message_to_out_buff(self.root_address, pkt) pass def send_message(self, s): if not self.is_connected: return print('sending mesaage to:') pkt = PacketFactory.new_message_packet(s, self.address) for address in self.children: print(address) self.stream.add_message_to_out_buff(address, pkt) if not (self.is_root) and (self.address is not None): print(self.parent) self.stream.add_message_to_out_buff(self.parent, pkt) pass def send_join(self): pkt = PacketFactory.new_join_packet(self.address) self.stream.add_message_to_out_buff(self.parent, pkt) self.is_connected = True pass def run(self): """ The main loop of the program. Code design suggestions: 1. Parse server in_buf of the stream. 2. Handle all packets were received from our Stream server. 3. Parse user_interface_buffer to make message packets. 4. Send packets stored in nodes buffer of our Stream object. 5. ** sleep the current thread for 2 seconds ** Warnings: 1. At first check reunion daemon condition; Maybe we have a problem in this time and so we should hold any actions until Reunion acceptance. 2. In every situation checkout Advertise Response packets; even is Reunion in failure mode or not :return: """ while not self.stop: # if not self.is_connected: # self.stream.clear_in_buff() # self.stream.clear_out_buff() # self.stream.clear_not_reg_nodes() print('we R in loop') self.read_stream_in_buffer() self.handle_user_interface_buffer() self.stream.send_out_buf_messages() time.sleep(2) def read_stream_in_buffer(self): buffers = self.stream.read_in_buf() for buffer in buffers: print(buffer) packet = PacketFactory.parse_buffer(buffer) if packet is None: continue if packet.length + 20 != len(buffer): continue self.handle_packet(packet) self.stream.clear_in_buff() def run_reunion_daemon(self): """ In this function, we will handle all Reunion actions. Code design suggestions: 1. Check if we are the network root or not; The actions are identical. 2. If it's the root Peer, in every interval check the latest Reunion packet arrival time from every node; If time is over for the node turn it off (Maybe you need to remove it from our NetworkGraph). 3. If it's a non-root peer split the actions by considering whether we are waiting for Reunion Hello Back Packet or it's the time to send new Reunion Hello packet. Warnings: 1. If we are the root of the network in the situation that we want to turn a node off, make sure that you will not advertise the nodes sub-tree in our GraphNode. 2. If we are a non-root Peer, save the time when you have sent your last Reunion Hello packet; You need this time for checking whether the Reunion was failed or not. 3. For choosing time intervals you should wait until Reunion Hello or Reunion Hello Back arrival, pay attention that our NetworkGraph depth will not be bigger than 8. (Do not forget main loop sleep time) 4. Suppose that you are a non-root Peer and Reunion was failed, In this time you should make a new Advertise Request packet and send it through your register_connection to the root; Don't forget to send this packet here, because in the Reunion Failure mode our main loop will not work properly and everything will be got stock! :return: """ # self.reunion_timer.start() pass def send_broadcast_packet(self, broadcast_packet): """ For setting broadcast packets buffer into Nodes out_buff. Warnings: 1. Don't send Message packets through register_connections. :param broadcast_packet: The packet that should be broadcast through the network. :type broadcast_packet: Packet :return: """ nodes = self.stream.get_not_register_nodes() for node in nodes: self.stream.add_message_to_out_buff(node.get_server_address(), broadcast_packet) pass def handle_packet(self, packet): """ This function act as a wrapper for other handle_###_packet methods to handle the packet. Code design suggestion: 1. It's better to check packet validation right now; For example Validation of the packet length. :param packet: The arrived packet that should be handled. :type packet Packet """ print('this is handle packet') if not self.chenck_in_neighbour(packet.get_source_server_address()): return self.ui.display_pkt(packet) if packet.type == 1: self.__handle_register_packet(packet) return elif packet.type == 2: self.__handle_advertise_packet(packet) pass elif packet.type == 3: self.__handle_join_packet(packet) pass elif packet.type == 4: self.__handle_message_packet(packet) pass elif packet.type == 5: self.__handle_reunion_packet(packet) pass pass def chenck_in_neighbour(self, address): if not self.is_connected: return False if self.address_equal(self.parent, address): return True for c in self.children: if self.address_equal(c, address): return True return False def __check_registered(self, source_address): """ If the Peer is the root of the network we need to find that is a node registered or not. :param source_address: Unknown IP/Port address. :type source_address: tuple :return: """ return self.graph.is_registered(source_address) pass def __handle_advertise_packet(self, packet): """ For advertising peers in the network, It is peer discovery message. Request: We should act as the root of the network and reply with a neighbour address in a new Advertise Response packet. Response: When an Advertise Response packet type arrived we should update our parent peer and send a Join packet to the new parent. Code design suggestion: 1. Start the Reunion daemon thread when the first Advertise Response packet received. 2. When an Advertise Response message arrived, make a new Join packet immediately for the advertised address. Warnings: 1. Don't forget to ignore Advertise Request packets when you are a non-root peer. 2. The addresses which still haven't registered to the network can not request any peer discovery message. 3. Maybe it's not the first time that the source of the packet sends Advertise Request message. This will happen in rare situations like Reunion Failure. Pay attention, don't advertise the address to the packet sender sub-tree. 4. When an Advertise Response packet arrived update our Peer parent for sending Reunion Packets. :param packet: Arrived register packet :type packet Packet :return: """ if (packet.body[0:3] == 'RES') and (not self.is_root): self.children.clear() self.stream.remove_not_reg_nodes() self.parent = Node.parse_address( (packet.body[3:18], packet.body[18:23])) succ = self.stream.add_node(self.parent) if not succ: return join = PacketFactory.new_join_packet(self.address) self.stream.add_message_to_out_buff(self.parent, join) self.is_connected = True self.r_h_b_received = True self.since_last = 0 # now we are connected elif (packet.body[0:3] == 'REQ') and (self.is_root): if not self.graph.is_registered( packet.get_source_server_address()): return address = self.graph.find_live_node( packet.get_source_server_address()).get_address() if address is None: return self.graph.add_node(packet.sender_ip, packet.sender_port, address) res_pkt = PacketFactory.new_advertise_packet( 'RES', self.address, address) self.stream.add_message_to_out_buff( packet.get_source_server_address(), res_pkt) pass def __handle_register_packet(self, packet): """ For registration a new node to the network at first we should make a Node with stream.add_node for'sender' and save it. Code design suggestion: 1.For checking whether an address is registered since now or not you can use SemiNode object except Node. Warnings: 1. Don't forget to ignore Register Request packets when you are a non-root peer. :param packet: Arrived register packet :type packet Packet :return: """ source_address = (packet.sender_ip, packet.sender_port) if self.is_root and (not self.__check_registered(source_address)): self.graph.register(source_address) self.stream.add_node(source_address, True) res_pkt = PacketFactory.new_register_packet('RES', self.address) self.stream.add_message_to_out_buff(source_address, res_pkt) pass def __check_neighbour(self, address): """ It checks is the address in our neighbours array or not. :param address: Unknown address :type address: tuple :return: Whether is address in our neighbours or not. :rtype: bool """ if (address[0] == self.parent[0]) & (address[1] == self.parent[1]): return True for add in self.children: if (address[0] == add[0]) & (address[1] == add[1]): return True return False pass def __handle_message_packet(self, packet): """ Only broadcast message to the other nodes. Warnings: 1. Do not forget to ignore messages from unknown sources. 2. Make sure that you are not sending a message to a register_connection. :param packet: Arrived message packet :type packet Packet :return: """ if not self.child_or_parent(packet.get_source_server_address()): return message = packet.get_body() self.ui.display_message('Broadcast Message: ' + message) res_pkt = PacketFactory.new_message_packet(message, self.address) print('trying to send packet to:') for address in self.children: if not self.address_equal(address, packet.get_source_server_address()): self.stream.add_message_to_out_buff(address, res_pkt) print(address) if (not self.is_root) and (self.parent is not Node): if self.address_equal(self.parent, packet.get_source_server_address()): return print(self.parent) self.stream.add_message_to_out_buff(self.parent, res_pkt) def address_equal(self, address1, address2): address1 = Node.parse_address(address1) address2 = Node.parse_address(address2) return (address1[0] == address2[0]) and (address1[1] == address2[1]) def child_or_parent(self, input): for address in self.children: if self.address_equal(input, address): return True if self.parent is not Node: if self.address_equal(input, self.parent): return True return False pass def __handle_reunion_packet(self, packet): """ In this function we should handle Reunion packet was just arrived. Reunion Hello: If you are root Peer you should answer with a new Reunion Hello Back packet. At first extract all addresses in the packet body and append them in descending order to the new packet. You should send the new packet to the first address in the arrived packet. If you are a non-root Peer append your IP/Port address to the end of the packet and send it to your parent. Reunion Hello Back: Check that you are the end node or not; If not only remove your IP/Port address and send the packet to the next address, otherwise you received your response from the root and everything is fine. Warnings: 1. Every time adding or removing an address from packet don't forget to update Entity Number field. 2. If you are the root, update last Reunion Hello arrival packet from the sender node and turn it on. 3. If you are the end node, update your Reunion mode from pending to acceptance. :param packet: Arrived reunion packet :return: """ nodes = PacketFactory.parse_reunion_packet_body(packet.body) if self.is_root: if packet.body[0:3] == 'RES': return self.graph.reunion_arrived(nodes[0]) nodes.reverse() res_pkt = PacketFactory.new_reunion_packet('RES', self.address, nodes) self.stream.add_message_to_out_buff( packet.get_source_server_address(), res_pkt) print('hello back sent to') print(packet.get_source_server_address()) return else: if packet.body[0:3] == 'REQ': nodes.append(self.address) res_pkt = PacketFactory.new_reunion_packet( 'REQ', self.address, nodes) self.stream.add_message_to_out_buff(self.parent, res_pkt) return else: nodes = PacketFactory.parse_reunion_packet_body(packet.body) if not self.address_equal(nodes[0], self.address): return if len(nodes) == 1: # print('bla bla bla bla bla bla') self.reunion_back_arrived() return # print('kiiiiir') nodes.remove(nodes[0]) res_pkt = PacketFactory.new_reunion_packet( 'RES', self.address, nodes) self.stream.add_message_to_out_buff(nodes[0], res_pkt) return pass def __handle_join_packet(self, packet): """ When a Join packet received we should add a new node to our nodes array. In reality, there is a security level that forbids joining every node to our network. :param packet: Arrived register packet. :type packet Packet :return: """ self.children.append((packet.sender_ip, packet.sender_port)) self.stream.add_node((packet.sender_ip, packet.sender_port)) pass def __get_neighbour(self, sender): """ Finds the best neighbour for the 'sender' from the network_nodes array. This function only will call when you are a root peer. Code design suggestion: 1. Use your NetworkGraph find_live_node to find the best neighbour. :param sender: Sender of the packet :return: The specified neighbour for the sender; The format is like ('192.168.001.001', '05335'). """ pass # def reunion_failed_notify(self): # self.reunion_timer.stop() # self.is_connected = False # pass def send_reunion(self): if not self.is_connected: return pkt = PacketFactory.new_reunion_packet('REQ', self.address, [self.address]) self.stream.add_message_to_out_buff(self.parent, pkt) self.since_last = 0 self.r_h_b_received = False def disconnect(self): self.is_connected = False self.parent = None self.stream.remove_not_reg_nodes() self.stream.clear_in_buff() # maybe we need some more shit def sec_passed(self): print('........') print(self.is_root) print(self.is_connected) print(self.r_h_b_received) print(self.since_last) print('........') self.ui.update_stats() if self.is_connected and (not self.is_root): self.since_last += 1 if (not self.r_h_b_received) and (self.since_last > 50): self.disconnect() return elif (self.r_h_b_received) and (self.since_last > 4): self.send_reunion() if self.is_root: self.graph.increment_time() pass def reunion_back_arrived(self): self.r_h_b_received = True def exit(self): self.timer.stop = True self.stream.remove_not_reg_nodes()