From 04f6fb60026976e04a941b7c8d86a31a742b18af Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Tue, 29 Dec 2020 13:48:55 +0100 Subject: [PATCH] Implemented the inundation, strange type error encountered when testing --- squinnondation/hazel.py | 89 ++++++++++++++++++++++++++++++++++++-- squinnondation/messages.py | 14 ++++-- 2 files changed, 95 insertions(+), 8 deletions(-) diff --git a/squinnondation/hazel.py b/squinnondation/hazel.py index 58bc52a..1071a14 100644 --- a/squinnondation/hazel.py +++ b/squinnondation/hazel.py @@ -1,7 +1,7 @@ # Copyright (C) 2020 by eichhornchen, ÿnérant # SPDX-License-Identifier: GPL-3.0-or-later from datetime import datetime -from random import randint +from random import randint, uniform from typing import Any, Tuple #from ipaddress import IPv6Address from threading import Thread, RLock @@ -61,6 +61,7 @@ class Squirrel(Hazelnut): self.history = [] self.received_messages = dict() + self.recent_messages = dict() #of the form [Pkt(DataTLV), date of first reception, dict(neighbour, date of the next send, nb of times it has already been sent)] self.history_pad = curses.newpad(curses.LINES - 2, curses.COLS) self.input_pad = curses.newpad(1, curses.COLS) self.emoji_pad = curses.newpad(18, 12) @@ -75,7 +76,7 @@ class Squirrel(Hazelnut): self.activehazelnuts = dict() #of the form [hazelnut, time of last #hello, time of last long hello, is symmetric] self.nbNS = 0 - self.minNS = 3 #minimal number of symetric neighbours a squirrel needs + self.minNS = 3 #minimal number of symmetric neighbours a squirrel needs #to have. self.add_system_message(f"Listening on {self.address}:{self.port}") @@ -294,18 +295,93 @@ class Squirrel(Hazelnut): if self.last_line == len(self.history) - 2: self.last_line += 1 - def receive_message_from(self, msg: str, sender_id: int, nonce: int) -> bool: + def receive_message_from(self, tlv: DataTLV, msg: str, sender_id: int, nonce: int, relay: Hazelnut) -> bool: """ This method is called by a DataTLV, sent by a real person. This add the message in the history if not already done. Returns True iff the message was not already received previously. """ + if (sender_id, nonce) not in self.recent_messages: + #If it is a new message, add it to recent_messages + d = self.make_inundation_dict() + pkt = Packet().construct(tlv) + self.recent_messages[(sender_id, nonce)] = [pkt, time.time(), d] + + #in all cases, remove the sender from the list of neighbours to be inundated + self.remove_from_inundation(relay, sender_id, nonce) + if (sender_id, nonce) in self.received_messages: return False - self.add_message(msg) + self.add_message(msg) #for display purposes self.received_messages[(sender_id, nonce)] = Message(msg, sender_id, nonce) return True + + def make_inundation_dict(self) -> dict: + """ + Takes the activehazels dictionnary and returns a list of [hazel, date+random, 0] + """ + res = dict() + l = list(self.activehazelnuts.items()) + for key, hazel in l: + if hazel[3]: #Only if the neighbour is symmetric + next_send = uniform(1, 2) + res[key] = [hazel[0], time.time()+next_send, 0] + return res + + def remove_from_inundation(self, hazel: Hazelnut, sender_id: int, nonce: int) -> None: + """ + Remove the sender from the list of neighbours to be inundated + """ + if (sender_id, nonce) in self.recent_messages: + #If a peer is late in its acknowledgement, the absence of the previous if causes an error. + self.recent_messages[(sender_id, nonce)][2].pop((hazel.address, hazel.port), None) + + if not self.recent_messages[(sender_id, nonce)][2] : #If dictionnary is empty, remove the message + self.recent_messages.pop((sender_id, nonce), None) + + def clean_inundation(self): + """ + Remove messages which are overdue (older than 2 minutes) from the inundation dictionnary. + """ + self.refresh_lock.acquire() + + for key in self.recent_messages: + if time.time()-self.recent_messages[key][1] > 120: + self.recent_messages.pop(key) + + self.refresh_lock.release() + + def main_inundation(self): + """ + The main inundation function. + """ + self.refresh_lock.acquire() + + for key in self.recent_messages: + for key2 in self.recent_messages[key][2]: + if time.time()-self.recent_messages[key][2][key2][1] >= 0: + #send the packet if it is overdue + self.send_packet(self.recent_messages[key][2][key2][0], self.recent_messages[key][0]) + + a = self.recent_messages[key][2][key2][2] + + if a==5: #the neighbour is not reactive enough + gatlv = GoAwayTLV().construct(GoAwayType.TIMEOUT, "No acknowledge") + pkt = Packet().construct(gatlv) + self.send_packet( + self.recent_messages[key][2][key2][0], pkt) + self.activehazelnuts.pop(key2) + self.potentialhazelnuts[key2] = self.recent_messages[key][2][key2][0] + self.recent_messages[key][2].pop(key2) + + #change the time until the next send + self.recent_messages[key][2][key2][2] = a+1 + next_send = uniform(2**(a-1), 2**a) + self.recent_messages[key][2][key2][1] = time.time()+next_send + + + self.refresh_lock.release() def add_system_message(self, msg: str) -> None: """ @@ -649,6 +725,11 @@ class Inondator(Thread): def run(self) -> None: while True: + #clean the dictionnary + self.squirrel.clean_inundation() + + #inundate + self.squirrel.main_inundation() class Message: diff --git a/squinnondation/messages.py b/squinnondation/messages.py index 7207532..c9fd962 100644 --- a/squinnondation/messages.py +++ b/squinnondation/messages.py @@ -165,8 +165,11 @@ class HelloTLV(TLV): timeHL = squirrel.activehazelnuts[(sender.address, sender.port)] if self.is_long and self.dest_id == squirrel.id : timeHL = time.time() + + #Make sure the sender is not in the potential hazelnuts squirrel.remove_from_potential(sender.address, sender.port) - + + #Add entry to/actualize the active hazelnuts dictionnary squirrel.activehazelnuts[(sender.address, sender.port)] = [sender, timeH,\ timeHL, True] squirrel.nbNS += 1 @@ -260,7 +263,7 @@ class DataTLV(TLV): # Acknowledge the packet squirrel.send_packet(sender, Packet.construct(AckTLV.construct(self.sender_id, self.nonce))) - if not squirrel.receive_message_from(msg, self.sender_id, self.nonce): + if not squirrel.receive_message_from(self, msg, self.sender_id, self.nonce, sender): # The message was already received, do not print it return @@ -312,8 +315,11 @@ class AckTLV(TLV): socket.htonl(self.nonce).to_bytes(4, sys.byteorder) def handle(self, squirrel: Any, sender: Any) -> None: - # TODO Implement AckTLV - squirrel.add_system_message("I received an AckTLV. I don't know what to do with it. Please implement me!") + """ + When an AckTLV is received, we know that we do not have to inundate that neighbour anymore. + """ + squirrel.add_system_message("I received an AckTLV") + squirrel.remove_from_inundation(sender, self.sender_id, self.nonce) @staticmethod def construct(sender_id: int, nonce: int) -> "AckTLV":