libmich/core/shtr.py
class decomposer(object):
    '''
    little utility to decompose an integer into factors from an 
    exponential (self.MUL), and returns the list of remainders:
    
    example:
    decomposer(0x10).decompose(53) -> [5, 3]
    this means:
    53 = 5 + (3 << 4), with 0x10 = 1 << 4
    
    more generally:
    decomposer(1<<X).decompose(Y) -> [a, b, c, d ...]
    means:
    Y = a + (b << (1<<X)) + (c << (2<<X)) + (d << (3<<X)) ...
    '''
    def __init__(self, MUL=0x100):
        self.MUL = MUL
        self._val_dec = []
    
    def decompose(self, val):
        self._val_dec.append( int(val % self.MUL) )
        if val >= self.MUL:
            val = val // self.MUL
            void = self.decompose( val )
        return self._val_dec
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 StrBCD(Str):
class BCDNumber(Layer):
class PLMN(Layer):
class PLMNList(Layer):
class LAI(Layer):
class RAI(Layer):
class ID(Layer):
class MSCm1(Layer):
class MSCm2(Layer):
class A5bits(CSN1):
class RSupport(CSN1):
class HSCSDMultiSlotCapability(CSN1):
class MSMeasurementCapability(CSN1):
class MSPositioningMethodCapability(CSN1):
class ECSDMultiSlotCapability(CSN1):
class PSK8(CSN1):
class SingleBandSupport(CSN1):
class GERANIuModeCapabilities(CSN1):
class MSCm3(CSN1):
class NetName(Layer):
class AuxState(Layer):
class BearerCap(Layer):
class CCCap(Layer):
class QoS(Layer):
class PDPAddr(Layer):
class ProtID(Layer):
class ProtConfig(Layer):
class PacketFlowID(Layer):
class DRX(Layer):
class VoicePref(Layer):
class CodecBitmap(Layer):
class CodecSysID(Layer):
class SuppCodecs(Layer):
class ExtGEABits(CSN1):
class MSNetCap(CSN1):
class MSRAA5bits(CSN1):
class MultislotCap(CSN1):
class GERANIuModeCap(CSN1):
class EnhancedFlexibleTimeslotAssign(CSN1):
class MSRAContent(CSN1):
class MSRAAccessCap(CSN1):
class MSRAAddTech(CSN1):
class MSRAAdd(CSN1):
class MSRACap(CSN1):
class NASSecToEUTRA(Layer):
class GUTI(Layer):
class EPSFeatSup(Layer):
class TAI(Layer):
class PartialTAIList(Layer):
class PartialTAIList0(PartialTAIList):
class PartialTAIList1(PartialTAIList):
class PartialTAIList2(PartialTAIList):
class TAIList(Layer):
class UENetCap(Layer):
class UESecCap(Layer):
class APN_AMBR(Layer):
class SSversion(Layer):
class AccessTechnology(Layer):
    def decode(self):
    def encode(self, num='12345'):
    def __repr__(self):
    def __init__(self, **kwargs):
    def __init__(self, MCCMNC='00101'):
    def get_mcc(self):
    def get_mnc(self):
    def get_mccmnc(self):
    def set_mcc(self, MCC='001'):
    def set_mnc(self, MNC='01'):
    def set_mccmnc(self, MCCMNC='000101'):
    def __repr__(self):
    def interpret(self):
    def __init__(self, *args, **kwargs):
    def add_plmn(self, *args):
    def add_PLMN(self, plmn=PLMN()):
    def map(self, s=''):
    def interpret(self):
    def __init__(self, MCCMNC='00101', LAC=0x0000):
    def __repr__(self):
    def __init__(self, MCCMNC='00101', LAC=0x0000, RAC=0x00):
    def __repr__(self):
    def __init__(self, val='0', type='No Identity'):
    def map(self, s=''):
    def __handle_digits(self, digits=''):
    def __repr__(self):
    def get_imsi(self):
    def get_imei(self):
    def get_bcd(self):
    def anon(self):
    def __init__(self, **kwargs):
    def __init__(self, **kwargs):
    def map(self, s=''):
    def __init__(self, **kwargs):
    def __init__(self, **kwargs):
    def map(self, s=''):
    def map(self, s=''):
    def map(self, buf=''):
    def __init__(self, **kwargs):
    def map(self, buf=''):
    def map(self, buf=''):
    def map(self, s=''):
    def __init__(self, *args, **kwargs):
    def __init__(self, *args, **kwargs):
    def map(self, s='', byte_offset=0):
    def map(self, s='', byte_offset=0):
    def map(self, s=''):
    def __init__(self, MCCMNC='00101', **kwargs):
    def __init__(self, MCCMNC='00101', TAC=0x0000):
    def __init__(self, *args, **kwargs):
    def _set_plmn(self, plmn):
    def add_tac(self, *args):
    def add_tai(self, *args):
    def map(self, s):
    def __init__(self, *args, **kwargs):
    def __init__(self, *args, **kwargs):
    def __init__(self, *args, **kwargs):
    def __init__(self, *args):
    def map(self, s=''):
    def map(self, s='\0\0'):
    def map(self, s=2*'\0'):
    def __init__(self, **kwargs):
    def _gen_dict(self, d={}):
    def _gen_ext_dict(self, d={}):
    def _gen_ext2_dict(self, d={}):
    def get_AT(self):
    def __repr__(self):
        MCC, MNC = int(self.get_mcc()), int(self.get_mnc())
        AT = []
libmich/core/CSN1.py
BREAK_LOOP = 0xdead1004
libmich/core/shtr.py
class shtr(str):
    '''
    shtr are strings that can be shifted !
    
    When a shtr is shifted (<< X or >> X), it returns the resulting string.
    When right shifted (>> X), null bytes are appended left-side.
    
    Methods .left_val(X) and .right_val(X) returns the integer value corresponding to 
    the X bits left or right side of the shtr.
    '''
    
    def __init__(self, s):
        self._bitlen = len(s)*8
    
    def left_val(self, bitlen):
        '''
        returns big endian integer value from the `bitlen' left bits of the shtr
        '''
        if bitlen > self._bitlen:
            bitlen = self._bitlen
        # 0)
        reg64 =  bitlen//64
        reg8  = (bitlen%64)//8
        reg1  =  bitlen%8
        acc, start, stop = 0, 0, 0
        # 1)
        if reg64:
            stop = 8*reg64
            acc += reduce(lambda x,y: (x<<64)+y,
                          unpack('>'+reg64*'Q', self[:stop]))
        # 2)
        if reg8:
            acc <<= 8*reg8
            start = stop
            stop = start + reg8
            acc += reduce(lambda x,y: (x<<8)+y,
                          unpack('>'+reg8*'B', self[start:stop]))
        # 3)
        if reg1:
            acc <<= reg1
            acc += (ord(self[stop:stop+1]) >> (8-reg1))
        #
        return acc
    
    def right_val(self, val):
        '''
        returns big endian integer value from the `val' right bits of the shtr
        '''
        if val > self._bitlen: val = self._bitlen
        acc = 0
        # 1) get value of full bytes
        for i in xrange(val/8):
            acc += ord(self[-1-i]) << (i*8)
        # 2) get value of last bits
        if val%8 and val/8 < len(self):
            acc += (ord(self[-1-(val/8)]) & ((1<<(val%8))-1)) << (8*(val/8))
        return acc
    
    def __lshift__(self, bitlen):
        '''
        returns resulting shtr after shifting left of `bitlen' bits
        '''
        if bitlen > self._bitlen:
            bitlen = self._bitlen
        # 0) full byte shifting
        buf = self[bitlen//8:]
        # 1) bit shifting
        reg1  = bitlen%8
        if reg1:
            buflen = len(buf)
            reg64 = buflen//8
            reg8  = buflen%8
            reg1inv   = 8-reg1
            reg1inv64 = 64-reg1
            fmt = '>'+reg64*'Q'+reg8*'B'
            values = unpack(fmt, buf)
            #print values
            if reg64:
                #print('reg64')
                chars = map(lambda X: ((X[0]<<reg1)%0x10000000000000000)\
                                      +(X[1]>>reg1inv64),
                            zip(values[:reg64], values[1:reg64]))
                if reg8:
                    #print('reg8')
                    chars.append( ((values[reg64-1]<<reg1)%0x10000000000000000)\
                                  +(values[reg64]>>reg1inv) )
                    chars.extend( map(lambda X: ((X[0]<<reg1)%0x100)+(X[1]>>reg1inv),
                                      zip(values[reg64:], values[reg64+1:])) )
                    chars.append( (values[-1]<<reg1)%0x100 )
                else:
                    chars.append( (values[reg64-1]<<reg1)%0x10000000000000000 )
            elif reg8:
                #print('reg8')
                chars = map(lambda X: ((X[0]<<reg1)%0x100)+(X[1]>>reg1inv),
                            zip(values, values[1:]))
                chars.append( (values[-1]<<reg1)%0x100 )
            #
            #print chars
            ret = shtr(pack(fmt, *chars))
        else:
            ret = shtr(buf)
        ret._bitlen = self._bitlen-bitlen
        return ret
    
    def __rshift__(self, val):
        '''
        returns resulting shtr after shifting right of `val' bits
        '''
        # 1) handle full byte shifting
        Bsh = val / 8
        buf = ''.join((Bsh*'\0', self[:len(self)-Bsh]))
        # 2) then bit shifting
        bsh = val % 8
        pbsh, invbsh = pow(2,bsh)-1, 8-bsh
        # go over the string, from last byte to the 1st one
        strlist = [ (ord(buf[i])>>bsh) + ((ord(buf[i-1])&pbsh)<<invbsh) \
                    for i in reversed(xrange(1, len(buf))) ]
        # add 1st bits of the string and reverse the list
        strlist.append( ord(buf[0])>>bsh )
        strlist.reverse()
        #
        #return shtr(''.join(map(chr, strlist)))
        ret = shtr(''.join(map(chr, strlist)))
        ret._bitlen = self._bitlen + val
        return ret
libmich/core/CSN1.py
class LHFlag(Bit):
    # LHFlag has new attributes: .LHdict = {0:'L',1:'H'}, .LH = 'L' | 'H'
    # 
    # when mapping a buffer string to the LHFlag, the CSN1 Layer must take
    # care that this dict is correctly set depending of the byte offset and
    # padding pattern
    def __init__(self, CallName='', LH='L', Dict=None):
        self.LHdict = iad({0:'L', 1:'H'})
        Bit.__init__(self, CallName=CallName, BitLen=1, Dict=Dict, Repr='hum')
        if LH not in ('L', 'H'): LH = 'L'
        self.LH = LH
        self.Pt = 0
        self.PtFunc = self.__lh_val
        
    def __lh_val(self, _unused):
        return self.LHdict[self.LH]
        
    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":
            if self.Dict:
                try: val = self.Dict[self.LHdict[self()]]
                except KeyError: val = self.LHdict[self()]
            else: 
                val = self.LHdict[self()]
            return repr(val)
    
    def clone(self):
        clone = self.__class__(self.CallName, self.LH, self.Dict)
        return clone
libmich/core/element.py
ERR = 1
libmich/core/element.py
DBG = 3
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
def log(level=DBG, string=''):
    # if needed, can be changed to write somewhere else
    # will redirect all logs from the library
    print('[libmich %s] %s' % (debug_level[level], string))
libmich/core/element.py
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) )
libmich/core/element.py
WNG = 2
libmich/core/CSN1.py
class CSN1(Layer):
    '''
    CSN1 Layer provides an extended notation compared to traditional Layer.
    This allows to handle condition flags for selecting the correct field(s)
    to be inserted in the Layer.
    It is based on the class attribute `csn1List`, 
    which lists possible bit-fields from a csn.1 structure:
    - mandatory fields: Bit(), Str(), Layer() including CSN1(), 
    - tuple of mandatory fields
    - dictionnary where keys are flags for selecting the correct bit-fields,
      or tuple or sub-dictionnary (this is done recursively)
    -> condition (dictionnary keys) can be:
    'L', 'H' or '0', '1', '00', '01', 'LH' ...
    -> 'L' means padding bit (e.g. '2b' for GSM), 
    -> 'H' means opposite bit from padding
    Do not try to mix LH and 01 into single condition
    '''
    # Layer byte alignment, should never be True for CSN1
    _byte_aligned = False
    #
    # those are used when decoding CSN1 fields
    pad_name = 'CSN1_padding'
    cond_name = 'CSN1_condition'
    #
    # L is padding, H is non-padding: 
    # eg in GSM, padding is 0x2b in binary [MSB, ..., LSB]
    L = [0,0,0,0,0,0,0,0]
    #
    # this is the list to fulfill from the CSN1 syntax
    # with Bit() fields, CSN1() structures, dict() and tuple()
    csn1List = []
    # during decoding, those fields will be pushed to the Layer.elementList
    # depending of the conditions evaluated
    #
    # to enforce a maximum bit length, especially during map()
    max_bitlen = None
    # IE not to appear when using .show()
    #_show_all = True
    _show_all = False
    _IE_no_show = ['CSN1_padding', 'CSN1_condition']
    #
    #dbg = DBG
    dbg = 0
    
    def __init__(self, build_auto=False, build_path=None, name=''):
        # ensure L is binary only
        if self.safe:
            assert(all([l in (0, 1) for l in self.L]))
        Layer.__init__(self, CallName=name)
        self.max_bitlen = CSN1.max_bitlen
        # decorrelate the csn1List of the instance from the class one
        self.csn1List = deepcopy(self.csn1List)
        # elementList is still empty as it might not be needed
        # eg when using only .map()
        self.elementList = []
        #
        if build_auto or build_path:
            self.build(self.csn1List, 0, build_path)
        #self.build(self.csn1List, 0, build_path)
    
    def build(self, csn1iter, offset=0, build_path=None):
        self.elementList = []
        # create ._offset to handle LH values
        self._offset = offset
        #
        if build_path is None:
            self._build_auto(csn1iter)
        else:
            # we need a temporary "current path" variable 
            # to keep track of path consumption accross recursion
            self._cur_path = build_path
            self._build_path(csn1iter)
        # clean the temporary ._offset and ._build_path attributes
        del self._offset
        if hasattr(self, '_cur_path'):
            del self._cur_path
    
    def _build_auto(self, csn1iter):
        #print self.CallName
        # recursive automatic loading of CSN.1 fields
        for f in csn1iter:
            if self.dbg >= DBG:
                log(DBG, '(_build_auto - %s) iterating over: %s' \
                    % (self.__class__, repr(f)))
            if isinstance(f, CSN1FIELDS):
                self._append_csn1_field(f)
            elif isinstance(f, tuple):
                self._build_auto(f)
            elif isinstance(f, dict):
                # select the shortest path: BREAK, BREAK_LOOP,
                # or the shortest value
                values = f.values()
                # easiest path is to get straight out of the dict
                if BREAK in values or BREAK_LOOP in values:
                    for i in f.items():
                        if i[1] in (BREAK, BREAK_LOOP):
                            self._append_path(i[0])
                            break
                # 2nd easiest path is to take a single CSN1FIELDS if exist
                elif True in [isinstance(i, CSN1FIELDS) for i in values]:
                    for i in f.items():
                        # "au petit bonheur la chance"
                        if isinstance(i[1], CSN1FIELDS):
                            self._append_path(i[0])
                            self._append_csn1_field(i[1])
                            break
                # 3rd easiest path is to take the shortest tuple available
                elif True in [isinstance(i, tuple) for i in values]:
                    min_tuple = min(map(len, values))
                    for i in f.items():
                        if len(i[1]) == min_tuple:
                            # "au petit bonheur la chance" again
                            self._append_path(i[0])
                            self._build_auto(i[1])
                            break
                # 4th easiest path is to enter the shortest dict available
                elif True in [isinstance(i, dict) for i in values]:
                    min_dict = min(map(len, values))
                    for i in f.items():
                        if len(i[1]) == min_dict:
                            # "au petit bonheur la chance" once again
                            self._append_path(i[0])
                            self._build_auto(i[1])
                            break
    
    def _build_path(self, csn1iter):
        # WNG : there is probably an issue as the ._offset attribute
        # is not changed during the several recursive calls...
        #
        cnt=0
        # recursive loading of CSN.1 fields, following the path of conditions
        for f in csn1iter:
            if self.dbg >= DBG:
                log(DBG, '(_build_path - %s) iterating over: %s' \
                    % (self.__class__, repr(f)))
            if isinstance(f, CSN1FIELDS):
                #print('<<<<<<<<<<<<<<<<<<<<<< %s' % f.CallName)
                self._append_csn1_field(f)
            elif isinstance(f, tuple):
                self._build_path(f)
            #
            elif isinstance(f, dict):
                # in case there is no more conditions privided
                if not self._cur_path:
                    self._build_auto(csn1iter[cnt:])
                    return
                # this is the case were we need to consume the _cur_path
                # and append the values as of the provided conditions
                #
                self._build_from_dict(f)
            #
            cnt += 1
    
    def _build_from_dict(self, d):
        # TODO: if BREAK_LOOP in the dict, 
        # we should iterate as much time as provided in the path list
        #
        conds = d.keys()
        #vals = d.values()
        #
        # select the value corresponding to the condition in _cur_path
        if self.dbg >= DBG:
            log(DBG, '(_build_path) _cur_path to process: %s' % self._cur_path)
        cond_from_path = self._cur_path.pop(0)
        if cond_from_path not in conds:
            raise(Exception('path %s does not correspond to any condition'\
                  ' for building %s' % (cond_from_path, self.__class__)))
        #
        self._append_path(cond_from_path)
        field = d[cond_from_path]
        if field not in (BREAK, BREAK_LOOP):
            if isinstance(field, CSN1FIELDS):
                #print('<<<<<<<<<<<<<<<<<<<<<< %s' % field.CallName)
                self._append_csn1_field(field)
            else:
                # for tuple or dict, go the recursive way
                self._build_path(field)
    
    def _append_path(self, path):
        if self.dbg >= DBG:
            log(DBG, '(build - %s) going path: %s' \
                % (self.__class__, repr(path)))
        cond = ''
        for c in path:
            # if LH, get the correct value with self._offset
            if c in 'LH':
                L = self.L[self._offset%8]
                self.append(Bit('%s' % self.cond_name, \
                    Pt={'L':L, 'H':(1, 0)[L]}[c] , BitLen=1))
            elif c in '01':
                self.append(Bit('%s' % self.pad_name, Pt=int(c, 2), BitLen=1))
            else:
                self._cond_error(path)
            self._offset += 1
    
    def _append_csn1_field(self, field):
        if isinstance(field, LHFlag):
            #print self._offset
            if self.L[self._offset%8] == 1:
                field.LHdict = iad({1:'L', 0:'H'})
        if isinstance(field, CSN1):
            # initialize the field without building it any specific way
            f = field.__class__(False, None)
            # and then call build() in order to pass the _offset attribute
            path = self._cur_path if hasattr(self, '_cur_path') else None
            f.build(f.csn1List, self._offset, path)
            #
            if self.dbg >= DBG:
                log(DBG, '(_append_csn1_field) field %s with path %s'\
                    % (f.CallName, path))
            self.append(f)
            self._offset += f.bit_len()
            #
        else:
            self.append(field)
            self._offset += field.bit_len()
    
    def map(self, string='', byte_offset=0):
        # pop each member of the initial csn1List
        # and see what we have
        # 1) CSN1FIELD -> map it directly
        # 2) dict -> conditions -> CSN1FIELD | dict | tuple
        # 3) tuple -> CSN1FIELD | dict
        # consume the string buffer bit per bit depending of the 
        # csn1List member poped
        #
        # as a CSN1 field does not always start on a byte limit,
        # we need to keep track of the byte offset, 
        # for solving padding (L | H) adequately
        #
        # in case CSN1 list is empty
        if len(self.csn1List) == 0:
            return
        # initialize the string buffer to be mapped
        self.BUF, self._buflen = shtr(string), len(string)*8
        self._consumed, self._offset, self._map_exit = 0, byte_offset, False
        self.elementList = []
        #
        # MAIN loop
        # go over the string, bit per bit
        index = 0
        while (self._consumed + self._offset) < self._buflen:
            if self._map_exit:
                break
            # get csn1 fields 1 by 1
            self._eval_csn1(self.csn1List[index])
            index += 1
            # when csn1List is empty, finish with padding
            if index >= len(self.csn1List):
                break
        #
        # TODO: the padding should only be done when instructed so in CSN1
        remaining = self._buflen - (self._consumed + self._offset)
        if remaining:
            if self.dbg >= DBG:
                log(DBG, '(CSN1._eval_csn1_dict) bits are remaining unmapped')
        # clean temporary data
        del self.BUF, self._buflen, self._consumed, \
            self._offset, self._map_exit
        # BUF is mapped on each CSN1 element each time one is appended
        # see ._append_map_csn1_field() method
    
    def _eval_csn1(self, csn1f):
        #
        # if we have directly a CSN.1 field
        if isinstance(csn1f, CSN1FIELDS):
            if isinstance(csn1f, CSN1):
                # decorrelate class attribute .csn1List from the instance' one
                csn1f = csn1f.__class__()
            elif isinstance(csn1f, Layer):
                csn1f._byte_aligned = False
            self._append_map_csn1_field(csn1f)
        #
        # if we have a dict with conditions
        elif isinstance(csn1f, dict):
            # special processing for recursive struct (syntax like {1 expr}**0)
            if BREAK_LOOP in csn1f.values():
                # then, loop on evaluating the expression until the 
                # key corresponding to the BREAK_LOOP item is found
                self._loop = True
                while self._loop:
                    self._eval_csn1_dict(csn1f)
            # classic dict & conditions processing
            else:
                self._eval_csn1_dict(csn1f)
        #
        # if we have a tuple: recursive call on each fields within the tuple
        elif isinstance(csn1f, tuple):
            for f in csn1f:
                self._eval_csn1(f)
    
    def _eval_csn1_dict(self, csn1f):
        # get the list of possible conditions
        # get it twice, 2nd going to be truncated
        conds_ori, conds_proc = csn1f.keys(), csn1f.keys()
        # initialize possible condition as retrieved from the buffer
        cond_from_buf, offset = '', 0
        #
        # Now we loop until we found a matching condition from the buffer
        while len(conds_proc) > 0:
            # collect a uniq set of the 1st bit from all conditions
            conds_1bit = set([c[:1] for c in conds_proc])
            # consume buffer bit per bit: 1st get binary value from BUF
            bufval = (self.BUF<<offset).left_val(1)
            #
            # check if we need to evaluate padding conditions
            if all([c in 'LH' for c in conds_1bit]):
                # get LH value from the current buffer, taking care of offset
                cond_from_buf += 'L' \
                    if bufval == self.L[(self._consumed+self._offset+offset)%8] \
                    else 'H'
            # or binary conditions
            elif all([c in '01' for c in conds_1bit]):
                cond_from_buf += str(bufval)
            # otherwise we mixed binary and padding conditions 
            # at the same iteration and this is unsolvable... call C.E. Shannon
            else:
                self._cond_error(conds_ori)
            #
            # if we found a matching condition
            # return and append the corresponding object
            if cond_from_buf in conds_ori:
                self._append_from_dict(csn1f[cond_from_buf], len(cond_from_buf))
                return
            # otherwise truncate conditions' 1st bit for next iteration
            # add an offset bit, and loop
            conds_proc = [c[1:] for c in conds_proc if len(c) > 1]
            offset += 1
        #
        # we should never come here,
        # or maybe we forgot a condition in our dict
        self._cond_error(conds_ori)
    
    def _cond_error(self, condset):
        #if self.dbg >= ERR:
        #    log(ERR, '(CSN1._get_cond - %s) undefined CSN1 condition set: %s' \
        #             % (condset, self.__class__))
        log(ERR, '(CSN1._get_cond - %s) undefined CSN1 condition set: %s' \
                     % (condset, self.__class__))
        if self.safe:
            assert()
    
    def _append_from_dict(self, from_dict, condlen):
        # xtra verbose debugging
        if self.dbg > DBG:
            if type(from_dict) is tuple: obj = list(from_dict)
            elif hasattr(from_dict, 'CallName'): obj = from_dict.CallName
            else: obj = from_dict
            log(DBG, '(CSN1._eval_csn1_dict) selected from_dict: %s' % obj)
        # if condition lead to None: padding
        if from_dict == BREAK:
            self._append_map_csn1_field(Bit('%s' % self.pad_name, BitLen=condlen))
        # if condition lead to breaking a loop 
        # for crappy CSN1 syntax like {1 expr}**0
        elif from_dict == BREAK_LOOP:
            self._append_map_csn1_field(Bit('%s' % self.pad_name, BitLen=condlen))
            self._loop = False
        # if condition lead to something:
        else:
            self._append_map_csn1_field(Bit('%s' % self.cond_name, BitLen=condlen))
            # recurse to the main csn1 eval method applied to object from dict
            self._eval_csn1(from_dict)
    
    # In case we directly have a CSN1 field
    def _append_map_csn1_field(self, csn1f):
        # ensure there is still enough buffer to map on new fields
        if hasattr(csn1f, 'bit_len') \
        and csn1f.bit_len() > (self._buflen - (self._consumed+self._offset)):
            if self.dbg >= WNG:
                log(WNG, '(CSN1.map - %s) buffer not long enough for field: ' \
                         '%s from csn1List' % (self.__class__, csn1f.CallName))
            self._map_exit = True
            return
        # append the field to the layer
        if self.dbg >= DBG:
            log(DBG, '(CSN1._append_map_csn1_field) appending csn1 field: %s' \
                % csn1f.CallName)
        #
        # map the BUF on the element: this solves its bit length
        # moreover, we transmit the byte offset, for solving correctly L|H flags
        if type(csn1f) == type and issubclass(csn1f, CSN1):
            self.append(csn1f().clone())
        else:
            self.append(csn1f)
        #
        if isinstance(self[-1], CSN1):
            self[-1].map(self.BUF, (self._consumed+self._offset)%8)
            bitlen = self[-1].bit_len()
        else:
            self[-1].map(self.BUF)
            bitlen = self[-1].bit_len()
            # check if LHFlag to possibly update its LH dictionnary
            if isinstance(self[-1], LHFlag):
                l = self.L[(self._consumed+self._offset)%8]
                h = (1, 0)[l]
                self[-1].LHdict = iad({l:'L', h:'H'})
        #
        # update global BUFFER and consumed bit length 
        self.BUF = self.BUF << bitlen
        self._consumed += bitlen
        #
    
    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]
        str_lst = []
        for e in self:
            if e.is_transparent():
                pass
            elif not self._show_all and e.CallName in self._IE_no_show:
                pass
            else:
                if isinstance(e, Str) and isinstance(e.Pt, Layer):
                    str_lst.append(e.Pt.show(with_trans).replace('\n', '\n '))
                else:
                    str_lst.append(e.show(with_trans).replace('\n', '\n '))
        #
        # 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]
libmich/core/CSN1.py
BREAK      = 0xdead1001


Question: from binascii import unhexlify as unh
from libmich.core.element import Bit, Layer, show, log, DBG, WNG, ERR
from libmich.core.shtr import decomposer, shtr
from libmich.core.CSN1 import CSN1, LHFlag, BREAK, BREAK_LOOP
from libmich.formats.L3Mobile_IE import AccessTechnoType_dict
# -*- coding: UTF-8 -*-
#/**
# * Software Name : libmich 
# * Version : 0.2.2
# *
# * Copyright © 2012. Benoit Michau. ANSSI.
# *
# * This program is free software: you can redistribute it and/or modify
# * it under the terms of the GNU General Public License version 2 as published
# * by the Free Software Foundation. 
# *
# * This program is distributed in the hope that it will be useful,
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# * GNU General Public License for more details. 
# *
# * You will find a copy of the terms and conditions of the GNU General Public
# * License version 2 in the "license.txt" file or
# * see http://www.gnu.org/licenses/ or write to the Free Software Foundation,
# * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
# *
# *--------------------------------------------------------
# * File Name : formats/L3GSM_rest.py
# * Created : 2012-04-10 
# * Authors : Benoit Michau 
# *--------------------------------------------------------
#*/ 

#!/usr/bin/env python

# exporting
__all__ = ['RestOctets',
           'P1RestOctets', 'P2RestOctets', 'P3RestOctets',
           'IARestOctets',
           'SI1RestOctets', 'SI2terRestOctets', 'SI2quaterRestOctets',
           'SI3RestOctets', 'SI4RestOctets', 'SI6RestOctets', 'SI13RestOctets']

# for convinience
#

# Decoder joker
class _Paf_(Bit):
    def map(self, s):
        raise(Exception)


# In order to map correctly the rest bits at the end of the RestOctets
class RestOctets(CSN1):
    # name for padding at the end
    rest_name = 'Rest_padding'
    # GSM padding: 0x2b (for CSN1)
    L = [0,0,1,0,1,0,1,1]
    # default has empty CSN1
    csn1List = []
    #
    def __init__(self, build_auto=False, build_path=None, name=''):
        # build according to the CSN1 struct
        CSN1.__init__(self, build_auto, build_path, name)
        # check how many bits already built, how many remaining
        # TODO
        
    def map(self, string='', byte_offset=0):
        # map according to the CSN1 struct
        CSN1.map(self, string, byte_offset)
        # check how many bits already mapped, and how many remaining
        done = self.bit_len()
        rem = len(string)*8 - done

Answer:         s = shtr(string) << done