triggerlist.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. """TriggerList for handling dynamic headers."""
  2. import logging
  3. logger = logging.getLogger("pypacker")
  4. class TriggerList(list):
  5. """
  6. List with trigger-capabilities representing a Packet header.
  7. This list can contain one type of raw bytes, tuples or packets representing an individual
  8. header field. Using bytes or tuples "_pack()" can be overwritten to reassemble bytes.
  9. """
  10. def __init__(self, packet, dissect_callback=None, buffer=b""):
  11. """
  12. packet -- packet where this TriggerList gets ingegrated
  13. dissect_callback -- callback which dessects byte string "buffer"
  14. buffer -- byte string to be dissected
  15. """
  16. # set by external Packet
  17. # logger.debug(">>> init of TriggerList (contained in %s): %s" % (packet.__class__.__name__, buffer))
  18. self._packet = packet
  19. self._dissect_callback = dissect_callback
  20. self._cached_result = buffer
  21. def _lazy_dissect(self):
  22. if not self._packet._unpacked and self._packet._unpacked is not None:
  23. # Before changing TriggerList we need to unpack or
  24. # cached header won't fit on _unpack(...)
  25. # This is called before any changes to TriggerList so place it here.
  26. # Ignore if TriggerList changed in _dissect (_unpacked is None)
  27. self._packet._unpack()
  28. if self._dissect_callback is None:
  29. # already dissected, ignore
  30. return
  31. initial_list_content = self._dissect_callback(self._cached_result)
  32. self._dissect_callback = None
  33. super().extend(initial_list_content)
  34. # Python predefined overwritten methods
  35. def __getitem__(self, pos):
  36. self._lazy_dissect()
  37. return super().__getitem__(pos)
  38. def __iadd__(self, v):
  39. """Item can be added using '+=', use 'append()' instead."""
  40. self._lazy_dissect()
  41. super().__iadd__(v)
  42. self.__refresh_listener([v])
  43. return self
  44. def __setitem__(self, k, v):
  45. self._lazy_dissect()
  46. try:
  47. # remove listener from old packet which gets overwritten
  48. self[k].remove_change_listener(None, remove_all=True)
  49. except:
  50. pass
  51. super().__setitem__(k, v)
  52. self.__refresh_listener([v])
  53. def __delitem__(self, k):
  54. # logger.debug("removing elements: %r" % k)
  55. self._lazy_dissect()
  56. if type(k) is int:
  57. itemlist = [self[k]]
  58. else:
  59. # assume slice: [x:y]
  60. itemlist = self[k]
  61. super().__delitem__(k)
  62. # logger.debug("removed, handle mod")
  63. self.__refresh_listener(itemlist, add_listener=False)
  64. # logger.debug("finished removing")
  65. def __len__(self):
  66. self._lazy_dissect()
  67. return super().__len__()
  68. def append(self, v):
  69. self._lazy_dissect()
  70. super().append(v)
  71. # logger.debug("handling mod")
  72. self.__refresh_listener([v])
  73. # logger.debug("finished")
  74. def extend(self, v):
  75. self._lazy_dissect()
  76. super().extend(v)
  77. self.__refresh_listener(v)
  78. def insert(self, pos, v):
  79. self._lazy_dissect()
  80. super().insert(pos, v)
  81. self.__refresh_listener([v])
  82. # TODO: pop(...) needed?
  83. def __refresh_listener(self, val, add_listener=True):
  84. """
  85. Handle modifications of this TriggerList (adding, removing, ...).
  86. val -- list of bytes, tuples or packets
  87. add_listener -- re-add listener if True
  88. """
  89. try:
  90. for v in val:
  91. # react on changes of packets in this triggerlist
  92. v._remove_change_listener(None, remove_all=True)
  93. if add_listener:
  94. v._add_change_listener(self._notify_change)
  95. except AttributeError as e:
  96. # this will fail if val is not a packet
  97. # logger.debug(e)
  98. pass
  99. self._notify_change()
  100. # logger.debug("handle mod sub: finished")
  101. def _notify_change(self):
  102. """
  103. Update _header_changed of and _header_format_changed of the Packet having
  104. this TriggerList as field and _cached_result.
  105. Called by: this list on changes or Packets in this list
  106. """
  107. try:
  108. self._packet._header_changed = True
  109. self._packet._header_format_changed = True
  110. # logger.debug(">>> TriggerList changed!!!")
  111. except AttributeError as e:
  112. # this only works on Packets
  113. # logger.debug(e)
  114. pass
  115. # list changed: old cache of TriggerList not usable anymore
  116. self._cached_result = None
  117. __TYPES_TRIGGERLIST_SIMPLE = set([bytes, tuple])
  118. def bin(self):
  119. """
  120. Output the TriggerLists elements as concatenated bytestring.
  121. Custom implementations can be set by overwriting _pack().
  122. """
  123. if self._cached_result is None:
  124. try:
  125. self._cached_result = self._pack()
  126. except:
  127. # logger.debug(self)
  128. # logger.debug("packing packets")
  129. # logger.debug([pkt.bin() for pkt in self])
  130. self._cached_result = b"".join([pkt.bin() for pkt in self])
  131. # logger.debug("new cached result: %s" % self._cached_result)
  132. return self._cached_result
  133. def find_pos(self, search_cb, offset=0):
  134. """
  135. Find an item-position giving search callback as search criteria.
  136. search_cb -- callback to compare values, signature: callback(value) [True|False]
  137. Return True to return value found.
  138. offset -- start at index "offset" to search
  139. return -- index of first element found or None
  140. """
  141. self._lazy_dissect()
  142. while offset < len(self):
  143. try:
  144. if search_cb(self[offset]):
  145. return offset
  146. except:
  147. # error on callback (unknown fields etc), ignore
  148. pass
  149. offset += 1
  150. # logger.debug("position not found")
  151. return None
  152. def find_value(self, search_cb, offset=0):
  153. """
  154. Same as find_pos() but directly returning found value or None.
  155. """
  156. self._lazy_dissect()
  157. try:
  158. return self[self.find_pos(search_cb, offset=offset)]
  159. except TypeError:
  160. return None
  161. """
  162. def _pack(self):
  163. # This ca be overwritten to create TriggerLists containing non-Packet values (see layer567/http.py)
  164. # return -- byte string representation of this triggerlist
  165. return b"".join(self)
  166. """
  167. def __repr__(self):
  168. self._lazy_dissect()
  169. return super().__repr__()
  170. def __str__(self):
  171. self._lazy_dissect()
  172. return super().__str__()