CommunicationProcessor.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. from lea import Lea
  2. from Attack.MembersMgmtCommAttack import MessageType
  3. from Attack.MembersMgmtCommAttack import Message
  4. # needed because of machine inprecision. E.g A time difference of 0.1s is stored as >0.1s
  5. EPS_TOLERANCE = 1e-13 # works for a difference of 0.1, no less
  6. def greater_than(a: float, b: float):
  7. """
  8. A greater than operator desgined to handle slight machine inprecision up to EPS_TOLERANCE.
  9. :return: True if a > b, otherwise False
  10. """
  11. return b - a < -EPS_TOLERANCE
  12. class CommunicationProcessor():
  13. """
  14. Class to process parsed input CSV/XML data and retrieve a mapping or other information.
  15. """
  16. def __init__(self, packets:list, mtypes:dict, nat:bool):
  17. """
  18. Creates an instance of CommunicationProcessor.
  19. :param packets: the list of abstract packets
  20. :param mtypes: a dict containing an int to EnumType mapping of MessageTypes
  21. :param nat: whether NAT is present in this network
  22. """
  23. self.packets = packets
  24. self.mtypes = mtypes
  25. self.nat = nat
  26. def set_mapping(self, packets: list, mapped_ids: dict):
  27. """
  28. Set the selected mapping for this communication processor.
  29. :param packets: all packets contained in the mapped time frame
  30. :param mapped_ids: the chosen IDs
  31. """
  32. self.packets = packets
  33. self.local_init_ids = set(mapped_ids)
  34. def det_id_roles_and_msgs(self):
  35. """
  36. Determine the role of every mapped ID. The role can be initiator, responder or both.
  37. On the side also connect corresponding messages together to quickly find out
  38. which reply belongs to which request and vice versa.
  39. :return: the selected messages
  40. """
  41. mtypes = self.mtypes
  42. # setup initial variables and their values
  43. respnd_ids = set()
  44. # msgs --> the filtered messages, msg_id --> an increasing ID to give every message an artificial primary key
  45. msgs, msg_id = [], 0
  46. # keep track of previous request to find connections
  47. prev_reqs = {}
  48. # used to determine whether a request has been seen yet, so that replies before the first request are skipped and do not throw an error by
  49. # accessing the empty dict prev_reqs (this is not a perfect solution, but it works most of the time)
  50. req_seen = False
  51. local_init_ids = self.local_init_ids
  52. external_init_ids = set()
  53. # process every packet individually
  54. for packet in self.packets:
  55. id_src, id_dst, msg_type, time = packet["Src"], packet["Dst"], int(packet["Type"]), float(packet["Time"])
  56. lineno = packet.get("LineNumber", -1)
  57. # if if either one of the IDs is not mapped, continue
  58. if (id_src not in local_init_ids) and (id_dst not in local_init_ids):
  59. continue
  60. # convert message type number to enum type
  61. msg_type = mtypes[msg_type]
  62. # process a request
  63. if msg_type in {MessageType.SALITY_HELLO, MessageType.SALITY_NL_REQUEST}:
  64. if not self.nat and id_dst in local_init_ids and id_src not in local_init_ids:
  65. external_init_ids.add(id_src)
  66. elif id_src not in local_init_ids:
  67. continue
  68. else:
  69. # process ID's role
  70. respnd_ids.add(id_dst)
  71. # convert the abstract message into a message object to handle it better
  72. msg_str = "{0}-{1}".format(id_src, id_dst)
  73. msg = Message(msg_id, id_src, id_dst, msg_type, time, line_no = lineno)
  74. msgs.append(msg)
  75. prev_reqs[msg_str] = msg_id
  76. msg_id += 1
  77. req_seen = True
  78. # process a reply
  79. elif msg_type in {MessageType.SALITY_HELLO_REPLY, MessageType.SALITY_NL_REPLY} and req_seen:
  80. if not self.nat and id_src in local_init_ids and id_dst not in local_init_ids:
  81. # process ID's role
  82. external_init_ids.add(id_dst)
  83. elif id_dst not in local_init_ids:
  84. continue
  85. else:
  86. # process ID's role
  87. respnd_ids.add(id_src)
  88. # convert the abstract message into a message object to handle it better
  89. msg_str = "{0}-{1}".format(id_dst, id_src)
  90. # find the request message ID for this response and set its reference index
  91. refer_idx = prev_reqs[msg_str]
  92. msgs[refer_idx].refer_msg_id = msg_id
  93. msg = Message(msg_id, id_src, id_dst, msg_type, time, refer_idx, lineno)
  94. msgs.append(msg)
  95. # remove the request to this response from storage
  96. del(prev_reqs[msg_str])
  97. msg_id += 1
  98. elif msg_type == MessageType.TIMEOUT and id_src in local_init_ids and not self.nat:
  99. # convert the abstract message into a message object to handle it better
  100. msg_str = "{0}-{1}".format(id_dst, id_src)
  101. # find the request message ID for this response and set its reference index
  102. refer_idx = prev_reqs.get(msg_str)
  103. if refer_idx is not None:
  104. msgs[refer_idx].refer_msg_id = msg_id
  105. if msgs[refer_idx].type == MessageType.SALITY_NL_REQUEST:
  106. msg = Message(msg_id, id_src, id_dst, MessageType.SALITY_NL_REPLY, time, refer_idx, lineno)
  107. else:
  108. msg = Message(msg_id, id_src, id_dst, MessageType.SALITY_HELLO_REPLY, time, refer_idx, lineno)
  109. msgs.append(msg)
  110. # remove the request to this response from storage
  111. del(prev_reqs[msg_str])
  112. msg_id += 1
  113. # store the retrieved information in this object for later use
  114. self.respnd_ids = sorted(respnd_ids)
  115. self.external_init_ids = sorted(external_init_ids)
  116. self.messages = msgs
  117. # return the selected messages
  118. return self.messages
  119. def det_ext_and_local_ids(self, prob_rspnd_local: int=0):
  120. """
  121. Map the given IDs to a locality (i.e. local or external} considering the given probabilities.
  122. :param comm_type: the type of communication (i.e. local, external or mixed)
  123. :param prob_rspnd_local: the probabilty that a responder is local
  124. """
  125. external_ids = set()
  126. local_ids = self.local_init_ids.copy()
  127. # set up probabilistic chooser
  128. rspnd_locality = Lea.fromValFreqsDict({"local": prob_rspnd_local*100, "external": (1-prob_rspnd_local)*100})
  129. for id_ in self.external_init_ids:
  130. external_ids.add(id_)
  131. # determine responder localities
  132. for id_ in self.respnd_ids:
  133. if id_ in local_ids or id_ in external_ids:
  134. continue
  135. pos = rspnd_locality.random()
  136. if pos == "local":
  137. local_ids.add(id_)
  138. elif pos == "external":
  139. external_ids.add(id_)
  140. self.local_ids, self.external_ids = local_ids, external_ids
  141. return self.local_ids, self.external_ids