pypacker_meta.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. import struct
  2. class MetaPacket(type):
  3. """
  4. This Metaclass is a more efficient way of setting attributes than using __init__.
  5. This is done by reading name, format and default value out of __hdr__ in every subclass.
  6. This configuration is set one time when loading the module (not at instatiation).
  7. Attributes can be normally accessed using "obj.field" notation.
  8. General note: Callflaw is: __new__ (loading module) -> __init__ (initiate class)
  9. CAUTION:
  10. - List et al are _SHARED_ among all instantiated classes! A copy is needed on changes to them
  11. - New protocols: don't use header fields having same name as methods in Packet class
  12. """
  13. def __new__(cls, clsname, clsbases, clsdict):
  14. # Using properties will slow down access to header fields but it's needed:
  15. # This way we get informed about get-access more efficiently than using
  16. # __getattribute__ (slow access for header fields vs. slow access
  17. # for ALL class fields).
  18. def get_setter(varname, is_field_type_simple=True, is_field_static=True):
  19. """
  20. varname -- name of the variable to set the property for
  21. is_field_type_simple -- get property for simple static or dynamic type if True, else TriggerList
  22. is_field_static -- if is_field_type_simple is True: get static type (int, fixed size bytes, ...),
  23. else dynamic (format "xs") which can change in format (eg DNS names)
  24. return -- set-property for simple types or triggerlist
  25. """
  26. varname_shadowed = "_%s" % varname
  27. def setfield_simple(obj, value):
  28. """
  29. Unpack field ondemand
  30. """
  31. if obj._unpacked is not None and not obj._unpacked:
  32. # obj._unpacked = None means: dissect not yet finished
  33. obj._unpack()
  34. if value is None and obj.__getattribute__(varname_shadowed + "_active"):
  35. object.__setattr__(obj, varname_shadowed + "_active", False)
  36. obj._header_format_changed = True
  37. # logger.debug("deactivating field: %s" % varname_shadowed)
  38. elif value is not None and not obj.__getattribute__(varname_shadowed + "_active"):
  39. object.__setattr__(obj, varname_shadowed + "_active", True)
  40. obj._header_format_changed = True
  41. # logger.debug("activating field: %s" % varname_shadowed)
  42. if not is_field_static and value is not None:
  43. # simple dynamic field
  44. format_new = "%ds" % len(value)
  45. # logger.debug(">>> changing format for dynamic field: %r / %s / %s" % (obj.__class__, varname_shadowed, format_new))
  46. object.__setattr__(obj, varname_shadowed + "_format", format_new)
  47. obj._header_format_changed = True
  48. object.__setattr__(obj, varname_shadowed, value)
  49. obj._header_changed = True
  50. obj._notify_changelistener()
  51. def setfield_triggerlist(obj, value):
  52. """
  53. Clear list and add value as only value.
  54. value -- Packet, bytes (single or as list)
  55. """
  56. tl = obj.__getattribute__(varname_shadowed)
  57. if type(tl) is list:
  58. # we need to create the original TriggerList in order to unpack correctly
  59. # _triggerlistName = [b"bytes", callback] or
  60. # _triggerlistName = [b"", callback] (default initiation)
  61. # logger.debug(">>> initiating TriggerList")
  62. tl = obj._header_fields_dyn_dict[varname_shadowed](obj, dissect_callback=tl[1], buffer=tl[0])
  63. object.__setattr__(obj, varname_shadowed, tl)
  64. # this will trigger unpacking
  65. del tl[:]
  66. # TriggerList: avoid overwriting dynamic fields eg when using keyword constructor Class(key=val)
  67. if type(value) is list:
  68. tl.extend(value)
  69. else:
  70. tl.append(value)
  71. obj._header_changed = True
  72. obj._notify_changelistener()
  73. if is_field_type_simple:
  74. return setfield_simple
  75. else:
  76. return setfield_triggerlist
  77. def get_getter(varname, is_field_type_simple=True):
  78. """
  79. varname -- name of the variable to set the property for
  80. is_field_type_simple -- get property for simple static or dynamic type if True, else TriggerList
  81. return -- get-property for simple type or triggerlist
  82. """
  83. varname_shadowed = "_%s" % varname
  84. def getfield_simple(obj):
  85. """
  86. Unpack field ondemand
  87. """
  88. # logger.debug("getting value for simple field: %s" % varname_shadowed)
  89. if obj._unpacked is not None and not obj._unpacked:
  90. obj._unpack()
  91. # logger.debug("now returning value")
  92. return obj.__getattribute__(varname_shadowed)
  93. def getfield_triggerlist(obj):
  94. tl = obj.__getattribute__(varname_shadowed)
  95. # logger.debug(">>> getting Triggerlist: %r" % tl)
  96. if type(tl) is list:
  97. # _triggerlistName = [b"bytes", callback] or
  98. # _triggerlistName = [b"", callback] (default initiation)
  99. tl = obj._header_fields_dyn_dict[varname_shadowed](obj, dissect_callback=tl[1], buffer=tl[0])
  100. object.__setattr__(obj, varname_shadowed, tl)
  101. return tl
  102. if is_field_type_simple:
  103. return getfield_simple
  104. else:
  105. return getfield_triggerlist
  106. t = type.__new__(cls, clsname, clsbases, clsdict)
  107. # dictionary of TriggerLists: name -> TriggerListClass
  108. t._header_fields_dyn_dict = {}
  109. # get header-infos from subclass: [("name", "format", value), ...]
  110. hdrs = getattr(t, "__hdr__", None)
  111. # cache header for performance reasons, will be set to bytes later on
  112. t._header_cached = []
  113. # all header names
  114. t._header_field_names = []
  115. t._header_format_order = getattr(t, "__byte_order__", ">")
  116. # all header formats including byte order
  117. header_fmt = [t._header_format_order]
  118. if hdrs is not None:
  119. # every header var will get two additional values set:
  120. # var_active = indicates if header is active
  121. # var_format = indicates the header format
  122. # logger.debug("loading meta for: %s, st: %s" % (clsname, st))
  123. for hdr in hdrs:
  124. shadowed_name = "_%s" % hdr[0]
  125. t._header_field_names.append(shadowed_name)
  126. setattr(t, shadowed_name + "_active", True)
  127. # remember header format
  128. # t._header_field_infos[shadowed_name] = [True, hdr[1]]
  129. is_field_type_simple = False
  130. is_field_static = True
  131. if hdr[1] is not None or (hdr[2] is None or type(hdr[2]) == bytes):
  132. # simple static or simple dynamic type
  133. # we got one of: ("name", format, ???) = static or
  134. # ("name", None, ???) = dynamic
  135. # -> Format given = static, Format None = dynamic
  136. is_field_type_simple = True
  137. if hdr[1] is None:
  138. # assume simple dynamic field
  139. is_field_static = False
  140. setattr(t, shadowed_name + "_format", hdr[1])
  141. if is_field_type_simple:
  142. fmt = hdr[1]
  143. if hdr[2] is not None:
  144. # value given: field is active
  145. if fmt is None:
  146. # dynamic field
  147. fmt = "%ds" % len(hdr[2])
  148. setattr(t, shadowed_name + "_format", fmt)
  149. header_fmt.append(fmt)
  150. t._header_cached.append(hdr[2])
  151. """
  152. if fmt is not None:
  153. header_fmt.append(fmt)
  154. t._header_cached.append(hdr[2])
  155. """
  156. # logger.debug("--------> field is active: %r" % hdr[0])
  157. else:
  158. setattr(t, shadowed_name + "_active", False)
  159. # only simple fields can get deactivated
  160. setattr(t, shadowed_name + "_active", True if hdr[2] is not None else False)
  161. # set initial value via shadowed variable: _varname <- varname [optional in subclass: <- varname_s]
  162. # setting/getting value is done via properties.
  163. # logger.debug("init simple type: %s=%r" % (shadowed_name, hdr[2]))
  164. setattr(t, shadowed_name, hdr[2])
  165. setattr(t, hdr[0], property(
  166. get_getter(hdr[0], is_field_type_simple=True),
  167. get_setter(hdr[0], is_field_type_simple=True, is_field_static=is_field_static)
  168. )
  169. )
  170. else:
  171. # assume TriggerList
  172. # Triggerlists don't have initial default values (and can't get deactivated) TODO?
  173. t._header_fields_dyn_dict[shadowed_name] = hdr[2]
  174. # initial value of TiggerLists is: values to init empty list
  175. setattr(t, shadowed_name, [b"", None])
  176. setattr(t, hdr[0], property(
  177. get_getter(hdr[0], is_field_type_simple=False),
  178. get_setter(hdr[0], is_field_type_simple=False, is_field_static=is_field_static)
  179. )
  180. )
  181. # format and value needed for correct length in _unpack()
  182. header_fmt.append("0s")
  183. t._header_cached.append(b"")
  184. # logger.debug("<<<<")
  185. # logger.debug(">>> translated header names: %s/%r" % (clsname, t._header_name_translate))
  186. # current format as string
  187. t._header_format = struct.Struct("".join(header_fmt))
  188. # header size can be assigened by __init__() directly or given by _header_format.size
  189. t._header_len = t._header_format.size
  190. # track changes to header format (changes to simple dynamic fields or TriggerList)
  191. t._header_format_changed = False
  192. # cached header, return this if nothing changed
  193. t._header_cached = t._header_format.pack(*t._header_cached)
  194. # logger.debug("formatstring is: %s" % header_fmt)
  195. # body as raw byte string (None if handler is present)
  196. t._body_bytes = b""
  197. # name of the attribute which holds the object representing the body aka the body handler
  198. t._bodytypename = None
  199. # next lower layer: a = b + c -> b will be lower layer for c
  200. t._lower_layer = None
  201. # track changes to header values: This is needed for layers like TCP for
  202. # checksum-recalculation. Set to "True" on changes to header/body values, set to False on "bin()"
  203. # track changes to header values
  204. t._header_changed = False
  205. # track changes to body value like [None | bytes | body-handler] -> [None | bytes | body-handler]
  206. t._body_changed = False
  207. # objects which get notified on changes on header or body (shared)
  208. # TODO: use sets here
  209. t._changelistener = []
  210. # lazy handler data: [name, class, bytes]
  211. t._lazy_handler_data = None
  212. # indicates the most top layer until which should be unpacked (vs. lazy dissecting = just next upper layer)
  213. t._target_unpack_clz = None
  214. # inicates if static header values got already unpacked
  215. # [True|False] = Status after dissect, None = pre-dissect (not unpacked)
  216. t._unpacked = None
  217. # indicates if this packet contains fragmented data saved as body bytes
  218. t._fragmented = False
  219. t._dissect_error = False
  220. return t