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 QoS(Layer):
    constructorList = [
        Bit('spare', Pt=0, BitLen=2, Repr='hex'),
        Bit('DelayClass', Pt=0, BitLen=3, Dict=DelayClass_dict, Repr='hum'),
        Bit('ReliabilityClass', Pt=0, BitLen=3, Dict=ReliabClass_dict, Repr='hum'), # 1
        Bit('PeakThroughput', Pt=0, BitLen=4, Dict=PeakTP_dict, Repr='hum'),
        Bit('spare', Pt=0, BitLen=1, Repr='hex'),
        Bit('PrecedenceClass', Pt=0, BitLen=3, Dict=PrecedClass_dict, Repr='hum'), # 2
        Bit('spare', Pt=0, BitLen=3, Repr='hex'),
        Bit('MeanThroughput', Pt=0, BitLen=5), # 3
        Bit('TrafficClass', Pt=0, BitLen=3),
        Bit('DeliveryOrder', Pt=0, BitLen=2),
        Bit('DeliveryOfErrSDU', Pt=0, BitLen=3), # 4
        Int('MaxSDUSize', Pt=0, Type='uint8'),
        Int('MaxULBitRate', Pt=0, Type='uint8'),
        Int('MaxDLBitRate', Pt=0, Type='uint8'), # 7
        Bit('ResidualBitErrRate', Pt=0, BitLen=4),
        Bit('SDUErrRatio', Pt=0, BitLen=4), # 8
        Bit('TransferDelay', Pt=0, BitLen=6),
        Bit('TrafficHandlingPrio', Pt=0, BitLen=2), # 9
        Int('GuarantULBitRate', Pt=0, Type='uint8'),
        Int('GuarantDLBitRate', Pt=0, Type='uint8'), # 11
        Bit('spare', Pt=0, BitLen=3, Repr='hex'),
        Bit('SignallingInd', Pt=0, BitLen=1),
        Bit('SourceStatDesc', Pt=0, BitLen=4), # 12
        Int('MaxDLBitRateExt', Pt=0, Type='uint8'),
        Int('GuarantDLBitRateExt', Pt=0, Type='uint8'),
        Int('MaxULBitRateExt', Pt=0, Type='uint8'),
        Int('GuarantULBitRateExt', Pt=0, Type='uint8'), # 16
        ]
    
    def __init__(self, **kwargs):
        Layer.__init__(self, **kwargs)
        # for the last 4 integer values: if not explicitly provided,
        # make them transparent
        if 'MaxDLBitRateExt' not in kwargs \
        and 'GuarantDLBitRateExt' not in kwargs:
            for i in range(-4, 0):
                self[i].Trans = True
        if 'MaxULBitRateExt' not in kwargs \
        and 'GuarantULBitRateExt' not in kwargs:
            for i in range(-2, 0):
                self[i].Trans = True
    
    # rewrite map() in order to remove up to the 4 last Int() fields,
    # which are sometimes not provided... sometimes...
    def map(self, s=''):
        s_len = len(s)
        if s_len < 16:
            self[-1].Trans = True
        if s_len < 15:
            self[-2].Trans = True
        if s_len < 14:
            self[-3].Trans = True
        if s_len < 13:
            self[-4].Trans = True
        if s_len < 12:
            self[-5].Trans = True
            self[-6].Trans = True
            self[-7].Trans = True
        Layer.map(self, s)
libmich/core/element.py
class RawLayer(Layer):
    constructorList = [
        Str(CallName='s', Pt='', Len=None),
        ]
    
    def __init__(self, s=''):
        Layer.__init__(self, CallName='raw')
        self.s.Pt = s
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_IEdict.py

libmich/core/element.py
class Element(object):
    '''
    encapsulating class for: Str, Bit, Int
    '''
    # checking Element boundaries extensively
    #safe = True
    safe = False
    
    # Element debugging threshold: 0, ERR, WNG, DBG
    dbg = ERR
    
    # value assignment facilities
    def __lt__(self, Val):
        self.Val = Val
    
    def __gt__(self, Pt):
        self.Pt = Pt
    
    # transparency handling, common to all Element:
    def is_transparent(self):
        if self.TransFunc is not None:
            if self.safe: 
                assert( type(self.TransFunc(self.Trans)) is bool )
            if self.TransFunc(self.Trans): 
                return True
            else:
                return False
        elif self.Trans:
            if self.safe:
                assert(type(self.Trans) is bool)
            return True
        else:
            return False
    
    # an Element can point to another Element or Layer
    def getobj(self):
        if isinstance(self.Pt, (Element, Layer, tuple, list)) and not self.Val:
            return self.Pt
        else:
            return self()
    
    # this is to get a nice object representation:
    # can possibly be called with `show(element)`
    def show(self, with_trans=False):
        tr, re = '', ''
        if self.is_transparent():
            # TODO: eval the best convinience here
            if not with_trans:
                return ''
            tr = ' - transparent'
        else:
            tr = ''
        if self.ReprName != '':
            re = ''.join((self.ReprName, ' '))
        return '<%s[%s%s] : %s>' % ( re, self.CallName, tr, repr(self) )
    
    # when willing to bit shift str, call this instead of standard __str__()
    def shtr(self):
        return shtr(self.__str__())
    
    # this is to retrieve element's dynamicity from a mapped buffer
    def reautomatize(self):
        if self.Val is not None:
            if not self.PtFunc:
                self.Pt = self.Val
            self.Val = None
    
	# this is for uniformity with Block()
    def parse(self, s=''):
        self.map(s)
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 ProtConfig(Layer):
    constructorList = [
        Bit('ext', Pt=1, BitLen=1),
        Bit('spare', Pt=0, BitLen=4, Repr='hex'),
        Bit('ConfigProt', Pt=0, BitLen=3, Dict={0:'PPP with IP PDP'}, Repr='hum'),
        ]
    # when mapping a buffer, append much ProtID() as needed 
    def map(self, s=''):
        if s:
            Layer.map(self, s)
            s = s[1:]
            while len(s) >= 3:
                length = ord(s[2:3])
                if len(s) >= 3+length:
                    self.append(ProtID())
                    self[-1].map(s)
                    s = s[len(self[-1]):]
                else:
                    break
libmich/formats/L3Mobile_NAS.py
class Layer3NAS(Layer3):
    
    constructorList = [ ]
    
    # NAS security algorithm
    EIA = None
    #EIA = EIA1
    EEA = None
    #EEA = EEA1
    
    def __init__(self, with_security=False, **kwargs):
        Layer3.__init__(self, **kwargs)
        if with_security:
            self.ins_sec_hdr()
    
    def ins_sec_hdr(self):
        if not hasattr(self, 'MAC'):
            index = 0
            for ie in NASSecHeader():
                self.insert(index, ie)
                index += 1
    
    def map(self, s=''):
        if not s:
            return
        # check the security header
        s0 = ord(s[0])
        sh, pd = s0>>4, s0&0xF
        # ESM, or EMM with no security header
        if pd == 2 or sh in (0, 12):
            self.__init__(with_security=False)
            Layer3.map(self, s)
            self._map_eps_id()
        # EMM with security header
        elif pd == 7 and sh in (1, 2, 3, 4):
            self.ins_sec_hdr()
            # if no ciphering applied
            if sh in (1, 3): 
            #if sh in (1, 3) or self.EEA not in (EEA1, EEA2, EEA3):
                # if no payload is already there, just add a ciphered-like one
                if len(self.elementList) == 4:
                    self.append( Str('_enc') )
                # map directly the buffer onto the NAS payload
                Layer3.map(self, s)
                self._map_eps_id()
            else:
                if len(self.elementList) > 4:
                    # keep track of all IE of the original packet
                    self._pay = self[4:]
                    # replace them with a dummy string
                    for ie in self._pay:
                        self.remove(ie)
                self.append( Str('_enc') )
                Layer3.map(self, s)
        else:
            log(ERR, '[ERR] invalid Security Header value %i' % sh)
        #
    
    def _map_eps_id(self):
        # some additional processing for interpreting EPS_ID as ID or GUTI
        for ie in self[3:]:
            if ie.CallName == 'EPS_ID' and not ie.Trans:
                s = str(ie.V)
                if not s:
                    return
                typ = ord(s[0]) & 0b111
                ident = None
                if typ in (1, 3):
                    ident = ID()
                elif typ == 6:
                    ident = GUTI()
                if ident:
                    ident.map(s)
                    if str(ident) == s:
                        ie.V < None
                        ie.V > ident
    
    ###
    # security procedures
    ###
    
    def verify_mac(self, key=16*'\0', dir=0):
        # key: K_NAS_int
        # cnt: NAS uplink or downlink counter
        # dir: direction (uplink / downlink)
        # using self.EIA
        #
        # if no MAC to check
        sh = self.SH()
        if sh not in (1, 2, 3, 4, 12) \
        or self.EIA not in (EIA1, EIA2, EIA3):
            return True
        #
        if sh in (1, 2, 3, 4):
            # get NAS payload buffer (including NAS count)
            pay = str(self[3:])
            # compute MAC
            mac = self.EIA(key, self.SN(), 0, dir, pay)
            # compare MAC
            if mac != str(self.MAC):
                return False
            else:
                return True
        elif sh == 12:
            pay = str(self[0:4])
            mac = self.EIA(key, self.SN(), 0, dir, pay)
            if mac[2:4] != str(self.MAC):
                return False
            else:
                return True
    
    def compute_mac(self, key=16*'\0', dir=0):
        # if no MAC to apply
        sh = self.SH()
        if sh not in (1, 2, 3, 4, 12) \
        or self.EIA not in (EIA1, EIA2, EIA3):
            # give it null value
            if hasattr(self, 'MAC'):
                self.MAC < None
                self.MAC > '\0\0\0\0'
            return
        #
        if sh in (1, 2, 3, 4):
            # get NAS payload buffer (including NAS count)
            pay = str(self[3:])
            # compute MAC
            mac = self.EIA(key, self.SN(), 0, dir, pay)
            #print('MAC: %s' % repr(mac))
            self.MAC < None
            self.MAC > mac
        elif sh == 12:
            pay = str(self[0:4])
            mac = self.EIA(key, self.SN(), 0, dir, pay)
            self.MAC < None
            self.MAC > mac[2:4]
    
    def decipher(self, key=16*'\0', dir=0):
        # key: K_NAS_enc
        # cnt: NAS uplink or downlink counter
        # dir: direction (uplink / downlink)
        # using self.EEA
        #
        # if no deciphering to apply
        if self.SH() not in (2, 3) \
        or self.EEA not in (None, EEA1, EEA2, EEA3):
            return
        if not hasattr(self, '_pay') \
        or (not isinstance(self[-1], Str) and self[-1].CallName != '_enc'):
            log(ERR, 'Layer3NAS - decipher: not ready for deciphering')
            return
        #
        if self.EEA is None:
            # null ciphering EEA0
            dec = str(self[-1])
        else:
            dec = self.EEA(key, self.SN(), 0, dir, str(self[-1]))
        #
        # get the complete packet buffer
        buf = str(self[:4]) + dec
        # reinsert NAS payload IEs
        self.remove(self[-1])
        self.extend(self._pay)
        # remap the complete deciphered buffer to the NAS layer
        Layer3.map(self, buf)
        self._map_eps_id()
      
    def cipher(self, key=16*'\0', dir=0):
        # if no ciphering to apply
        if self.SH() not in (2, 3) \
        or self.EEA not in (None, EEA1, EEA2, EEA3):
            return
        #
        # keep track of all IE of the original packet
        self._pay = self[4:]
        if self.EEA is None:
            enc = str(self._pay)
        else:
            enc = self.EEA(key, self.SN(), 0, dir, str(self._pay))
        #
        # replace them with a dummy string
        for ie in self._pay:
            self.remove(ie)
        self << Str('_enc')
        self[-1].map(enc)
    
    def protect(self, key_int=16*'\0', key_enc=16*'\0', dir=0):
        self.cipher(key_enc, dir)
        self.compute_mac(key_int, dir)
    
    def unprotect(self, key_int=16*'\0', key_enc=16*'\0', dir=0):
        ret = self.verify_mac(key_int, dir)
        if not ret:
            log(WNG, 'Layer3NAS - unprotect: MAC verificaion failed')
        self.decipher(key_enc, dir)
    
    ###
    # When receiving ciphered NAS messages,
    # there is no way to know which type of Layer3NAS message it is.
    # We need a function that returns a buffer that will be parsed again
    ###
    
    def get_deciphered(self, key=16*'\0', dir=0):
        # this is to get the deciphered stream of a NAS EMM message
        # 
        # no NAS security
        if self.SH() == 0 and not hasattr(self, 'MAC'):
            return str(self)
        #
        # NAS security, only integrity protection
        elif self.SH() in (1, 3) and hasattr(self, 'MAC'):
            return str(self[4:])
        #
        # NAS security, ciphered
        elif self.SH() in (2, 4) and hasattr(self, 'MAC'):
            if self.EEA is None:
                # null ciphering EEA0
                return str(self[4:])
            elif self[-1].CallName == '_enc':
                return self.EEA(key, self.SN(), 0, dir, str(self[-1]))
        #
        log(ERR, 'Layer3NAS - get_deciphered: could not retrieve NAS payload')
        return ''
    
    def get_ciphered(self, key=16*'\0', dir=0):
        # this is to get the ciphered stream of a clear text NAS EMM message
        # 
        # if no correct ciphering algo to apply
        if self.EEA not in (None, EEA1, EEA2, EEA3):
            return ''
        #
        if self.EEA is None:
            return str(self)
        else:
            return self.EEA(key, self.SN(), 0, dir, str(self))
libmich/formats/L3Mobile_IE.py
class PDPAddr(Layer):
    constructorList = [
        Bit('spare', Pt=0, BitLen=4, Repr='hex'),
        Bit('PDPTypeOrga', Pt=0, BitLen=4, Dict=PDPTypeOrga_dict, Repr='hum'),
        Int('PDPType', Pt=33, Type='uint8', Dict=PDPTypeNum1_dict),
        Str('Addr', Repr='ipv4'),
        ]
    def __init__(self, **kwargs):
        Layer.__init__(self, **kwargs)
        self.Addr.Len = self.PDPType
        self.Addr.LenFunc = lambda t: {33:4, 87:16, 141:20}[t()] \
                                      if t() in (33, 87, 141) else 0
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()) )
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)))


Question: from libmich.core.element import Element, Str, Int, Bit, Layer, RawLayer
from .L3Mobile_24007 import Type1_TV, Type2, Type3_V, Type3_TV, Type4_LV, \
     Type4_TLV, Type6_LVE, Type6_TLVE, PD_dict, Layer3
from .L3Mobile_NAS import Layer3NAS
from .L3Mobile_IE import QoS, PDPAddr, ProtConfig
from .L3Mobile_IEdict import RequestType_dict, PDNType_dict, ESMTransFlag_dict
           'DEACTIVATE_EPS_BEARER_CTX_ACCEPT',
           'PDN_CONNECTIVITY_REQUEST', 'PDN_CONNECTIVITY_REJECT',
           'PDN_DISCONNECT_REQUEST', 'PDN_DISCONNECT_REJECT',
           'BEARER_RESOURCE_ALLOC_REQUEST', 'BEARER_RESOURCE_ALLOC_REJECT',
           'BEARER_RESOURCE_MODIF_REQUEST', 'BEARER_RESOURCE_MODIF_REJECT',
           'ESM_INFORMATION_REQUEST', 'ESM_INFORMATION_RESPONSE',
           'ESM_NOTIFICATION', 'ESM_STATUS',
           'ESM_dict', 'ESMCause_dict',
            ]


# these are the libraries for the handling of L3 NAS msg
#
# these are the libraries for IE interpretation

###
# TS 24.301, 11.5.0 specification
# NAS protocol for Evolved Packet System
# EMM procedures in section 5
# ESM procedures in section 6
# message function in section 8
# message format in section 9
###

ESM_dict = {
    #
    193 : "Activate default EPS bearer context request",
    194 : "Activate default EPS bearer context accept",
    195 : "Activate default EPS bearer context reject",
    #
    197 : "Activate dedicated EPS bearer context request",
    198 : "Activate dedicated EPS bearer context accept",
    199 : "Activate dedicated EPS bearer context reject",
    #
    201 : "Modify EPS bearer context request",
    202 : "Modify EPS bearer context accept",
    203 : "Modify EPS bearer context reject",
    #
    205 : "Deactivate EPS bearer context request",
    206 : "Deactivate EPS bearer context accept",
    #
    208 : "PDN connectivity request",
    209 : "PDN connectivity reject",
    #
    210 : "PDN disconnect request",
    211 : "PDN disconnect reject",
    #
    212 : "Bearer resource allocation request",
    213 : "Bearer resource allocation reject",
    #
    214 : "Bearer resource modification request",
    215 : "Bearer resource modification reject",
    #
    217 : "ESM information request",
    218 : "ESM information response",
    #
    219 : "Notification",
    #
    232 : "ESM status",
    }

# section 9.9.4.4
ESMCause_dict = {
    8 : "Operator Determined Barring",
    26 : "Insufficient resources",
    27 : "Missing or unknown APN",
    28 : "Unknown PDN type",
    29 : "User authentication failed",
    30 : "Request rejected by Serving GW or PDN GW",
    31 : "Request rejected, unspecified",
    32 : "Service option not supported",
    33 : "Requested service option not subscribed",
    34 : "Service option temporarily out of order",
    35 : "PTI already in use",
    36 : "Regular deactivation",
    37 : "EPS QoS not accepted",
    38 : "Network failure",
    39 : "Reactivation requested",
    41 : "Semantic error in the TFT operation",
    42 : "Syntactical error in the TFT operation",
    43 : "Invalid EPS bearer identity",
    44 : "Semantic errors in packet filter(s)",
    45 : "Syntactical errors in packet filter(s)",
    46 : "Unused (see NOTE 2)",
    47 : "PTI mismatch",
    49 : "Last PDN disconnection not allowed",
    50 : "PDN type IPv4 only allowed",
    51 : "PDN type IPv6 only allowed",
    52 : "Single address bearers only allowed",
    53 : "ESM information not received",
    54 : "PDN connection does not exist",
    55 : "Multiple PDN connections for a given APN not allowed",
    56 : "Collision with network initiated request",
    59 : "Unsupported QCI value",
    60 : "Bearer handling not supported",
    65 : "Maximum number of EPS bearers reached",
    66 : "Requested APN not supported in current RAT and PLMN combination",
    81 : "Invalid PTI value",
    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",
    111 : "Protocol error, unspecified",
    112 : "APN restriction value incompatible with active EPS bearer context",
    }


###
# NAS protocol headers
# section 9.2
###

class ESMHeader(Layer):
    constructorList = [
        Bit('EBT', ReprName='EPS Bearer Type', Pt=0, BitLen=4, Repr='hum'),
        Bit('PD', ReprName='Protocol Discriminator', Pt=2, BitLen=4,
            Dict=PD_dict, Repr='hum'),

Answer:         Int('TI', ReprName='Procedure Transaction ID', Pt=0, Type='uint8'),