def __init__(self, *k, **kw): """Initialise a packet value. The function fills the content of the packet - either from a list of values (*k), in this case the values are expected to have the same order as the - either by assigning the fields explicitely (**kw notation), in that case, the fields are referenced by their name or alias. Unreferenced fields will be left undefined. For example, using the ICMPv6 type definition above, the two following instantiations are equivalent: my_pkt = ICMPv6 (4, 0, 0x1234, b" ") my_pkt = ICMPv6 (Type=4, Code=0, Checksum=0x1234, Body=b" ") When using the explicit assignment notation, unreferenced fields are left undefined, thus the followings are equivalent: my_pkt2 = ICMPv6 (Type=4, Body=b"blah") my_pkt2 = ICMPv6 (4, None, None, b"blah") my_pkt2 = ICMPv6 (Type=4, Code=None, Checksum=None, Body=b"blah") """ Value.__init__(self) # TODO: copy constructor ? if len(k): if len(kw): raise Error('cannot mix positional and named arguments') missing = len(self) - len(k) if missing < 0: raise Error("to many parameters") self.__datas = [f.store_data(d) for d, f in zip(k, self.fields())] if missing: self.__datas.extend([None for v in range(0, missing)]) else: # named parameters self.__datas = [] for field in self.fields(): for n in field.name, field.alias: if n in kw: v = kw.pop(n) break else: v = None # append the type to the internal list, but possibly convert it before self.__datas.append(field.store_data(v)) if len(kw): raise Error('unknown fields in type %s: %s' % (type(self).__name__, ' '.join(kw.keys())))
def __init__ (self, *k): if len(k) != len(self.__types): raise Error("Expected %d parameters instead of %d" % (len(self.__types), len(k))) for i in range (0, len (k)): t = self.__types[i] if k[i] is None and self.__optional[i]: continue if not isinstance (k[i], t): raise Error("Param id %d is an instance of %s instead of %s" % (i, type(k[i]), t)) self.__values = k self.__timestamp = clock.Clock.get_instance().time()
def post_decode(self, ctx, value): value *= self.__unit if self.get_ref_id() is None: assert ctx.initial_slice.same_buffer_as( ctx.remaining_slice ) # we must still be working on the same slice right = ctx.initial_slice.get_left() + value if right != ctx.initial_slice.get_right(): remaining_bits = right - ctx.remaining_slice.get_left() if remaining_bits < 0: raise Error( "InetLength: expected length is to small: %d (%d bits), %d bits decoded so far" % (value / self.__unit, value, ctx.remaining_slice.get_left() - ctr.initial_slice.get_left())) else: final_slice = ctx.initial_slice.shift_bits(value) # TODO: update the initial_slice too ??? ctx.remaining_slice = ctx.remaining_slice.bit_slice( 0, remaining_bits) def cleaner(): ctx.remaining_slice = final_slice ctx.push_cleaner(cleaner) else: # set a handler -> a 'with' context ! to change the slice on the fly ctx.push_context(self.get_ref_id(), self.__decode_context(ctx, value))
def get_dissection( self, protocol: optional(is_protocol) = None, ) -> list_of(OrderedDict): """ Function to get dissection of a capture as a list of frames represented as strings :param protocol: Protocol class for filtering purposes :type protocol: type :raises TypeError: If protocol is not a protocol class :raises ReaderError: If the reader couldn't process the file :return: A list of Frame represented as API's dict form :rtype: [OrderedDict] """ # log.debug('Starting dissection.') # Check the protocol is one entered if all((protocol, not is_protocol(protocol))): raise TypeError(protocol.__name__ + ' is not a protocol class') fs = self.frames # For speeding up the process with Data.disable_name_resolution(): # Filter the frames for the selected protocol if protocol: fs, _ = Frame.filter_frames(fs, protocol) if fs is None: raise Error('Empty capture cannot be dissected') # Then return the list of dictionary frame representation return [frame.dict() for frame in fs]
def get_type_for_python_type(cls, python_type: type): try: return cls.__type_map[python_type] except KeyError: raise Error( "There is no know PrimitiveValue class to handle type '%s'" % python_type)
def __new__(cls, value=None): if isinstance(value, str): mo = re.match(cls.__reg, value) if not mo: raise Error("malformed IEEE 802.15.4 short address") value = bytes(int(v, 16) for v in mo.groups()) return super().__new__(cls, value)
def new(cls, value): try: t = cls.__type_map[type(value)] except KeyError: raise Error( "Cannot build a PrimitiveValue from '%s' (not a known python primitive type)" % value) return t(value)
def _flatten(cls, values: list_of(this_class)): # ensure that all the values are equal v0 = values[0] for v in values[1:]: if v != v0: raise Error("cannot flatten different values together " + str(values)) return v0
def __new__(cls, value=None): if value is None: value = b"\0" * size # TODO: check that the resulting value is valid result = super().__new__(cls, value) if len(result) != size: raise Error("Invalid value length (%d instead of %d)" % (len(value), size)) return result
def summary(self, protocol: optional(is_protocol) = None) -> list_of((int, str)): """ The summaries function to get the summary of frames :param protocol: Protocol class for filtering purposes :type protocol: type :Example: from ttproto.core.lib.all import Ieee802154 for s in dissector.summary(protocol = Ieee802154): print(s) :raises TypeError: If protocol is not a protocol class :raises ReaderError: If the reader couldn't process the file :return: Basic informations about frames like the underlying example :rtype: [(int, str)] :Example: [ (13, '[127.0.0.1 -> 127.0.0.1] CoAP [CON 38515] GET /test'), (14, '[127.0.0.1 -> 127.0.0.1] CoAP [ACK 38515] 2.05 Content'), (21, '[127.0.0.1 -> 127.0.0.1] CoAP [CON 38516] PUT /test'), (22, '[127.0.0.1 -> 127.0.0.1] CoAP [ACK 38516] 2.04 Changed')] ] .. note:: With the protocol option we can filter the response """ if all((protocol, not is_protocol(protocol))): raise TypeError(protocol.__name__ + ' is not a protocol class') fs = self.frames # For speeding up the process with Data.disable_name_resolution(): # Filter the frames for the selected protocol if protocol: fs, _ = Frame.filter_frames(fs, protocol) if fs is None: raise Error('Empty capture cannot be dissected') # Return list of frames summary return [frame.summary() for frame in fs]
def compute(self, seq, values_bins): if self.get_ref_id() is None: l = 0 for vb in values_bins: l += get_binary_length(vb[1]) else: l = get_binary_length(values_bins[self.get_ref_id()][1]) if l % self.__unit: raise Error("Invalid length: must be a multiple of %d bytes" % (self.__unit // 8)) return l // self.__unit
def get_dissection_simple_format( self, protocol: optional(is_protocol) = None, ) -> list_of(str): """ Function to get dissection of a capture as a list of frames represented as strings :param protocol: Protocol class for filtering purposes :type protocol: type :raises TypeError: If protocol is not a protocol class :raises ReaderError: If the reader couldn't process the file :return: A list of Frame represented as plain non-structured text :rtype: [str] """ # log.debug('Starting dissection.') # Check the protocol is one entered if all((protocol, not is_protocol(protocol))): raise TypeError(protocol.__name__ + ' is not a protocol class') fs = self.frames # For speeding up the process with Data.disable_name_resolution(): # Filter the frames for the selected protocol if protocol: fs, _ = Frame.filter_frames(fs, protocol) if fs is None: raise Error('Empty capture cannot be dissected') # fixme modify Message class from ttproto.data structure so I can get text display wihtout this patch class WritableObj(object): def __init__(self, text=''): self.val = text def __str__(self): return self.val def write(self, text): self.val += text frame_dissection_list = [] for f in fs: text_output = WritableObj() f.message.display(output=text_output) frame_dissection_list.append(str(text_output)) # Then return the list of frames,each as a simple text dissection return frame_dissection_list
def decompress( cls, bin_slice: BinarySlice ) -> (is_flat_value, BinarySlice, is_binary, int): """returns: (decoded header, new slice, decompressed header, next header value)""" assert cls == SixLowpanNHC # decompress() must be reimplemented in derived classes nhc_id = bin_slice[0] try: new_class = sixlowpan_nhc_decompress_dict[nhc_id] except KeyError: # cannot decompress raise Error("Unable to decompress 6lowpan NHC id %d" % nhc_id) return new_class.decompress(bin_slice)
def __decode_context(ctx, value): # generate a shorter slice #TODO: detect possible errors (slice too short) with ctx.replace_attr("remaining_slice", ctx.remaining_slice.bit_slice(0, value)): yield # ensure that we decoded all the bits bl = ctx.remaining_slice.get_bit_length() if bl: raise Error( "Did not decode all the content of the slice (%d bits remaining)" % bl) # move forward n bits in the restored slice ctx.remaining_slice = ctx.remaining_slice.shift_bits(value)
def post_decode(self, ctx, value): assert self.__bidict is not None # must have been initialised expected_type = self.__bidict[value] if self.get_ref_id() is not None: # set a handler -> a 'with' context to change the slice on the fly ctx.push_context(self.get_ref_id(), self.__decode_context(ctx, expected_type)) else: # try to change the type on the fly # check that we can safely change the variant nb_decoded = len(ctx.values) v = expected_type prune = False while v != ctx.variant: if v.get_prune_id() < nb_decoded: prune = True v = v.get_base_variant() if v is None: raise Error( "The new variant must be based on the current one" ) # FIXME: this could be relaxed try: if not (expected_type._decode_message.__func__ is ctx.variant._decode_message.__func__): # the new variant has a different _decode_message() function # -> we must restart decoding from scratch raise _RestartDecodingAs(expected_type) except AttributeError: pass if prune: # we have already decode too many fields to be able to change the type on the fly # -> restart decoding from the beginning of the packet ctx.reset() ctx.remaining_slice = ctx.initial_slice ctx.values[:] = () ctx.field_id = -1 # change the variant ctx.variant = expected_type
def compute_unicast(am, ac, inl, hw, ci): if am == 0: return IPV6_UNSPECIFIED_ADDRESS if ac else inl else: if ac == 0: pfx = b"\xfe\x80\0\0\0\0\0\0" else: if ci == Omit(): raise Error( "6Lowpan decompression error: packet uses stateful compression whereas CID == 0" ) pfx, length = cls.get_context(ci) pfx = pfx[:8] if am == 1: iid = inl elif am == 2: iid = b"\0\0\0\xff\xfe\0" + inl else: iid = hw return IPv6Address(pfx + iid)
def _fill_default_values(self: is_flat_value) -> list_of(Value): """Fill the undefined fields with a default value. This function returns a list contaning for each field: - the value of the field (if defined) - the default provided by the field tag (otherwise) """ values = [] for f, v in zip(self.fields(), self): # fill the field value (and use the default if not specified) if v is None: v = f.tag.get_default_value() if v is None: raise Error("field '%s' in '%s' must have a value" % (f.name, type(self).__name__)) else: v = f.store_data(v) values.append(v) return values
def pack(self, data: is_data) -> this_class: """Encapsulate the given data into the present packet The given data is encapsulated into the "Payload" field (which must be present). If the current packet has no payload defined, this function is just returns self(Payload=data) (it generates a derived object and fills the Payload field with the given data. Otherwise the function will recursively call the pack() function on the existing payload. The result will be equal to: self(Payload=self["Payload"].pack(data)) """ pid = self.get_payload_id() if pid is None: raise Error("Cannot pack a message into %s (no Payload field)" % type(self).__name__) new_seq = self() payloads = list(new_seq.get_datas(pid)) field = new_seq.get_field(pid) if len(payloads) == 0: new_seq.__datas[pid] = field.store_data(data) elif len(payloads) >= 1: # FIXME: this will create lots of duplicated objects when pack is called in cascade using the __div__ operator # we may want to avoid duplicating the parent object when it is not modified # (in this case: when it is a truly anonymous object) # FIXME: if a sequence has multiple payloads (because of inheritance), we will # implicitely pack the data at the end of the first known payload # (this may have side effects ?) new_seq.__datas[pid] = field.store_data(payloads[0].pack(data)) return new_seq
def _decode_message(cls, bin_slice, root_header=True): hwsrc, hwdst = cls.get_current_iid_context() def decoder_func(): nonlocal bin_slice v = None for f in cls.fields(): t = yield v v, bin_slice = f.tag.decode_message(t if t else f.type, bin_slice, None) values.append(v) yield v values = cls.DataList() # prepare the decoder decoder = decoder_func() next(decoder) def decode(): return next(decoder) def decode_if(cond): return decoder.send(None if cond else Omit) def decode_as(type): return decoder.send(type) # decode the base format dp = decode() tf = decode() nh = decode() hl = decode() cid = decode() sac = decode() sam = decode() m = decode() dac = decode() dam = decode() # CID extension sci = decode_if(cid) dci = decode_if(cid) # TF fields iecn = decode_if(tf != 3) idscp = decode_if(not (tf & 1)) ipad = decode_as(Omit if tf & 2 else (UInt2 if tf else UInt4)) ifl = decode_if(not (tf & 2)) # NH field inh = decode_if(nh == 0) # HLIM field ihl = decode_if(hl == 0) # src & dst addr def decode_unicast(am, ac): if am == 0: return decode_as(Omit if ac else IPv6Address) else: return decode_as((None, Bytes8, Bytes2, Omit)[am]) isrc = decode_unicast(sam, sac) if not m: idst = decode_unicast(dam, dac) else: # multicast if dac: idst = decode_as(Omit if dam else Bytes6) else: idst = decode_as((IPv6Address, Bytes6, Bytes4, Bytes1)[dam]) # compute the addresses def compute_unicast(am, ac, inl, hw, ci): if am == 0: return IPV6_UNSPECIFIED_ADDRESS if ac else inl else: if ac == 0: pfx = b"\xfe\x80\0\0\0\0\0\0" else: if ci == Omit(): raise Error( "6Lowpan decompression error: packet uses stateful compression whereas CID == 0" ) pfx, length = cls.get_context(ci) pfx = pfx[:8] if am == 1: iid = inl elif am == 2: iid = b"\0\0\0\xff\xfe\0" + inl else: iid = hw return IPv6Address(pfx + iid) def compute_multicast(): if dac: # Stateful if dam == 0: pfx, length = cls.get_context(dci) return IPv6Address(b"".join( (b"\xff", idst[:2], bytes( (length, )), pfx[:8], idst[2:]))) else: return IPV6_UNSPECIFIED_ADDRESS else: # Stateless if dam == 0: return idst elif dam == 1: return IPv6Address(b"".join( (b"\xff", idst[:1], b"\0\0\0\0\0\0\0\0\0", idst[1:]))) elif dam == 2: return IPv6Address(b"".join( (b"\xff", idst[:1], b"\0\0\0\0\0\0\0\0\0\0\0", idst[1:]))) else: return IPv6Address(b"\xff\2\0\0\0\0\0\0\0\0\0\0\0\0\0" + idst) # decompress the addresses src = compute_unicast(sam, sac, isrc, hwsrc, sci) dst = compute_multicast() if m else compute_unicast( dam, dac, idst, hwdst, dci) # next compressed header if not nh: nhc = Omit() nhc_bin = b"" next_header = inh else: # here were enter a pseudo_addr context because we may need to compute an IPv6 checksum # and enter and iid context because we may have compressed addresses with InetPacketValue.ipv6_pseudo_addresses_context((src, dst)), \ cls.encapsulating_iid_context(src, dst): nhc, bin_slice, nhc_bin, next_header = SixLowpanNHC.decompress( bin_slice) values.append(nhc) def value(value, default_if_omit): return default_if_omit if isinstance(value, Omit) else value # Assemble the IPv6 header ipv6_bin = IPv6( tc=(value(iecn, 0) << 6) | value(idscp, 0), fl=value(ifl, 0), len=len(bin_slice) + len(nhc_bin), nh=next_header, hl=(ihl, 1, 64, 255)[hl], src=src, dst=dst, pl="", ).build_message()[1] if root_header: # Generate the new slice inner_slice = BinarySlice( concatenate((ipv6_bin, nhc_bin, bin_slice.as_binary()))) # Decode the payload payload, inner_slice = IPv6.decode_message(inner_slice) values.append(payload) if inner_slice: raise Error("Buffer not fully decoded (%d remaining bits)" % inner_slice.get_bit_length()) return cls(*values), bin_slice.shift_bits( bin_slice.get_bit_length()) else: values.append(Omit()) return cls(*values), bin_slice, ipv6_bin + nhc_bin