libmich/core/IANA_dict.py
class IANA_dict(dict):
    '''
    Class to manage dictionnaries with integer as keys 
    and 2-tuples of string as items,
    such as IANA protocols parameters reference: http://www.iana.org/protocols/
    
    call it like this:
    IANA_dict( { integer : ("parameter name", "parameter abbreviation"), 
                 integer: "parameter name", ... } )
    '''
    
    def __init__(self, IANA_dict={}):
        '''
        initialize like a dict
        [+] if 'key' is not integer, raises error
        [+] if value 'value' is not string or 2-tuple of string, raises error
        [+] if value is string, transforms it in 2-tuple ("value", "")
            i.e. value must be ("value name", value abbreviation")
        '''
        # 1st: rearrange string item to 2-tuple of string
        items = dict.items(IANA_dict)
        for i in items:
            if type(i[1]) is str:
                dict.__setitem__( IANA_dict, i[0], (i[1], "") )
        
        # 2nd: verification: key is int / long, and item is 2-tuple of string
        items = dict.items(IANA_dict)
        for i in items:
            if type(i[0]) not in (int, long):
                raise KeyError('%s : key must be integer' % i[0])
            if type(i[1]) is not tuple or len(i[1]) != 2:
                raise ValueError('%s : value must be string or 2-tuple of ' \
                                 'string' % i[1])
        
        dict.update(self, IANA_dict)
    
    def __getitem__(self, key):
        '''
        Same as dict.__getitem__(key)
        [+] If 'key' is integer and does not exist,
            returns the item corresponding to the last existing key,
            except if key is over the last key.
        [+] If 'key' is string and exists as value (name or abbreviation),
            returns first key found corresponding to the value.
        '''
        values = []
        for e in self.values():
            for v in e:
                if type(v) is str and len(v) > 0:
                    values.append(v)
        
        if self.__contains__(key):
            return dict.__getitem__(self, key)[0]
        
        elif type(key) in (int, long) \
        and self.s_keys()[0] < key < self.s_keys().pop():
            i = 0
            while self.__contains__(key-i) is False: i += 1
            return dict.__getitem__(self, key-i)[0]
        
        elif type(key) is str and key in values:
            i = 0
            while key not in self.items()[i][1]: i += 1
            return self.items()[i][0]
        
        else: 
            try: return dict.__getitem__(self, key)
            except KeyError: return key
    
    def __setitem__(self, key, item):
        '''
        Same as dict.__setitem__(key)
        [+] If 'key' is not integer, raises error
        [+] If 'value' is not string or 2-tuple of strings, raises error
        '''
        if type(key) not in [int, long]:
            raise KeyError('%s : key must be integer' % key)
        if type(item) is str:
            item = (item, "")
        if type(item) is not tuple or len(item) != 2:
            raise ValueError('%s : value must be string or 2-tuple of '\
                             'string' % item)
        dict.__setitem__(self, key, item)
    
    def s_keys(self):
        '''
        returns a sorted list of keys
        ''' 
        s_keys = dict.keys(self)
        s_keys.sort()
        return s_keys
    
    def items(self):
        '''
        returns the list of (key, value), following the order of sorted keys.
        '''
        items = []
        s_keys = self.s_keys()
        for k in s_keys:
            items.append( (k, dict.__getitem__(self, k)) )
        return items
libmich/core/element.py
class Layer(object):
    '''
    class built from stack of "Str", "Int", "Bit" and "Layer" objects
    got from the initial constructorList.
    Layer object is recursive: it can contain other Layer() instances
    Layer does not require to be byte-aligned. This happens depending of the
    presence of Bit() instances.
    
    when instantiated:
    clones the list of "Str", "Int", "Bit" elements in the constructorList
    to build a dynamic elementList, that can be changed afterwards (adding /
    removing objects);
    A common hierarchy level for the whole Layer is defined, it is useful 
    when used into "Block" to create hierarchical relationships: 
        self.hierarchy (int), self.inBlock (bool)
        when .inBlock is True, provides: .get_payload(), .get_header(), 
             .has_next(), .get_next(), .get_previous(), and .Block
    It provides several methods for calling elements in the layer:
        by CallName / ReprName passed in attribute
        by index in the elementList
        can be iterated too
        and many other manipulations are defined
    It has also some common methods with "Str", "Int" and "Bit" to emulate 
    a common handling:
    __str__, __len__, __int__, bit_len, getattr, showattr, show, map
    '''
    #
    # debugging threshold for Layer:
    dbg = ERR
    # add some sanity checks
    #safe = True
    safe = False
    # define the type of str() and map() method
    _byte_aligned = True
    # reserved attributes:
    Reservd = ['CallName', 'ReprName', 'elementList', 'Len', 'BitLen',
               'hierarchy', 'inBlock', 'Trans', 'ConstructorList',
               'dbg', 'Reservd']
    #
    # represent transparent elements in __repr__()
    _repr_trans = True
    
    # structure description:
    constructorList = []
    
    def __init__(self, CallName='', ReprName='', Trans=False, **kwargs):
        if type(CallName) is not str:
            raise AttributeError('CallName must be a string')
        elif len(CallName) == 0:
            self.CallName = self.__class__.__name__
        else:
            self.CallName = CallName
        if type(ReprName) is str and len(ReprName) > 0: 
            self.ReprName = ReprName
        else: 
            self.ReprName = ''
        self.elementList = []
        self.set_hierarchy(0)
        self.inBlock = False
        self.Trans = Trans
        
        CallNames = []
        for e in self.constructorList:
            # This is for little players
            #if isinstance(e, Element):
            # OK, now let's put the balls on the table and
            # make Layer recursive (so will have Layer() into Layer())
            if isinstance(e, (Element, Layer)):
                if e.CallName in self.Reservd:
                    if self.safe or self.dbg >= ERR:
                        log(ERR, '(Layer - %s) using a reserved '
                            'attribute as CallName %s: aborting...' \
                          % (self.__class__, e.CallName))
                    return
                if e.CallName in CallNames:
                    if self.dbg >= WNG:
                        log(WNG, '(Layer - %s) different elements have ' \
                           'the same CallName %s' % (self.__class__, e.CallName))
                if isinstance(e, Element):
                    self.append(e.clone())
                # do not clone Layer() as it breaks dynamic element inside
                # i.e. element with PtFunc, LenFunc, DictFunc, TransFunc defined
                # TODO: patch Layer().clone() method to solve this...
                # lets try with deepcopy()
                elif isinstance(e, Layer):
                    self.append(e.clone())
            CallNames.append(e.CallName)
        
        # check for bit alignment until we lost information on the Layer length
        # also check if fixed length can be deduced
        self.BitLen = 0
        for e in self.elementList:
            if self.dbg >= DBG:
                log(DBG, '(Layer - %s) length verification for %s' \
                    % (self.__class__, e.CallName))
            if isinstance(e, Bit):
                self.BitLen += e.bit_len()
            elif hasattr(e, 'Len') and type(e.Len) is int:
                self.BitLen += (e.Len)*8
            else:
                self.BitLen, self.Len = 'var', 'var'
                break
        if type(self.BitLen) is int :
            if self.BitLen % 8:
                if self.dbg >= WNG and self._byte_aligned:
                    log(WNG, '(Layer - %s) Elements seem not to be '\
                        'byte-aligned: hope you expect it!' \
                        % self.__class__)
                # record length in bit (precise one) and in bytes (unprecised)
                self.Len = 1 + self.BitLen//8
            else:
                self.Len = self.BitLen//8
        #
        # check additional args that would correspond to contained Element
        args = kwargs.keys()
        if self.dbg >= DBG:
            log(DBG, '(Layer - %s) init kwargs: %s' % (self.__class__, args))
        for e in self:
            if hasattr(e, 'CallName') and e.CallName in args:
                if hasattr(e, 'Pt'):
                    e.Pt = kwargs[e.CallName]
                else:
                    kwe = kwargs[e.CallName]
                    if isinstance(kwe, (tuple, list)):
                        e.__init__(*kwe)
                    elif isinstance(kwe, dict):
                        e.__init__(**kwe)
    
    # define some basic list facilities for managing elements into the Layer, 
    # through the "elementList" attribute:
    def __iter__(self):
        if 'elementList' in self.__dict__.keys():
            return self.__dict__['elementList'].__iter__()
        else: return [].__iter__()
    
    def __getitem__(self, num):
        return self.elementList[num]
    
    def __getslice__(self, i, j):
        l = Layer('_slice_')
        if not i or i < 0:
            i=0
        #maxj = len(self.elementList)-1
        maxj = len(self.elementList)
        if not j or j > maxj:
            j = maxj
        #
        for k in xrange(i, j):
            l.append( self[k] )
        return l
    
    def __setitem__(self, num, value):
        # special handling here: 
        # use to override the element value 
        # with its "Val" attribute (like when mapping a string)
        self.elementList[num].Val = value
    
    def append(self, element):
        #if isinstance(element, Element):
        # make Layer recursive:
        if isinstance(element, (Element, Layer)):
            if self.dbg >= WNG and element.CallName in self.getattr():
                log(WNG, '(Layer - %s) different elements have the same '\
                         'CallName %s' % (self.__class__, element.CallName))
            self.elementList.append(element)
    
    def __lshift__(self, element):
        self.append(element)
        if isinstance(element, Layer):
            element.inc_hierarchy(self.hierarchy)
    
    def insert(self, index, element):
        CallNames = self.getattr()
        #if isinstance(element, Element):
        # make Layer recursive:
        if isinstance(element, (Element, Layer)):
            if self.dbg >= WNG and element.CallName in CallNames:
                log(WNG, '(Layer - %s) different elements have the same '\
                         'CallName %s' % (self.__class__, element.CallName))
            self.elementList.insert(index, element)
    
    def __rshift__(self, element):
        self.insert(0, element)
        if isinstance(element, Layer):
            element.inc_hierarchy(self.hierarchy)
    
    def extend(self, newElementList):
        for e in newElementList:
            self.append(e)
    
    def remove(self, element):
        for e in self:
            if e == element:
                self.elementList.remove(element)
    
    def replace(self, current_element, new_element):
        # check index of the element ro replace
        index = 0
        for elt in self.elementList:
            if elt == current_element:
                self.remove(current_element)
                self.insert(index, new_element)
                return
            else:
                index += 1
    
    # define some attribute facilities for managing elements 
    # by their CallName into the Layer
    # warning: dangerous when parsing data into Layer, 
    # with elements which could have same CallName
    # 
    # list facilities can be preferred in this case
    def __getattr__(self, name):
        names = [x.CallName for x in self.elementList]
        if name in names:
            return self.elementList[ names.index(name) ]
        names = [x.ReprName for x in self.elementList]
        if name in names:
            return self.elementList[ names.index(name) ]
        #
        return object.__getattribute__(self, name)
        #return self.__getattribute__(name)
        #return getattr(self, name)
    
    def __setattr__(self, name, value):
        # special handling here: use to override the element value 
        # with its "Val" attribute (like when mapping a string)
        for e in self:
            if name == e.CallName or name == e.ReprName: 
                e.Val = value
                return
        return object.__setattr__(self, name, value)
        raise AttributeError( '"Layer" has no "%s" attribute: %s' \
              % (name, self.getattr()) )
    
    def __hasattr__(self, name):
        for e in self:
            if name == e.CallName or name == e.ReprName: 
                return True
        #return object.__hasattr__(self, name): 
        # not needed (does not work in the code... but works in python...)
        raise AttributeError( '"Layer" has no "%s" attribute: %s' \
              % (name, self.getattr()) )
    
    # method for managing the Layer hierarchy (easy):
    def set_hierarchy(self, hier=0):
        self.hierarchy = hier
        for e in self:
            if isinstance(e, Layer):
                e.set_hierarchy(hier)
        
    def inc_hierarchy(self, ref=None):
        if ref is None:
            self.set_hierarchy(self.hierarchy+1)
        else: 
            self.set_hierarchy(self.hierarchy+ref+1)
        #for l in self:
        #    if isinstance(l, Layer):
        #        l.hierarchy = self.hierarchy
    
    def dec_hierarchy(self, ref=None):
        if ref is None: 
            self.set_hierarchy(self.hierarchy-1)
        else: 
            self.set_hierarchy(self.hierarchy+ref-1)
        #for l in self:
        #    if isinstance(l, Layer):
        #        l.hierarchy = self.hierarchy
    
    # define same methods as "Element" type for being use the same way
    def __str__(self):
        if self.dbg >= DBG:
            log(DBG, '(Layer.__str__) entering str() for %s' % self.CallName)
        # First take care of transparent Layer (e.g. in L3Mobile)
        if hasattr(self, 'Trans') and self.Trans:
            return ''
        # dispatch to the right method depending of byte alignment
        if self._byte_aligned is True:
            return self.__str_aligned()
        else:
            return self.__str_unaligned()
    
    def __str_unaligned(self):
        # then init resulting string 
        # and bit offset needed to shift unaligned strings
        s = []
        off = 0
        # loop on each element into the Layer
        # also on Layer into Layer...
        for e in self:
            shtr_e, bitlen_e = e.shtr(), e.bit_len()
            rest_e = bitlen_e % 8
            if self.dbg >= DBG:
                log(DBG, '(Layer.__str__) %s: %s, %i, offset: %i' \
                    % (e.CallName, hexlify(shtr_e), bitlen_e, off))
            # if s is not byte-aligned and e is not transparent
            # update the last component of s
            if off and shtr_e:
                # 1st update last bits of s with MSB of e, 
                # before stacking with the rest of e
                # (8 - off) is the room left in the LSB of s
                s[-1] = ''.join((s[-1][:-1],
                                  chr(ord(s[-1][-1]) + shtr_e.left_val(8-off)),
                                  (shtr_e << (8-off))
                                ))
                # take care in case the shifting of e voids its last byte
                if rest_e and rest_e-(8-off) <= 0:
                    s[-1] = s[-1][:-1]
                # update offset
                if rest_e:
                    off += bitlen_e
                    off %= 8
            # in case s is already aligned, append a new component to s
            elif shtr_e:
                s.append(shtr_e)
                # update offset
                if rest_e:
                    off += rest_e
            if self.dbg >= DBG:
                log(DBG, '(Layer.__str__) %s' % hexlify(s))
        # well done!
        return ''.join(s)
    
    def __str_aligned(self):
        s = []
        BitStream = ''
        # loop on each element in the Layer
        # also on Layer into Layer...
        for e in self:
            # need special processing for stacking "Bit" element: 
            #   using "BitStream" variable
            #   works only with contiguous "Bit" elements 
            #   to avoid byte-misalignment of other element in the Layer 
            #   (and programming complexity with shifting everywhere...)
            if isinstance(e, Bit):
                # manage element transparency with (Trans, TranFunc)
                # and build a bitstream ('100110011...1101011') from Bit values
                if not e.is_transparent():
                    BitStream += str(e.__bin__())
                # when arriving on a byte boundary from bitstream, 
                # create bytes and put it into the s variable
                if len(BitStream) >= 8:
                    while 1:
                        s.append( pack('!B', int(BitStream[:8], 2)) )
                        BitStream = BitStream[8:]
                        if len(BitStream) < 8:
                            break
                if self.dbg >= DBG:
                    log(DBG, '(Element) %s: %s, %s\nBitstream: %s' \
                        % (e.CallName, e(), e.__bin__(), BitStream))
            # when going to standard Str or Int element, 
            # or directly end of __str__ function 
            # verify the full BitStream has been consumed
            # and continue to build the resulting string easily...
            else:
                # possible byte mis-alignment for Str / Int is not managed...
                self.__is_aligned(BitStream)
                BitStream = ''
                if isinstance(e, Layer) and not e.Trans \
                or isinstance(e, Element):
                    s.append( str(e) )
        self.__is_aligned(BitStream)
        return ''.join(s)
    
    def __is_aligned(self, BitStream):
        if BitStream and self.dbg >= ERR:
            log(ERR, '(Layer - %s) some of the Bit elements have not been ' \
                'stacked in the "str(Layer)"\nremaining bitstream: %s' \
                % (self.__class__, BitStream))
            if self.safe:
                assert(not BitStream)
    
    def __call__(self):
        return self.__str__()
    
    def __len__(self):
        return len(self.__str__())
    
    def shtr(self):
        return shtr(self.__str__())
    
    def bit_len(self):
        # just go over all internal elements to track their own bit length
        # updated attributes initialized when Layer was constructed
        self.BitLen = 0
        for e in self:
            if hasattr(e, 'bit_len'):
                self.BitLen += e.bit_len()
            elif hasattr(e, '__len__'):
                self.BitLen += len(e)*8
        self.Len = 1 + (self.BitLen // 8) if self.BitLen % 8 \
                   else (self.BitLen // 8)
        return self.BitLen
    
    def __hex__(self):
        bit_len = self.bit_len()
        hex_len = bit_len/4
        if bit_len%4:
            hex_len += 1
        #
        return self.__str__().encode('hex')[:hex_len]
    
    def __bin__(self):
        bits = []
        for e in self:
            bits.append( e.__bin__() )
        return ''.join(bits)
    
    def __int__(self):
        # big endian integer representation of the string buffer
        if self._byte_aligned:
            return shtr(self).left_val(len(self)*8)
        else:
            return shtr(self).left_val(self.bit_len())
    
    def __repr__(self):
        t = ''
        if self.Trans:
            t = ' - transparent '
        s = '<%s[%s]%s: ' % ( self.ReprName, self.CallName, t )
        for e in self:
            if self._repr_trans or not e.is_transparent():
                s += '%s(%s):%s, ' % ( e.CallName, e.ReprName, repr(e) )
        s = s[:-2] + '>'
        return s
    
    def map_len(self):
        return len(self)
    
    def getattr(self):
        return [e.CallName for e in self.elementList]
    
    def showattr(self):
        for a in self.getattr():
            print('%s : %s' % ( a, repr(self.__getattr__(a))) )
    
    def clone2(self):
        # TODO: deepcopy is not adapted here, can create errors...
        return deepcopy(self)
    
    def clone(self):
        #
        # build a new constructorList made of clones
        constructorList_new = []
        for e in self:
            constructorList_new.append(e.clone())
        #
        # substitute the current constructorList with the one made of clones
        constructorList_ori = self.__class__.constructorList
        self.__class__.constructorList = constructorList_new
        # instantiate the clone
        c = self.__class__()
        c.CallName = self.CallName
        if hasattr(self, 'ReprName'):
            c.ReprName = self.ReprName
        if hasattr(self, 'Trans'):
            c.Trans = self.Trans
        # restore the original constructorList
        self.__class__.constructorList = constructorList_ori
        #
        return c
    
    def is_transparent(self):
        if self.Trans:
            return True
        else:
            return False
    
    def show(self, with_trans=False):
        re, tr = '', ''
        if self.ReprName != '':
            re = '%s ' % self.ReprName
        if self.is_transparent():
            # TODO: eval the best convinience here
            if not with_trans:
                return ''
            tr = ' - transparent'
        # Layer content
        str_lst = [e.show().replace('\n', '\n ') for e in self]
        # insert spaces for nested layers and filter out empty content
        str_lst = [' %s\n' % s for s in str_lst if s]
        # insert layer's title
        str_lst.insert(0, '### %s[%s]%s ###\n' % (re, self.CallName, tr))
        # return full inline string without last CR
        return ''.join(str_lst)[:-1]
    
    def map(self, string=''):
        if self.dbg >= DBG:
            log(DBG, '(Layer.map) entering map() for %s' % self.CallName)
        # First take care of transparent Layer (e.g. in L3Mobile)
        if hasattr(self, 'Trans') and self.Trans:
            return
        # dispatch to the right method depending of byte alignment
        if self._byte_aligned is True:
            self.__map_aligned(string)
        else:
            self.__map_unaligned(string)
    
    def __map_unaligned(self, string=''):
        s = shtr(string)
        # otherwise go to map() over all elements
        for e in self:
            if self.dbg >= DBG:
                log(DBG, '(Layer.__map_unaligned) %s, bit length: %i' \
                    % (e.CallName, e.bit_len()))
                log(DBG, '(Layer.__map_unaligned) string: %s' % hexlify(s))
            # this is beautiful
            s = e.map_ret(s)
    
    def __map_aligned(self, string=''):
        # Bit() elements are processed intermediary: 
        # 1st placed into BitStack
        # and when BitStack is byte-aligned (check against BitStack_len)
        # string buffer is then mapped to it
        self.__BitStack = []
        self.__BitStack_len = 0
        # Furthermore, it manages only contiguous Bit elements 
        # for commodity... otherwise, all other elements should be shifted
        #
        for e in self:
            # special processing for Bit() element:
            if isinstance(e, Bit):
                self.__add_to_bitstack(e)
                # if BitStack is byte aligned, map string to it:
                if self.__BitStack_len % 8 == 0:
                    string = self.__map_to_bitstack(string)
            # for other elements (Str(), Int(), Layer()), standard processing:   
            else:
                if self.__BitStack_len > 0 and self.dbg >= ERR:
                    log(WNG, '(Layer - %s) some of the Bit elements have not ' \
                        'been mapped in the "Layer": not byte-aligned' \
                        % self.__class__)
                if isinstance(e, (Layer, Element)) and not e.is_transparent():
                    if len(string) < e.map_len() and self.dbg >= WNG:
                        log(WNG, '(Layer - %s) String buffer not long ' \
                            'enough for %s' % (self.__class__, e.CallName))
                        #if self.safe:
                        #    return
                    e.map(string)
                    string = string[e.map_len():]
        # delete .map() *internal* attributes
        del self.__BitStack
        del self.__BitStack_len
    
    def __add_to_bitstack(self, bit_elt):
        # check for Bit() element transparency
        if not bit_elt.is_transparent():
            self.__BitStack += [bit_elt]
            self.__BitStack_len += bit_elt.bit_len()
            
    def __map_to_bitstack(self, string):
        # 1st check if string is long enough for the prepared BitStack
        if len(string) < self.__BitStack_len//8 and self.dbg >= ERR:
            log(ERR, '(Layer - %s) String buffer not long enough for %s' \
                % (self.__class__, self.__BitStack[-1].CallName))
            #if self.safe:
            #    return
        # string buffer parsing is done through intermediary
        # string buffer "s_stack"
        s_stack = string[:self.__BitStack_len//8]
        # create a bitstream "s_bin" for getting the full BitStack
        s_bin = ''
        for char in s_stack:
            # convert to bitstream thanks to python native bit repr
            s_bin_tmp = bin(ord(char))[2:]
            # prepend 0 to align on byte (python does not do it)
            # and append to the bitstream "s_bin" (string of 0 and 1)
            s_bin = ''.join((s_bin, (8-len(s_bin_tmp))*'0', s_bin_tmp))
        # map the bitstream "s_bin" into each BitStack element
        for bit_elt in self.__BitStack:
            bitlen = bit_elt.bit_len()
            if bitlen:
                # convert the bitstream "s_bin" into integer 
                # according to the length in bit of bit_elt
                bit_elt.map_bit( int(s_bin[:bit_elt.bit_len()], 2) )
                # truncate the "s_bin" bitstream
                s_bin = s_bin[bit_elt.bit_len():]
        # consume the global string buffer that has been mapped 
        # (from s_stack internal variable)
        # and reinitialize self.__BitStack* attributes
        string = string[self.__BitStack_len//8:]
        self.__BitStack = []
        self.__BitStack_len = 0
        # finally return string to parent method .map()
        return string
    
    # map_ret() maps a buffer to a Layer, the unaligned way,
    # and returns the rest of the buffer that was not mapped
    def map_ret(self, string=''):
        if self.dbg >= DBG:
            log(DBG, '(Layer.map_ret) entering map_ret() for %s' % self.CallName)
        # First take care of transparent Layer (e.g. in L3Mobile)
        if hasattr(self, 'Trans') and self.Trans:
            return string
        if self._byte_aligned is True:
            self.__map_aligned(string)
            return string[len(self):]
        else:
            # actually, map_ret() is only interesting for unaligned layers
            s = shtr(string)
            for e in self:
                if self.dbg >= DBG:
                    log(DBG, '(Layer.map_ret) %s, bit length: %i' \
                        % (e.CallName, e.bit_len()))
                    log(DBG, '(Layer.map_ret) string: %s' % hexlify(s))
                # this is beautiful
                s = e.map_ret(s)
            return s
    
    # define methods when Layer is in a Block:
    # next, previous, header: return Layer object reference
    # payload: returns Block object reference
    def get_index(self):
        if self.inBlock is not True: 
            return 0
        i = 0
        for l in self.Block:
            if l == self: return i
            else:  i += 1
        # could happen if Layer is placed in several Block()
        # and the last Block used is deleted
        return False
    
    def has_next(self):
        if self.inBlock is not True: 
            return False
        return self.Block.has_index(self.get_index()+1)
    
    def get_next(self):
        if self.has_next():
            return self.Block[self.get_index()+1]
        return RawLayer()
    
    def has_previous(self):
        if self.inBlock is not True: 
            return False
        index = self.get_index()
        if index <= 0:
            return False
        return self.Block.has_index(index-1)
        
    def get_previous(self):
        if self.has_previous():
            return self.Block[self.get_index()-1]
        return RawLayer()
        
    def get_header(self):
        if self.has_previous():
            index = self.get_index()
            i = index - 1
            while i >= 0:
                if self.Block[i].hierarchy == self.hierarchy-1:
                    return self.Block[i]
                else:
                    i -= 1
        return RawLayer()
    
    def get_payload(self):
        # return a Block, not a Layer like other methods 
        # for management into a Block
        pay = Block('pay')
        if self.has_next():
            index = self.get_index()
            for l in self.Block[ index+1 : ]:
                if l.hierarchy > self.hierarchy:
                    #pay.append( l.clone() )
                    # not needed to append a clone
                    # better keep reference to original layer
                    pay.append( l )
                else:
                    break
            if pay.num() == 0:
                pay.append( RawLayer() )
            return pay
        pay.append( RawLayer() )
        return pay
    
    def num(self):
        return 1
    
    # this is to retrieve full Layer's dynamicity from a mapped layer
    def reautomatize(self):
        for e in self:
            if hasattr(e, 'reautomatize'):
                e.reautomatize()
    
    def parse(self, s=''):
        self.map(s)
    
    # shar manipulation interface
    def to_shar(self):
        if hasattr(self, 'Trans') and self.Trans:
            return shar()
        bits = []
        for e in self:
            bits.append( e.to_shar().get_bits() )
        s = shar()
        s.set_bits( bits )
        return s
    
    def map_shar(self, sh):
        if self.dbg >= DBG:
            log(DBG, '(Layer.map) entering map() for %s' % self.CallName)
        # First take care of transparent Layer (e.g. in L3Mobile)
        if hasattr(self, 'Trans') and self.Trans:
            return
        if not isinstance(sh, shar):
            sh = shar(sh)
        for e in self:
            e.map_shar(sh)
libmich/formats/L3Mobile_IE.py
class MSCm2(Layer):
    constructorList = [
        Bit('spare1', Pt=0, BitLen=1),
        Bit('rev', Pt=1, BitLen=2, Repr='hum', Dict=Revision_level),
        Bit('ES', ReprName='Controlled early classmark sending', \
            Pt=0, BitLen=1, Repr='hum'),
        Bit('noA51', Pt=0, BitLen=1, Repr='hum'),
        Bit('RFclass', Pt=0, BitLen=3, Repr='hum', Dict=RFclass_dict),
        Bit('spare2', Pt=0, BitLen=1),
        Bit('PScap', Pt=0, BitLen=1, Repr='hum'),
        Bit('SSscreen', Pt=0, BitLen=2, Dict=SSscreen_dict, Repr='hum'),
        Bit('SMcap', Pt=0, BitLen=1, Repr='hum'),
        Bit('VBSnotif', Pt=0, BitLen=1, Repr='hum'),
        Bit('VGCSnotif', Pt=0, BitLen=1, Repr='hum'),
        Bit('Freqcap', Pt=0, BitLen=1, Repr='hum'),
        Bit('Classmark3', Pt=0, BitLen=1, Repr='hum'),
        Bit('spare3', Pt=0, BitLen=1),
        Bit('LCSVAcap', Pt=0, BitLen=1, Repr='hum'),
        Bit('UCS2', Pt=0, BitLen=1, Repr='hum'),
        Bit('SoLSA', Pt=0, BitLen=1, Repr='hum'),
        Bit('CMSP', ReprName='CM service prompt', Pt=0, \
            BitLen=1, Repr='hum'),
        Bit('A53', Pt=0, BitLen=1, Repr='hum'),
        Bit('A52', Pt=0, BitLen=1, Repr='hum')]
libmich/formats/L3Mobile_IE.py
class LAI(Layer):
    constructorList = [
        PLMN(),
        Int('LAC', Pt=0, Type='uint16', Repr='hex')
        ]
    
    def __init__(self, MCCMNC='00101', LAC=0x0000):
        Layer.__init__(self)
        self.PLMN.set_mcc(MCCMNC[:3])
        self.PLMN.set_mnc(MCCMNC[3:])
        self.LAC > LAC
    
    def __repr__(self):
        return '<[LAI]: %s / LAC: 0x%.4x>' % (self.PLMN.__repr__(), self.LAC())
libmich/formats/L3Mobile_IE.py
class MSCm1(Layer):
    constructorList = [
        Bit('spare', Pt=0, BitLen=1),
        Bit('rev', Pt=1, BitLen=2, Repr='hum', Dict=Revision_level),
        Bit('ES', ReprName='Controlled early classmark sending', \
            Pt=0, BitLen=1, Repr='hum'),
        Bit('noA51', Pt=0, BitLen=1, Repr='hum'),
        Bit('RFclass', Pt=0, BitLen=3, Repr='hum', Dict=RFclass_dict)]
libmich/core/element.py
class Str(Element):
    '''
    class defining a standard Element, 
    managed like a stream of byte(s), or string.
    It is always byte-aligned (in term of length, at least)
    
    attributes:
    Pt: to point to another stream object (can simply be a string);
    PtFunc: when defined, PtFunc(Pt) is used 
            to generate the str() / len() representation;
    Val: when defined, overwrites the Pt (and PtFunc) string value, 
         used when mapping a string buffer to the element;
    Len: can be set to a fixed int value, or to another object
         when called by LenFunc
    LenFunc: to be used when mapping string buffer with variable length
             (e.g. in TLV object), LenFunc(Len) is used;
    Repr: python representation; binary, hexa, human or ipv4;
    Trans: to define transparent element which has empty str() and len() to 0,
           it "nullifies" its existence; can point to something for automation;
    TransFunc: when defined, TransFunc(Trans) is used to automate the 
               transparency aspect: used e.g. for conditional element;
    '''
    
    # this is used when printing the object representation
    _repr_limit = 1024
    _reprs = ['hex', 'bin', 'hum', 'ipv4']
    
    # padding is used when .Pt and .Val are None, 
    # but Str instance has still a defined .Len attribute
    _padding_byte = '\0'
    
    def __init__(self, CallName='', ReprName=None, 
                 Pt=None, PtFunc=None, Val=None, 
                 Len=None, LenFunc=None,
                 Repr="hum",
                 Trans=False, TransFunc=None):
        if CallName or not self.CallName:
            self.CallName = CallName
        if ReprName is None :
            self.ReprName = ''
        else :
            self.ReprName = ReprName
        self.Pt = Pt
        self.PtFunc = PtFunc
        self.Val = Val
        self.Len = Len
        self.LenFunc = LenFunc
        self.Type = 'stream'
        self.Repr = Repr
        self.Trans = Trans
        self.TransFunc = TransFunc
    
    def __setattr__(self, attr, val):
        # ensures no bullshit is provided into element's attributes 
        # (however, it is not a exhaustive test...)
        # managed with the class "safe" trigger
        if self.safe :
            if attr == 'CallName' :
                if type(val) is not str or len(val) == 0 :
                    raise AttributeError('CallName must be a non-null string')
            elif attr == 'ReprName' :
                if type(val) is not str:
                    raise AttributeError('ReprName must be a string')
            elif attr == 'PtFunc' :
                if val is not None and not isinstance(val, type_funcs) :
                    raise AttributeError('PtFunc must be a function')
            elif attr == 'Val' :
                if val is not None and not isinstance(val, \
                (str, bytes, Element, Layer, Block, tuple, list)) :
                    raise AttributeError('Val must be a string or something ' \
                    'that makes a string at the end...')
            elif attr == 'Len' :
                if val is not None and not isinstance(val, \
                (int, tuple, Element, type_funcs)) :
                    raise AttributeError('Len must be an int or element')
            elif attr == 'LenFunc' :
                if val is not None and not isinstance(val, type_funcs) :
                    raise AttributeError('LenFunc must be a function')
            elif attr == 'Repr' :
                if val not in self._reprs :
                    raise AttributeError('Repr %s does not exist, only: %s' \
                          % (val, self._reprs))
            elif attr == 'TransFunc' :
                if val is not None and not isinstance(val, type_funcs) :
                    raise AttributeError('TransFunc must be a function')
        # this is for Layer() pointed by Pt attr in Str() object
        #if isinstance(self.Pt, Layer) and hasattr(self.Pt, attr):
        #    setattr(self.Pt, attr, val)
        # ...does not work properly
        # and the final standard python behaviour
        object.__setattr__(self, attr, val)
    
    def __getattr__(self, attr):
        # this is for Layer() pointed by Pt attr in Str() object
        if isinstance(self.Pt, Layer) and hasattr(self.Pt, attr):
            return getattr(self.Pt, attr)
        # and the final standard python behaviour
        object.__getattr__(self, attr)
    
    # the libmich internal instances check
    # this is highly experimental...
    def __is_intern_inst(self, obj):
        return isinstance(obj, (Element, Layer, Block))
    
    # building basic methods for manipulating easily the Element 
    # from its attributes
    def __call__(self, l=None):
        # when length has fixed value:
        if not l and type(self.Len) is int:
            l = self.Len
        #else:
        #    l = None
        # when no values are defined at all:
        if self.Val is None and self.Pt is None: 
            if l: return l * self._padding_byte
            else: return ''
        # returning the right string:
        # if defined, self.Val overrides self.Pt capabilities
        elif self.Val is not None:
            # allow to pass tuple or list of libmich internal instances
            if isinstance(self.Val, (list, tuple)) \
            and False not in map(self.__is_intern_inst, self.Val):
                return ''.join(map(str, self.Val))[:l]
            return str(self.Val)[:l]
        # else: use self.Pt capabilities to get the string
        elif self.PtFunc is not None: 
            if self.safe: 
                assert(hasattr(self.PtFunc(self.Pt), '__str__'))
            return str(self.PtFunc(self.Pt))[:l]
        else:
            # allow to pass tuple or list of libmich internal instances
            if isinstance(self.Pt, (list, tuple)) \
            and False not in map(self.__is_intern_inst, self.Pt):
                return ''.join(map(str, self.Pt))[:l]
            # otherwise, handle simply as is
            if self.safe: 
                assert(hasattr(self.Pt, '__str__'))
            return str(self.Pt)[:l]
    
    def __str__(self):
        # when Element is Transparent:
        if self.is_transparent():
            return ''
        #else:
        return self()
    
    def __len__(self):
        # does not take the LenFunc(Len) into account
        # When a Str is defined, the length is considered dependent of the Str
        # the Str is dependent of the LenFunc(Len) only 
        # when mapping data into the Element
        return len(self.__str__())
    
    def bit_len(self):
        return len(self)*8
    
    def map_len(self):
        # need special length definition 
        # when mapping a string to the 'Str' element 
        # that has no fixed length
        #
        # uses LenFunc, applied to Len, when length is variable:
        # and TransFunc, applied to Trans, to managed potential transparency
        # (e.g. for optional element triggered by other element)
        #
        if self.Len is None:
            return None
            #return 0
        if self.LenFunc is None: 
            return self.Len
        else:
            if self.safe:
                assert( type(self.LenFunc(self.Len)) in (int, long) )
            return self.LenFunc(self.Len)
    
    def __int__(self):
        # big endian integer representation of the string buffer
        return shtr(self).left_val(len(self)*8)
    
    def __bin__(self):
        # does not use the standard python 'bin' function to keep 
        # the right number of prefixed 0 bits
        h = hex(self)
        binary = ''
        for i in xrange(0, len(h), 2):
            b = format( int(h[i:i+2], 16), 'b' )
            binary += ( 8-len(b) ) * '0' + b
        return binary
    
    def __hex__(self):
        return self().encode('hex')
    
    def __repr__(self):
        # check for simple representations
        if self.Pt is None and self.Val is None: 
            return repr(None)
        if self.Repr == 'ipv4':
            #if self.safe: assert( len(self) == 4 )
            if len(self) != 4:
                return '0x%s' % hex(self)
            return inet_ntoa( self.__str__() )
        elif self.Repr == 'hex': 
            ret = '0x%s' % hex(self)
        elif self.Repr == 'bin': 
            ret = '0b%s' % self.__bin__()
        # check for the best human-readable representation
        elif self.Repr == 'hum':
            # standard return
            ret = repr( self() )
            # complex return:
            # allow to assign a full Block or Layer to a Str...
            if self.__is_intern_inst(self.Pt):
                ret = repr(self.Pt)
            if self.__is_intern_inst(self.Val):
                ret = repr(self.Val)
            # allow to assign a list or tuple of Block or Layer...
            if isinstance(self.Pt, (list, tuple)) \
            and False not in map(self.__is_intern_inst, self.Pt):
                ret = '|'.join(map(repr, self.Pt))
            if isinstance(self.Val, (list, tuple)) \
            and False not in map(self.__is_intern_inst, self.Val):
                ret = '|'.join(map(repr, self.Val))
            # finally, self.Val can be a raw value... still
            if self.Val is not None and hasattr(self.Val, '__repr__'):
                ret = repr(self.Val)
        # truncate representation if string too long:
        # avoid terminal panic...
        if len(ret) <= self._repr_limit:
            return ret
        else:
            return ret[:self._repr_limit-3]+'...'
    
    # some more methods for checking Element's attributes:
    def getattr(self):
        return ['CallName', 'ReprName', 'Pt', 'PtFunc', 'Val', 'Len',
                'LenFunc', 'Type', 'Repr', 'Trans', 'TransFunc']
    
    def showattr(self):
        for a in self.getattr():
            print('%s : %s' % (a, repr(self.__getattribute__(a))) )
    
    # cloning an Element, useful for "duplicating" an Element 
    # without keeping any dependency
    # used in Layer with Element
    # However,
    #...
    # This is not that true, as an object pointed by .Pt or .Len or .Trans
    # will not be updated to its clone()
    # Conclusion:
    # use this with care
    def clone(self):
        clone = self.__class__(
                 self.CallName, self.ReprName,
                 self.Pt, self.PtFunc,
                 self.Val, 
                 self.Len, self.LenFunc,
                 self.Repr,
                 self.Trans, self.TransFunc )
        return clone
    
    # standard method map() to map a string to the Element
    def map(self, string=''):
        if not self.is_transparent():
            l = self.map_len()
            if l is not None:
                self.Val = string[:l]
            else:
                self.Val = string
            if self.dbg >= DBG:
                log(DBG, '(Element) mapping %s on %s, %s' \
                    % (repr(string), self.CallName, repr(self)))
    
    def map_ret(self, string=''):
        self.map(string)
        return string[len(self):]
    
    # shar manipulation interface
    def to_shar(self):
        ret = shar()
        ret.set_buf(self())
        return ret
    
    def map_shar(self, sh):
        if not self.is_transparent():
            l = self.map_len()
            if l is not None:
                self.Val = sh.get_buf(l*8)
            else:
                self.Val = sh.get_buf()
            if self.dbg >= DBG:
                log(DBG, '(Element) mapping %s on %s, %s' \
                    % (repr(string), self.CallName, repr(self)))
libmich/formats/L3Mobile_24007.py
class LayerTLV(Layer):
class Type1_V(LayerTLV):
class Type1_TV(LayerTLV):
class Type2(LayerTLV):
class Type3_V(LayerTLV):
class Type3_TV(LayerTLV):
class Type4_LV(LayerTLV):
class Type4_TLV(LayerTLV):
class Type6_LVE(LayerTLV):
class Type6_TLVE(LayerTLV):
class StrRR(Str):
class Layer3(Layer):
    def getobj(self):
    def __init__(self, CallName='', ReprName='', V=0):
    def __init__(self, CallName='', ReprName='', T=0, V=0, \
                 Trans=False, Dict=None):
    def __len__(self):
    def __init__(self, CallName='', ReprName='', T=0, Trans=False):
    def __len__(self):
    def __init__(self, CallName='', ReprName='', V='\0', Len=1):
    def __init__(self, CallName='', ReprName='', T=0, V='\0', \
                 Len=1, Trans=False):
    def __len__(self):
    def __init__(self, CallName='', ReprName='', V='\0'):
    def __init__(self, CallName='', ReprName='', T=0, V='\0', Trans=False):
    def __len__(self):
    def __init__(self, CallName='', ReprName='', V='\0'):
    def __init__(self, CallName='', ReprName='', T=0, V='\0', Trans=False):
    def __len__(self):
    def __call__(self):
    def __init__(self, CallName='', ReprName='', Trans=False, **kwargs):
    def _post_init(self, with_options=True, **kwargs):
    def _len_gsmrr(self, string=''):
    def map(self, string=''):
    def interpret_IE(self, field, cn):
    def __map_opts(self, string=''):
    def _select_tag_old_(self, s='\0', taglist=[]):
    def _select_tag(self, s='\0', taglist=[]):
    def __get_opts(self):
    def __map_opt(self, tag, string, opt_ie):
    def __map_unknown_opt(self, string):
    def show(self, with_trans=False):
        GSM_RR = False
libmich/formats/L3Mobile_IE.py
class ID(Layer):
    constructorList = [
        Bit('digit1', Pt=0, BitLen=4, Repr='hum'),
        Bit('odd', Pt=1, BitLen=1, Repr='hum'),
        Bit('type', BitLen=3, Dict=IDtype_dict, Repr='hum')]
    
    def __init__(self, val='0', type='No Identity'):
        Layer.__init__(self)
        self.type > IDtype_dict[type]
        if type in ('IMSI', 'IMEI', 'IMEISV'):
            self.__handle_digits(val)
            if len(val)%2 == 0:
                self.odd > 0
        elif type == 'TMSI':
            self.digit1 > 0b1111
            self.odd > 0
            self.append(Str('tmsi', Pt=val[0:4], Len=4, Repr='hex'))
        elif self.type() > 4:
            self.append(Str('data', Pt=val, Repr='hex'))
    
    def map(self, s=''):
        Layer.map(self, s)
        # imsi, imei, imeisv
        if self.type() in (1,2,3):
            self.__handle_digits(''.join(['0']*(len(s[1:9])*2)))
            #if self.odd() == 0:
            #    self[-1].CallName = 'end'
        # tmsi
        elif self.type() == 4:
            self.append(Str('tmsi', Len=4, Repr='hex'))
        elif self.type() > 4:
            self.append(Str('data', Repr='hex'))
        Layer.map(self, s)
    
    def __handle_digits(self, digits=''):
        try:
            self.digit1 > int(digits[0])
        except ValueError:
            debug(self.dbg, 2, '(ID) non digit character: %s' % digits[0])
        ext, i = [], 2
        for d in digits[1:]:
            try:
                ext.append(Bit('digit%i'%i, Pt=int(d), BitLen=4, Repr='hum'))
            except ValueError:
                ext.append(Bit('digit%i'%i, Pt=0, BitLen=4, Repr='hum'))
            if len(ext) == 2:
                ext.reverse()
                self.extend(ext)
                ext = []
            i+=1
        if len(ext) == 1:
            ext.append(Bit('digit%i'%i, Pt=0b1111, BitLen=4, Repr='hum'))
            #ext.append(Bit('end', Pt=0b1111, BitLen=4, Repr='hum'))
            ext.reverse()
            self.extend(ext)
    
    def __repr__(self):
        t = self.type()
        # no id
        if t == 0:
            repr = IDtype_dict[t]
        # imsi, imei, imeisv
        elif self.type() in (1, 2, 3):
            repr = '%s:%s' % (IDtype_dict[t], self.get_bcd())
        # tmsi
        elif t == 4:
            repr = '%s:0x%s' % (IDtype_dict[t], hex(self.tmsi))
        # not handled
        else:
            repr = '%s:0x%s' % (IDtype_dict[t], hex(self.data))
        return '<[ID]: %s>' % repr
    
    def get_imsi(self):
        if self.type() == 1:
            return self.get_bcd()
        else:
            return ''
    
    def get_imei(self):
        if self.type() in (2, 3):
            return self.get_bcd()
        else:
            return ''
    
    def get_bcd(self):
        return ''.join([str(getattr(self, 'digit%s'%i)()) \
                        for i in range(1, len(self)*2+self.odd()-1)])
    
    def anon(self):
        # for IMSI and IMEI, clear some digits to anonymize identities 
        if self.type() in (1, 2, 3):
            if hasattr(self, 'digit8'):
                self.digit8 < None
                self.digit8 > 0
            if hasattr(self, 'digit9'):
                self.digit9 < None
                self.digit9 > 0
            if hasattr(self, 'digit10'):
                self.digit10 < None
                self.digit10 > 0
            if hasattr(self, 'digit11'):
                self.digit11 < None
                self.digit11 > 0
            if hasattr(self, 'digit12'):
                self.digit12 < None
                self.digit12 > 0
libmich/core/element.py
class Int(Element):
    '''
    class defining a standard element, managed like an integer.
    It can be signed (int) or unsigned (uint),
    and support arbitrary byte-length:
        int8, int16, int24, int32, int40, int48, int56, int64, int72, ...
        uint8, uint16, uint24, uint32, ..., uint128, ...
        and why not int65536 !
    It is always byte-aligned (in term of length, at least).
    
    attributes:
    Pt: to point to another object or direct integer value;
    PtFunc: when defined, PtFunc(Pt) is used to generate the integer value;
    Val: when defined, overwrites the Pt (and PtFunc) integer value, 
         used when mapping a string buffer to the element;
    Type: type of integer for encoding, 8,16,24,32,40,48,56,64 bits signed or
          unsigned integer;
    Dict: dictionnary to use for a look-up when representing 
          the element into python;
    Repr: representation style, binary, hexa or human: human uses Dict 
          if defined;
    Trans: to define transparent element which has empty str() and len() to 0,
           it "nullifies" its existence; can point to something for automation;
    TransFunc: when defined, TransFunc(Trans) is used to automate the 
               transparency aspect: used e.g. for conditional element;
    '''
    # endianness is 'little' / 'l' or 'big' / 'b'
    _endian = 'big'
    # types format for struct library
    # 24 (16+8), 40 (32+8), 48 (32+16), 56 (32+16+8)
    _types = { 'int8':'b', 'int16':'h', 'int32':'i', 'int64':'q',
               #'int24':None, 'int40':None, 'int48':None, 'int56':None,
               'uint8':'B', 'uint16':'H', 'uint32':'I', 'uint64':'Q',
               #'uint24':None, 'uint40':None, 'uint48':None, 'uint56':None,
               }
    #
    # for object representation
    _reprs = ['hex', 'bin', 'hum']
    
    def __init__(self, CallName='', ReprName=None,
                 Pt=None, PtFunc=None, Val=None,
                 Type='int32', Dict=None, DictFunc=None,
                 Repr='hum',
                 Trans=False, TransFunc=None):
        if CallName or not self.CallName:
            self.CallName = CallName
        if ReprName is None:
            self.ReprName = ''
        else:
            self.ReprName = ReprName
        self.Pt = Pt
        self.PtFunc = PtFunc
        self.Val = Val
        self.Type = Type
        self.Dict = Dict
        self.DictFunc = DictFunc
        self.Repr = Repr
        self.Trans = Trans
        self.TransFunc = TransFunc
        # automated attributes:
        self.Len = int(self.Type.lstrip('uint'))//8
    
    def __setattr__(self, attr, val):
        # ensures no bullshit is provided into element's attributes 
        # (however, it is not a complete test...)
        if self.safe:
            if attr == 'CallName':
                if type(val) is not str or len(val) == 0:
                    raise AttributeError('CallName must be a non-null string')
            elif attr == 'ReprName':
                if type(val) is not str:
                    raise AttributeError('ReprName must be a string')
            elif attr == 'PtFunc':
                if val is not None and not isinstance(val, type_funcs) :
                    raise AttributeError('PtFunc must be a function')
            elif attr == 'Val':
                if val is not None and not isinstance(val, (int, long)):
                    raise AttributeError('Val must be an int or long')
            elif attr == 'Type':
                cur = 0
                if val[cur] == 'u':
                    cur = 1
                if val[cur:cur+3] != 'int' or int(val[cur+3:]) % 8 != 0:
                    raise AttributeError('Type must be intX / uintX with X '\
                    'multiple of 8 bits (e.g. int24, uint32, int264, ...)')
            elif attr == 'DictFunc':
                if val is not None and not isinstance(val, type_funcs) :
                    raise AttributeError('DictFunc must be a function')
            elif attr == 'Repr':
                if val not in self._reprs:
                    raise AttributeError('Repr %s does not exist, use in: %s' \
                          % (val, self._reprs))
            elif attr == 'TransFunc':
                if val is not None and not isinstance(val, type_funcs) :
                    raise AttributeError('TransFunc must be a function')
        if attr == 'Type':
            self.Len = int(val.lstrip('uint'))//8
        object.__setattr__(self, attr, val)
    
    def __call__(self):
        # when no values are defined at all, arbitrary returns None:
        if self.Val is None and self.Pt is None:
            # instead of "return None" 
            # that triggers error when calling __str__() method
            return 0
        # else, Val overrides Pt capabilities:
        # transparency are not taken into account in __call__, 
        # only for the __str__ representation
        elif self.Val is not None: 
            return self.__confine( self.Val )
        elif self.PtFunc is not None: 
            if self.safe: 
                assert( type(self.PtFunc(self.Pt)) in (int, long) )
            return self.__confine(self.PtFunc(self.Pt))
        else:
            return self.__confine(int(self.Pt))
    
    def __confine(self, value):
        # unsigned
        if self.Type[0] == 'u':
            return max(0, min(2**(8*self.Len)-1, value))
        # signed
        else:
            return max(-2**(8*self.Len-1), min(2**(8*self.Len-1)-1, value))
    
    def __str__(self):
        # manages Element transparency
        if self.is_transparent():
                return ''
        # otherwise returns standard string values
        return self.__pack()
    
    def __len__(self):
        if self.is_transparent(): 
            return 0
        return self.Len
    
    def bit_len(self):
        return 8*self.__len__()
    
    # map_len() is a-priori not needed in "Int" element, 
    # but still kept for Element API uniformity
    def map_len(self):
        return self.__len__()
    
    # define integer value
    def __int__(self):
        return self()
    
    def __bin__(self):
        # unsigned or positive signed:
        val = self()
        if self.Type[0] == 'u' \
        or self.Type[0] == 'i' and val >= 0 : 
            binstr = bin(val)[2:]
            if self._endian[0] == 'l':
                # little endian
                bs = '0'*(8*self.__len__() - len(binstr)) + binstr
                bs = [bs[i:i+8] for i in range(0, len(bs), 8)]
                bs.reverse()
                return ''.join(bs)
            else:
                # big endian
                return '0'*(8*self.__len__() - len(binstr)) + binstr
        # negative signed
        else : 
            # takes 2' complement to the signed val
            binstr = bin(val + 2**(8*self.__len__()-1))[2:]
            if self._endian[0] == 'l':
                # little endian
                bs = '1' + '0'*(8*self.__len__() - len(binstr) - 1) + binstr
                bs = [bs[i:i+8] for i in range(0, len(bs), 8)]
                bs.reverse()
                return ''.join(bs)
            else:
                # big endian
                return '1' + '0'*(8*self.__len__() - len(binstr) - 1) + binstr
    
    def __hex__(self):
        return hexlify(self.__pack())
    
    def __repr__(self):
        if self.Pt is None and self.Val is None:
            return repr(None)
        if self.Repr == 'hex':
            return '0x%s' % self.__hex__()
        elif self.Repr == 'bin':
            return '0b%s' % self.__bin__()
        elif self.Repr == 'hum':
            value = self()
            if self.DictFunc:
                if self.safe:
                    assert(hasattr(self.DictFunc(self.Dict), '__getitem__'))
                try:
                    val = '%i : %s' % (value, self.DictFunc(self.Dict)[value])
                except KeyError:
                    val = value
            elif self.Dict:
                try:
                    val = '%i : %s' % (value, self.Dict[value])
                except KeyError:
                    val = value
            else:
                val = value
            rep = repr(val)
            if rep[-1] == 'L':
                return rep[:-1]
            return rep
    
    def getattr(self):
        return ['CallName', 'ReprName', 'Pt', 'PtFunc', 'Val', 'Len',
                'Type', 'Dict', 'DictFunc', 'Repr', 'Trans', 'TransFunc']
    
    def showattr(self):
        for a in self.getattr():
            if a == "Dict" and self.Dict is not None:
                print('%s : %s' % ( a, self.__getattribute__(a).__class__) )
            else:
                print('%s : %s' % ( a, repr(self.__getattribute__(a))) )
    
    def clone(self):
        clone = self.__class__(
                 self.CallName, self.ReprName,
                 self.Pt, self.PtFunc,
                 self.Val, self.Type,
                 self.Dict, self.DictFunc, self.Repr, 
                 self.Trans, self.TransFunc )
        #clone._endian = self._endian
        return clone
    
    def map(self, string=''):
        if not self.is_transparent():
            # error log will be done by the Layer().map() method
            # but do this not to throw exception
            if len(string) < self.Len:
                if self.dbg >= WNG:
                    log(WNG, '(%s) %s map(string) : string not long enough' \
                        % (self.__class__, self.CallName))
                return
            self.Val = self.__unpack(string[:self.Len])
    
    def map_ret(self, string=''):
        l = self.__len__()
        if 0 < l <= len(string):
            self.map(string)
            return string[l:]
        else:
            return string
    
    def __pack(self):
        # manage endianness (just in case...)
        if self._endian[0] == 'l':
            e = '<'
        else:
            e = '>'
        if self.Len == 0:
            return ''
        elif self.Len in (1, 2, 4, 8):
            # standard types for using Python struct
            return pack(e+self._types[self.Type], self())
        elif self.Type[0] == 'u':
            # non-standard unsigned int types
            return self.__pack_uX(e)
        else:
            # non-standard signed int types
            return self.__pack_iX(e)
    
    def __unpack(self, string):
        if self._endian[0] == 'l':
            e = '<'
        else:
            e = '>'
        if self.Len == 0:
            return
        elif self.Len in (1, 2, 4, 8):
            # standard types for using Python struct
            return unpack(e+self._types[self.Type], string[:self.Len])[0]
        elif self.Type[0] == 'u':
            # non-standard unsigned int types
            return self.__unpack_uX(string[:self.Len], e)
        else:
            # non-standard signed int types
            return self.__unpack_iX(string[:self.Len], e)
    
    def __pack_uX(self, e='>'):
        if e == '<':
            # little endian, support indefinite uint (e.g. uint3072)
            if self.Len <= 8:
                return pack('<Q', self())[:self.Len]
            else:
                #'''
                u8 = decompose(0x100, self())
                # pad with 0 until the right length
                if len(u8) < self.Len:
                    u8.extend( [0]*(self.Len-len(u8)) )
                return pack('<'+self.Len*'B', *u8)
        else:
            # big endian, support indefinite uint too
            if self.Len <= 8:
                return pack('>Q', self())[-self.Len:]
            else:
                u64 = decompose(0x10000000000000000, self())
                u64_len = 8*len(u64)
                u64_pad = 0
                if u64_len < self.Len:
                    rest_len = self.Len - u64_len
                    u64_pad = rest_len // 8
                    if rest_len % 8:
                        u64_pad += 1
                    u64.extend( [0]*u64_pad )
                u64.reverse()
                return pack('>'+len(u64)*'Q', *u64)[-self.Len:]
    
    def __pack_iX(self, e='>'):
        val = self()
        # positive values are encoded just like uint
        if val >= 0:
            return self.__pack_uX(e)
        #
        # negative values, 2's complement encoding
        if e == '<':
            # little endian
            if self.Len <= 8:
                return pack('<Q', 2**(self.Len*8) - abs(val))[:self.Len]
            else:
                i8 = decompose(0x100, 2**(self.Len*8) - abs(val))
                if len(i8) < self.Len:
                    i8.extend( [0]*(self.Len-len(i8)) )
                return pack('<'+self.Len*'B', *i8)
        else:
            # big endian
            if self.Len <= 8:
                return pack('>Q', 2**(self.Len*8) - abs(val))[-self.Len:]
            else:
                i8 = decompose(0x100, 2**(self.Len*8) - abs(val))
                if len(i8) < self.Len:
                    i8.extend( [0]*(self.Len-len(i8)) )
                i8.reverse()
                return pack('>'+self.Len*'B', *i8)
    
    def __unpack_uX(self, string, e='>'):
        if e == '<':
            # little endian, support indefinite uint (e.g. uint3072)
            if self.Len <= 8:
                return unpack('<Q', string + '\0'*(8 - self.Len))[0]
            else:
                u8 = list(unpack('<'+self.Len*'B', string))
                u8.reverse()
                return reduce(lambda x,y: (x<<8)+y, u8)
        else:
            # big endian, support indefinite uint (e.g. uint3072)
            if self.Len <= 8:
                return unpack('>Q', '\0'*(8 - self.Len) + string)[0]
            else:
                #'''
                u64_len, u64_pad = self.Len // 8, self.Len % 8
                if u64_pad:
                    u64_len += 1
                    string = '\0'*u64_pad + string
                u64 = unpack('>'+u64_len*'Q', string)
                return reduce(lambda x,y:(x<<64)+y, u64)
    
    def __unpack_iX(self, string, e='>'):
        if e == '<':
            # little endian
            if ord(string[-1]) & 0x80 == 0:
                # check if it is a positive value
                return self.__unpack_uX(string, e)
            elif self.Len <= 8:
                return unpack('<Q', string + '\0'*(8 - self.Len))[0] \
                       - 2**(self.Len*8)
            else:
                i8 = list(unpack('<'+self.Len*'B', string))
                i8.reverse()
                return reduce(lambda x,y:(x<<8)+y, i8) - 2**(self.Len*8)
        else:
            # big endian
            if ord(string[0]) & 0x80 == 0:
                # check if it is a positive value
                return self.__unpack_uX(string, e)
            elif self.Len <= 8:
                return unpack('>Q', '\0'*(8 - self.Len) + string)[0] \
                       - 2**(self.Len*8)
            else:
                i8 = unpack('>'+self.Len*'B', string)
                return reduce(lambda x,y:(x<<8)+y, i8) - 2**(self.Len*8)
    
    # shar manipulation interface
    def to_shar(self):
        ret = shar()
        ret.set_buf(self.__str__())
        return ret
    
    def map_shar(self, sh):
        if len(sh)*8 < self.Len:
            if self.dbg >= WNG:
                log(WNG, '(%s) %s map(string) : shar buffer not long enough' \
                    % (self.__class__, self.CallName))
            return
        # standard handling
        if not self.is_transparent():
            self.map(sh.get_buf(len(self)*8))
libmich/formats/L3Mobile_IE.py
class PLMNList(Layer):
    constructorList = [ ]
    
    def __init__(self, *args, **kwargs):
        Layer.__init__(self)
        self.add_plmn(*args)
    
    def add_plmn(self, *args):
        for arg in args:
            if arg.isdigit() and len(arg) in (5, 6):
                self.append( PLMN(arg) )
            elif isinstance(arg, PLMN):
                self.append( arg )
            elif isinstance(arg, (list, tuple)):
                # this is truly recursive :)
                for e in arg:
                    self.add_plmn(e)
    
    def add_PLMN(self, plmn=PLMN()):
        # this was the old method
        if isinstance(plmn, PLMN):
            self.append(plmn)
    
    def map(self, s=''):
        Layer.map(self, s)
        s = s[3:]
        while len(s) > 0:
            self.append( PLMN() )
            self[-1].map(s)
            s = s[3:]
    
    def interpret(self):
        return ''.join([str(plmn.interpret()) for plmn in self])
libmich/core/element.py
class Bit(Element):
    '''
    class defining a standard element, managed like a bit (e.g. a flag)
    or bit-stream of variable bit length
    Values are corresponding to unsigned integer: from 0 to pow(2, bit_len)-1.
    It does not require to be byte-aligned.
    
    attributes:
    Pt: to point to another object or direct integer value;
    PtFunc: when defined, PtFunc(Pt) is used to generate the integer value;
    Val: when defined, overwrites the Pt (and PtFunc) integer value, 
         used when mapping string to the element;
    BitLen: length in bits of the bit stream;
    BitLenFunc: to be used when mapping string with variable bit-length, 
                BitLenFunc(BitLen) is used;
    Dict: dictionnary to use for a look-up when representing 
          the element into python;
    Repr: representation style, binary, hexa or human: human uses Dict;
    Trans: to define transparent element which has empty str() and len() to 0,
           it "nullifies" its existence; can point to something for automation;
    TransFunc: when defined, TransFunc(Trans) is used to automate the 
               transparency aspect: used e.g. for conditional element;
    '''
    # for object representation
    _reprs = ['hex', 'bin', 'hum']
    
    def __init__(self, CallName='', ReprName=None, 
                 Pt=None, PtFunc=None, Val=None, 
                 BitLen=1, BitLenFunc=None,
                 Dict=None, DictFunc=None, Repr='bin', 
                 Trans=False, TransFunc=None):
        if CallName or not self.CallName:
            self.CallName = CallName
        if ReprName is None: 
            self.ReprName = ''
        else: 
            self.ReprName = ReprName
        self.Pt = Pt
        self.PtFunc = PtFunc
        self.Val = Val
        self.BitLen = BitLen
        self.BitLenFunc = BitLenFunc
        self.Dict = Dict
        self.DictFunc = DictFunc
        self.Repr = Repr
        self.Trans = Trans
        self.TransFunc = TransFunc
    
    def __setattr__(self, attr, val):
        # ensures no bullshit is provided into element's attributes 
        # (however, it is not a complete test...)
        if self.safe:
            if attr == 'CallName':
                if type(val) is not str or len(val) == 0:
                    raise AttributeError('CallName must be a non-null string')
            elif attr == 'ReprName':
                if type(val) is not str:
                    raise AttributeError('ReprName must be a string')
            elif attr == 'PtFunc':
                if val is not None and not isinstance(val, type_funcs) :
                    raise AttributeError('PtFunc must be a function')
            elif attr == 'Val':
                if val is not None and not isinstance(val, (int, long)):
                    raise AttributeError('Val must be an int')
            elif attr == 'BitLenFunc':
                if val is not None and not isinstance(val, type_funcs) :
                    raise AttributeError('BitLenFunc must be a function')
            elif attr == 'DictFunc':
                if val is not None and not isinstance(val, type_funcs) :
                    raise AttributeError('DictFunc must be a function')
            elif attr == 'Repr':
                if val not in self._reprs:
                    raise AttributeError('Repr %s does not exist, use in: %s' \
                          % (val, self._reprs))
            elif attr == 'TransFunc':
                if val is not None and not isinstance(val, type_funcs) :
                    raise AttributeError('TransFunc must be a function')
        object.__setattr__(self, attr, val)
    
    def __call__(self):
        if self.Val is None and self.Pt is None: return 0
        if self.Val is not None: return self.__confine(self.Val) 
        elif self.PtFunc is not None:
            if self.safe:
                assert( type(self.PtFunc(self.Pt)) is int )
            return self.__confine(self.PtFunc(self.Pt))
        else: return self.__confine(int(self.Pt))
    
    def __confine(self, value):
        # makes sure value provided does not overflow bit length
        return max( 0, min( pow(2, self.bit_len())-1, value ))
    
    def __str__(self):
        # return the string representation of the integer value
        # big-endian encoded
        # left-aligned according to the bit length
        # -> last bits of the last byte are nullified
        # 
        # manages Element transparency
        if self.is_transparent():
            return ''
        # do it the dirty way:
        h = self.__hex__()
        if not h:
            return ''
        if len(h) % 2: h = ''.join(('0', h))
        return str(shtr(unhexlify(h)) << (8-(self.bit_len()%8))%8)
    
    def _shar__str__(self):
        bitlen = self.bit_len()
        if bitlen == 0:
            return ''
        ret = shar()
        ret.set_uint(self(), bitlen)
        return ret.to_buf()
    
    def __len__(self):
        # just for fun here, 
        # but do not use this in program...
        bitlen = self.bit_len()
        if bitlen % 8:
            return (bitlen//8) + 1
        return bitlen//8
    
    def bit_len(self):
        # manages Element transparency
        if self.is_transparent():
            return 0
        # and standard bit length processing
        if self.BitLenFunc is not None:
            if self.safe:
                assert( type(self.BitLenFunc(self.BitLen)) is int )
            return self.BitLenFunc(self.BitLen)
        else:
            if self.safe:
                assert( type(self.BitLen) is int )
            return self.BitLen
    
    def map_len(self):
        bitlen = self.bit_len()
        if bitlen % 8:
            return (bitlen//8)+1
        return bitlen//8
    
    def __hex__(self):
        bitlen = self.bit_len()
        if not bitlen:
            return ''
        hexa = hex(self())[2:]
        if hexa[-1] == 'L':
            # thx to Python to add 'L' for long on hex repr...
            hexa = hexa[:-1]
        if self.bit_len()%4: 
            return '0'*(self.bit_len()//4 + 1 - len(hexa)) + hexa
        else: 
            return '0'*(self.bit_len()//4 - len(hexa)) + hexa
    
    def __int__(self):
        return self()
        
    def __bin__(self):
        bitlen = self.bit_len()
        if not bitlen:
            return ''
        binary = format(self(), 'b')
        return (bitlen - len(binary))*'0' + binary
        
    def __repr__(self):
        if self.Repr == 'hex': return '0x%s' % self.__hex__()
        elif self.Repr == 'bin': return '0b%s' % self.__bin__()
        elif self.Repr == 'hum':
            value = self()
            if self.DictFunc:
                if self.safe:
                    assert(hasattr(self.DictFunc(self.Dict), '__getitem__'))
                try: val = '%i : %s' % (value, self.DictFunc(self.Dict)[value])
                except KeyError: val = value
            elif self.Dict:
                try: val = '%i : %s' % (value, self.Dict[value])
                except KeyError: val = value
            else:
                val = value
            #return repr(val)
            rep = repr(val)
            if rep[-1] == 'L':
                rep = rep[:-1]
            return rep
    
    def getattr(self):
        return ['CallName', 'ReprName', 'Pt', 'PtFunc', 'Val', 'BitLen',
                'BitLenFunc', 'Dict', 'DictFunc', 'Repr', 'Trans', 'TransFunc']
    
    def showattr(self):
        for a in self.getattr():
            if a == 'Dict' and self.Dict is not None: 
                print('%s : %s' % ( a, self.__getattribute__(a).__class__) )
            else: 
                print('%s : %s' % ( a, repr(self.__getattribute__(a))) )
    
    # cloning an element, used in set of elements
    def clone(self):
        clone = self.__class__(
                 self.CallName, self.ReprName,
                 self.Pt, self.PtFunc,
                 self.Val, 
                 self.BitLen, self.BitLenFunc,
                 self.Dict, self.DictFunc, self.Repr,
                 self.Trans, self.TransFunc )
        return clone
    
    def map(self, string=''):
        # map each bit of the string from left to right
        # using the shtr() class to shift the string
        # string must be ascii-encoded (see shtr)
        if not self.is_transparent():
            self.map_bit( shtr(string).left_val(self.bit_len()) )
    
    def map_bit(self, value=0):
        # map an int / long value
        if self.safe:
            assert( 0 <= value <= pow(2, self.bit_len()) )
        self.Val = value
    
    def map_ret(self, string=''):
        if self.is_transparent():
            return string
        else:
            shtring = shtr(string)
            bitlen = self.bit_len()
            self.map_bit( shtring.left_val(bitlen) )
            return shtring << bitlen
    
    # shar manipulation interface
    def to_shar(self):
        ret = shar()
        ret.set_uint(self(), self.bit_len())
        return ret
    
    def map_shar(self, sh):
        if not self.is_transparent():
            self.map_bit( sh.get_uint(self.bit_len()) )


Question: from libmich.core.element import Bit, Int, Str, Layer
from libmich.core.IANA_dict import IANA_dict
from .L3Mobile_24007 import Type1_TV, Type2, Type3_V, Type3_TV, Type4_LV, \
     Type4_TLV, PD_dict, Layer3
from .L3Mobile_IE import LAI, ID, MSCm1, MSCm2, PLMNList
from .L3Mobile_IEdict import *
# Message definition
#
# describes mobile L3 signalling messages
# each message composed of Information Element (IE)
# each IE is coded on a TypeX (T, TV, LV, TLV...) from 24.007
# each IE is mandatory, conditional, or optional in the message
#
# Messages are coded as Layer
# containing Bit, Int, Str or Layer for each IE
# ...
# It's going to rock!

# 24008, section 10.4
# CS Mobility Management procedures dict
CS_MM_dict = {
    1:"Registration - IMSI DETACH INDICATION",
    2:"Registration - LOCATION UPDATING ACCEPT",
    4:"Registration - LOCATION UPDATING REJECT",
    8:"Registration - LOCATION UPDATING REQUEST",
    17:"Security - AUTHENTICATION REJECT",
    18:"Security - AUTHENTICATION REQUEST",
    20:"Security - AUTHENTICATION RESPONSE",
    28:"Security - AUTHENTICATION FAILURE",
    24:"Security - IDENTITY REQUEST",
    25:"Security - IDENTITY RESPONSE",
    26:"Security - TMSI REALLOCATION COMMAND",
    27:"Security - TMSI REALLOCATION COMPLETE",
    33:"Connection Mgt - CM SERVICE ACCEPT",
    34:"Connection Mgt - CM SERVICE REJECT",
    35:"Connection Mgt - CM SERVICE ABORT",
    36:"Connection Mgt - CM SERVICE REQUEST",
    37:"Connection Mgt - CM SERVICE PROMPT",
    38:"Connection Mgt - Reserved",
    40:"Connection Mgt - CM RE-ESTABLISHMENT REQUEST",
    41:"Connection Mgt - ABORT",
    48:"Misc - MM NULL",
    49:"Misc - MM STATUS",
    50:"Misc - MM INFORMATION",
    }

# 24008, section 10.5.3.6
# Reject Cause
Reject_dict = IANA_dict({ \
    0:'private',
    2:'IMSI unknown in HLR',
    3:'Illegal MS',
    4:'IMSI unknown in VLR',
    5:'IMEI not accepted',
    6:'Illegal ME',
    7:'private',
    11:'PLMN not allowed',
    12:'Location Area not allowed',
    13:'Roaming not allowed in this location area',
    14:'private',
    15:'No Suitable Cells In Location Area',
    16:'private',
    17:'Network failure',
    18:'private',
    20:'MAC failure',
    21:'Synch failure',
    22:'Congestion',
    23:'GSM authentication unacceptable',
    24:'private',
    25:'Not authorized for this CSG',
    26:'private',
    32:'Service option not supported',
    33:'Requested service option not subscribed',
    34:'Service option temporarily out of order',
    35:'private',
    38:'Call cannot be identified',
    39:'private',
    48:'retry upon entry into a new cell',
    95:'Semantically incorrect message',
    96:'Invalid mandatory information',
    97:'Message type non-existent or not implemented',
    98:'Message type not compatible with the protocol state',
    99:'Information element non-existent or not implemented',
    100:'Conditional IE error',
    101:'Message not compatible with the protocol state',
    102:'private',
    111:'Protocol error, unspecified',
    112:'private'})

###################
# message formats #
###################
# TS 24.008, section 9
class Header(Layer):
    constructorList = [
        Bit('SI', ReprName='Skip Indicator', Pt=0, BitLen=4),
        Bit('PD', ReprName='Protocol Discriminator', \
            BitLen=4, Dict=PD_dict, Repr='hum'),
        Bit('seq', ReprName='Sequence Number', Pt=0, BitLen=2, Repr='hum'),
        Bit('Type', BitLen=6, Dict=CS_MM_dict, Repr='hum'),
        ]
    def __init__(self, prot=5, type=48):
        Layer.__init__(self)
        self.PD.Pt = prot
        self.Type.Pt = type


################
# TS 24.008, section 9.2 #
# Mobility Management    #
################

# section 9.2.12
class IMSI_DETACH_INDICATION(Layer3):
    '''
    ME -> Net
    Dual
    # content #
    MS classmark 1 is 1 byte
    Identity is 1 to 8 bytes
    '''
    constructorList = [ie for ie in Header(5, 1)]
    def __init__(self, with_options=True, **kwargs):
        Layer3.__init__(self)
        self.extend([ \
            Str('MSCm1', Pt=MSCm1(), Len=1),

Answer:             Type4_LV('ID', V=ID())])