|
@@ -5,6 +5,12 @@ from Attack.MembersMgmtCommAttack import Message
|
|
|
# needed because of machine inprecision. E.g A time difference of 0.1s is stored as >0.1s
|
|
|
EPS_TOLERANCE = 1e-13 # works for a difference of 0.1, no less
|
|
|
|
|
|
+def greater_than(a: float, b: float):
|
|
|
+ """
|
|
|
+ A greater than operator desgined to handle slight machine inprecision up to EPS_TOLERANCE.
|
|
|
+ :return: True if a > b, otherwise False
|
|
|
+ """
|
|
|
+ return b - a < -EPS_TOLERANCE
|
|
|
|
|
|
class CommunicationProcessor():
|
|
|
"""
|
|
@@ -24,146 +30,216 @@ class CommunicationProcessor():
|
|
|
:param mapped_ids: the chosen IDs
|
|
|
"""
|
|
|
self.packets = packets
|
|
|
- self.local_init_ids = set(mapped_ids.keys())
|
|
|
+ self.local_init_ids = set(mapped_ids)
|
|
|
|
|
|
def find_interval_most_comm(self, number_ids: int, max_int_time: float):
|
|
|
+ if self.nat:
|
|
|
+ return self._find_interval_most_comm_nat(number_ids, max_int_time)
|
|
|
+ else:
|
|
|
+ return self._find_interval_most_comm_nonat(number_ids, max_int_time)
|
|
|
+
|
|
|
+
|
|
|
+ def _find_interval_most_comm_nonat(self, number_ids: int, max_int_time: float):
|
|
|
"""
|
|
|
- Finds a time interval of the given seconds where the given number of IDs commuicate the most.
|
|
|
- If NAT is active, the most communication is restricted to the most communication by the given number of initiating IDs.
|
|
|
- If NAT is inactive, the intervall the most overall communication, that has at least the given number of initiating IDs in it, is chosen.
|
|
|
-
|
|
|
- :param number_ids: The number of IDs that are to be considered
|
|
|
- :param max_int_time: A short description of the attack.
|
|
|
- :return: A triple consisting of the IDs, as well as start and end idx with respect to the given packets.
|
|
|
+ Finds the time interval(s) of the given seconds with the most overall communication (i.e. requests and responses)
|
|
|
+ that has at least number_ids communication initiators in it.
|
|
|
+ :param number_ids: The number of initiator IDs that have to exist in the interval(s)
|
|
|
+ :param max_int_time: The maximum time period of the interval
|
|
|
+ :return: A list of triples, where each triple contains the initiator IDs, the start index and end index
|
|
|
+ of the respective interval in that order. The indices are with respect to self.packets
|
|
|
"""
|
|
|
+
|
|
|
+ # setup initial variables
|
|
|
packets = self.packets
|
|
|
mtypes = self.mtypes
|
|
|
+ idx_low, idx_high = 0, 0 # the indices spanning the interval
|
|
|
+ comm_sum = 0 # the communication sum of the current interval
|
|
|
+ cur_highest_sum = 0 # the highest communication sum seen so far
|
|
|
+ ids = [] # the initiator IDs seen in the current interval in order of appearance
|
|
|
+ possible_intervals = [] # all intervals that have cur_highest_sum of communication and contain enough IDs
|
|
|
+
|
|
|
+ # Iterate over all packets from start to finish and process the info of each packet.
|
|
|
+ # Similar to a Sliding Window approach.
|
|
|
+ while True:
|
|
|
+ if idx_high < len(packets):
|
|
|
+ cur_int_time = float(packets[idx_high]["Time"]) - float(packets[idx_low]["Time"])
|
|
|
+
|
|
|
+ # if current interval time exceeds maximum time period, process information of the current interval
|
|
|
+ if greater_than(cur_int_time, max_int_time) or idx_high >= len(packets):
|
|
|
+ interval_ids = set(ids)
|
|
|
+ # if the interval contains enough initiator IDs, add it to possible_intervals
|
|
|
+ if len(interval_ids) >= number_ids:
|
|
|
+ interval = {"IDs": sorted(interval_ids), "Start": idx_low, "End": idx_high-1}
|
|
|
+ # reset possible intervals if new maximum of communication is found
|
|
|
+ if comm_sum > cur_highest_sum:
|
|
|
+ possible_intervals = [interval]
|
|
|
+ cur_highest_sum = comm_sum
|
|
|
+ # append otherwise
|
|
|
+ elif comm_sum == cur_highest_sum:
|
|
|
+ possible_intervals.append(interval)
|
|
|
+
|
|
|
+ # stop if all packets have been processed
|
|
|
+ if idx_high >= len(packets):
|
|
|
+ break
|
|
|
|
|
|
- def get_nez_comm_counts(comm_counts: dict):
|
|
|
- """
|
|
|
- Filters out all msg_counts that have 0 as value
|
|
|
- """
|
|
|
- nez_comm_counts = dict()
|
|
|
- for id_ in comm_counts.keys():
|
|
|
- count = comm_counts[id_]
|
|
|
- if count > 0:
|
|
|
- nez_comm_counts[id_] = count
|
|
|
- return nez_comm_counts
|
|
|
-
|
|
|
- def greater_than(a: float, b: float):
|
|
|
+ # let idx_low "catch up" so that the current interval time fits into the maximum time period again
|
|
|
+ while greater_than(cur_int_time, max_int_time):
|
|
|
+ cur_packet = packets[idx_low]
|
|
|
+ # if message was no timeout, delete the first appearance of the initiator ID
|
|
|
+ # of this packet from the initiator list and update comm_sum
|
|
|
+ if mtypes[int(cur_packet["Type"])] != MessageType.TIMEOUT:
|
|
|
+ comm_sum -= 1
|
|
|
+ del ids[0]
|
|
|
+
|
|
|
+ idx_low += 1
|
|
|
+ cur_int_time = float(packets[idx_high]["Time"]) - float(packets[idx_low]["Time"])
|
|
|
+
|
|
|
+ # consume the new packet at idx_high and process its information
|
|
|
+ cur_packet = packets[idx_high]
|
|
|
+ cur_mtype = mtypes[int(cur_packet["Type"])]
|
|
|
+ # if message is request, add src to initiator list
|
|
|
+ if MessageType.is_request(cur_mtype):
|
|
|
+ ids.append(cur_packet["Src"])
|
|
|
+ comm_sum += 1
|
|
|
+ # if message is response, add dst to initiator list
|
|
|
+ elif MessageType.is_response(cur_mtype):
|
|
|
+ ids.append(cur_packet["Dst"])
|
|
|
+ comm_sum += 1
|
|
|
+
|
|
|
+ idx_high += 1
|
|
|
+
|
|
|
+ return possible_intervals
|
|
|
+
|
|
|
+
|
|
|
+ def _find_interval_most_comm_nat(self, number_ids: int, max_int_time: float):
|
|
|
+ """
|
|
|
+ Finds the time interval(s) of the given seconds with the most communication (i.e. requests and responses)
|
|
|
+ by the most number_ids communicative initiator IDs of the interval.
|
|
|
+ :param number_ids: The number of initiator IDs that have to exist in the interval(s)
|
|
|
+ :param max_int_time: The maximum time period of the interval
|
|
|
+ :return: A list of triples, where each triple contains the initiator IDs, the start index and the end index
|
|
|
+ of the respective interval in that order. The indices are with respect to self.packets
|
|
|
+ """
|
|
|
+
|
|
|
+ def get_nez_comm_amounts():
|
|
|
"""
|
|
|
- A greater than operator desgined to handle slight machine inprecision up to EPS_TOLERANCE.
|
|
|
- :return: True if a > b, otherwise False
|
|
|
+ Filters out all comm_amounts that have 0 as value.
|
|
|
+
|
|
|
+ :return: a dict with initiator IDs as keys and their non-zero communication amount as value
|
|
|
"""
|
|
|
- return b - a < -EPS_TOLERANCE
|
|
|
|
|
|
- def change_comm_counts(comm_counts: dict, idx: int, add=True):
|
|
|
+ nez_comm_amounts = dict()
|
|
|
+ # Iterate over comm_amounts dict and add every entry
|
|
|
+ # with non-zero comm_amount to new dict.
|
|
|
+ for id_ in comm_amounts:
|
|
|
+ amount = comm_amounts[id_]
|
|
|
+ if amount > 0:
|
|
|
+ nez_comm_amounts[id_] = amount
|
|
|
+ return nez_comm_amounts
|
|
|
+
|
|
|
+ def change_comm_amounts(packet: dict, add:bool=True):
|
|
|
"""
|
|
|
- Changes the communication count, stored in comm_counts, of the initiating ID with respect to the
|
|
|
- packet specified by the given index. If add is True, 1 is added to the value, otherwise 1 is subtracted.
|
|
|
+ Changes the communication amount, stored in comm_amounts, of the initiating ID with respect to the
|
|
|
+ packet specified by the given index.
|
|
|
+
|
|
|
+ :param packet: the packet to be processed, containing src and dst ID
|
|
|
+ :param add: If add is True, 1 is added to the communication amount of the IDs, otherwise 1 is subtracted
|
|
|
"""
|
|
|
+
|
|
|
change = 1 if add else -1
|
|
|
- mtype = mtypes[int(packets[idx]["Type"])]
|
|
|
- id_src, id_dst = packets[idx]["Src"], packets[idx]["Dst"]
|
|
|
- if mtype in {MessageType.SALITY_HELLO, MessageType.SALITY_NL_REQUEST}:
|
|
|
- if id_src in comm_counts:
|
|
|
- comm_counts[id_src] += change
|
|
|
+ mtype = mtypes[int(packet["Type"])]
|
|
|
+ id_src, id_dst = packet["Src"], packet["Dst"]
|
|
|
+ # if message is request, src is initiator
|
|
|
+ if MessageType.is_request(mtype):
|
|
|
+ # if src exists in comm_amounts, add 1 to its amount
|
|
|
+ if id_src in comm_amounts:
|
|
|
+ comm_amounts[id_src] += change
|
|
|
+ # else if op is add, add the ID with comm value 1 to comm_amounts
|
|
|
elif change > 0:
|
|
|
- comm_counts[id_src] = 1
|
|
|
- elif mtype in {MessageType.SALITY_HELLO_REPLY, MessageType.SALITY_NL_REPLY}:
|
|
|
- if id_dst in comm_counts:
|
|
|
- comm_counts[id_dst] += change
|
|
|
+ comm_amounts[id_src] = 1
|
|
|
+ # if message is response, dst is initiator
|
|
|
+ elif MessageType.is_response(mtype):
|
|
|
+ # if src exists in comm_amounts, add 1 to its amount
|
|
|
+ if id_dst in comm_amounts:
|
|
|
+ comm_amounts[id_dst] += change
|
|
|
+ # else if op is add, add the ID with comm value 1 to comm_amounts
|
|
|
elif change > 0:
|
|
|
- comm_counts[id_dst] = 1
|
|
|
+ comm_amounts[id_dst] = 1
|
|
|
|
|
|
- def get_comm_count_first_ids(comm_counts: list):
|
|
|
+ def get_comm_amount_first_ids():
|
|
|
"""
|
|
|
- Finds the IDs that communicate among themselves the most with respect to the given message counts.
|
|
|
- :param msg_counts: a sorted list of message counts where each entry is a tuple of key and value
|
|
|
- :return: The picked IDs and their total message count as a tuple
|
|
|
+ Finds the number_ids IDs that communicate the most with respect to nez_comm_amounts
|
|
|
+ :return: The picked IDs as a list and their summed message amount as a tuple like (IDs, sum).
|
|
|
"""
|
|
|
- # if order of most messages is important, use an additional list
|
|
|
- picked_ids = {}
|
|
|
- total_comm_count = 0
|
|
|
|
|
|
- # iterate over every message count
|
|
|
- for i, comm in enumerate(comm_counts):
|
|
|
+ picked_ids = [] # the IDs that have been picked
|
|
|
+ summed_comm_amount = 0 # the summed communication amount of all picked IDs
|
|
|
+ # sort the comm amounts to easily access the IDs with the most communication
|
|
|
+ sorted_comm_amounts = sorted(nez_comm_amounts.items(), key=lambda x: x[1], reverse=True)
|
|
|
+
|
|
|
+ # iterate over the sorted communication amounts
|
|
|
+ for id_, amount in sorted_comm_amounts:
|
|
|
count_picked_ids = len(picked_ids)
|
|
|
|
|
|
# if enough IDs have been found, stop
|
|
|
if count_picked_ids >= number_ids:
|
|
|
break
|
|
|
|
|
|
- picked_ids[comm[0]] = comm[1]
|
|
|
- total_comm_count += comm[1]
|
|
|
-
|
|
|
- return picked_ids, total_comm_count
|
|
|
-
|
|
|
+ # else pick this ID
|
|
|
+ picked_ids.append(id_)
|
|
|
+ summed_comm_amount += amount
|
|
|
|
|
|
- # first find all possible intervals that contain enough IDs that initiate communication
|
|
|
- idx_low, idx_high = 0, 0
|
|
|
- comm_counts = {}
|
|
|
- possible_intervals = []
|
|
|
- general_comm_sum, cur_highest_sum = 0, 0
|
|
|
+ return picked_ids, summed_comm_amount
|
|
|
|
|
|
- # Iterate over all packets from start to finish and process the info of each packet
|
|
|
- # If time of packet within time interval, update the message count for this communication
|
|
|
- # If time of packet exceeds time interval, substract from the message count for this communication
|
|
|
- # Similar to a Sliding Window approach
|
|
|
+ # setup initial variables
|
|
|
+ packets = self.packets
|
|
|
+ mtypes = self.mtypes
|
|
|
+ idx_low, idx_high = 0, 0 # the indices spanning the interval
|
|
|
+ cur_highest_sum = 0 # the highest communication sum seen so far
|
|
|
+ # a dict containing information about what initiator ID has communicated how much
|
|
|
+ comm_amounts = {} # entry is a tuple of (ID, amount)
|
|
|
+ possible_intervals = [] # all intervals that have cur_highest_sum of communication and contain enough IDs
|
|
|
+
|
|
|
+ # Iterate over all packets from start to finish and process the info of each packet.
|
|
|
+ # Similar to a Sliding Window approach.
|
|
|
while True:
|
|
|
if idx_high < len(packets):
|
|
|
cur_int_time = float(packets[idx_high]["Time"]) - float(packets[idx_low]["Time"])
|
|
|
|
|
|
- # if current interval time exceeds time interval, save the message counts if appropriate, or stop if no more packets
|
|
|
+ # if current interval time exceeds maximum time period, process information of the current interval
|
|
|
if greater_than(cur_int_time, max_int_time) or idx_high >= len(packets):
|
|
|
- # get all message counts for communications that took place in the current intervall
|
|
|
- nez_comm_counts = get_nez_comm_counts(comm_counts)
|
|
|
- # if we have enough IDs as specified by the caller, mark as possible interval
|
|
|
- if len(nez_comm_counts) >= number_ids:
|
|
|
- if self.nat:
|
|
|
- possible_intervals.append((nez_comm_counts, idx_low, idx_high-1))
|
|
|
- elif general_comm_sum >= cur_highest_sum:
|
|
|
- cur_highest_sum = general_comm_sum
|
|
|
- possible_intervals.append({"IDs": nez_comm_counts, "CommSum": general_comm_sum, "Start": idx_low, "End": idx_high-1})
|
|
|
- general_comm_sum = 0
|
|
|
-
|
|
|
+ # filter out all IDs with a zero amount of communication for the current interval
|
|
|
+ nez_comm_amounts = get_nez_comm_amounts()
|
|
|
+ # if the interval contains enough initiator IDs, add it to possible_intervals
|
|
|
+ if len(nez_comm_amounts) >= number_ids:
|
|
|
+ # pick the most communicative IDs and store their sum of communication
|
|
|
+ picked_ids, comm_sum = get_comm_amount_first_ids()
|
|
|
+ interval = {"IDs": picked_ids, "Start": idx_low, "End": idx_high-1}
|
|
|
+
|
|
|
+ # reset possible intervals if new maximum of communication is found
|
|
|
+ if comm_sum > cur_highest_sum:
|
|
|
+ possible_intervals = [interval]
|
|
|
+ cur_highest_sum = comm_sum
|
|
|
+ # append otherwise
|
|
|
+ elif comm_sum == cur_highest_sum:
|
|
|
+ possible_intervals.append(interval)
|
|
|
+
|
|
|
+ # stop if all packets have been processed
|
|
|
if idx_high >= len(packets):
|
|
|
break
|
|
|
|
|
|
- # let idx_low "catch up" so that the current interval time fits into the interval time specified by the caller
|
|
|
+ # let idx_low "catch up" so that the current interval time fits into the maximum time period again
|
|
|
while greater_than(cur_int_time, max_int_time):
|
|
|
- change_comm_counts(comm_counts, idx_low, add=False)
|
|
|
+ # adjust communication amounts to discard the earliest packet of the current interval
|
|
|
+ change_comm_amounts(packets[idx_low], add=False)
|
|
|
idx_low += 1
|
|
|
cur_int_time = float(packets[idx_high]["Time"]) - float(packets[idx_low]["Time"])
|
|
|
|
|
|
# consume the new packet at idx_high and process its information
|
|
|
- change_comm_counts(comm_counts, idx_high)
|
|
|
+ change_comm_amounts(packets[idx_high])
|
|
|
idx_high += 1
|
|
|
- general_comm_sum += 1
|
|
|
|
|
|
- if self.nat:
|
|
|
- # now find the interval in which as many IDs as specified communicate the most in the given time interval
|
|
|
- summed_intervals = []
|
|
|
- sum_intervals_idxs = []
|
|
|
- cur_highest_sum = 0
|
|
|
-
|
|
|
- # for every interval compute the sum of id_counts of the first most communicative IDs and eventually find
|
|
|
- # the interval(s) with most communication and its IDs
|
|
|
- # on the side also store the communication count of the individual IDs
|
|
|
- for j, interval in enumerate(possible_intervals):
|
|
|
- comm_counts = interval[0].items()
|
|
|
- sorted_comm_counts = sorted(comm_counts, key=lambda x: x[1], reverse=True)
|
|
|
- picked_ids, comm_sum = get_comm_count_first_ids(sorted_comm_counts)
|
|
|
-
|
|
|
- if comm_sum == cur_highest_sum:
|
|
|
- summed_intervals.append({"IDs": picked_ids, "CommSum": comm_sum, "Start": interval[1], "End": interval[2]})
|
|
|
- elif comm_sum > cur_highest_sum:
|
|
|
- summed_intervals = []
|
|
|
- summed_intervals.append({"IDs": picked_ids, "CommSum": comm_sum, "Start": interval[1], "End": interval[2]})
|
|
|
- cur_highest_sum = comm_sum
|
|
|
- return summed_intervals
|
|
|
- else:
|
|
|
- return possible_intervals
|
|
|
+ return possible_intervals
|
|
|
|
|
|
|
|
|
def det_id_roles_and_msgs(self):
|