def __init__(self, name=None, **kwds): name = name or kwds.setdefault('context', self.__class__.__name__) slct = octets_noop('select') slct[None] = cpppo.decide('SendData', predicate=lambda path=None, data=None, ** kwds: data[path + '..command'] in (0x006f, 0x0070), state=send_data(limit='...length', terminal=True)) slct[None] = cpppo.decide('Register', predicate=lambda path=None, data=None, ** kwds: data[path + '..command'] == 0x0065, state=register(limit='...length', terminal=True)) slct[None] = cpppo.decide('Unregister', predicate=lambda path=None, data=None, ** kwds: data[path + '..command'] == 0x0066, state=unregister(limit='...length', terminal=True)) slct[None] = cpppo.decide('ListServices', predicate=lambda path=None, data=None, ** kwds: data[path + '..command'] == 0x0004, state=list_services(limit='...length', terminal=True)) super(CIP, self).__init__(name=name, initial=slct, **kwds)
def __init__(self, name=None, datatype=None, **kwds): name = name or kwds.setdefault('context', self.__class__.__name__) assert datatype, "Must specify a relative path to the CIP data type; found: %r" % datatype slct = octets_noop('select') i_8d = octets_noop('end_8bit', terminal=True) i_8d[True] = i_8p = SINT() i_8p[None] = move_if('mov_8bit', source='.SINT', destination='.data', initializer=lambda **kwds: [], state=i_8d) i16d = octets_noop('end16bit', terminal=True) i16d[True] = i16p = INT() i16p[None] = move_if('mov16bit', source='.INT', destination='.data', initializer=lambda **kwds: [], state=i16d) i32d = octets_noop('end32bit', terminal=True) i32d[True] = i32p = DINT() i32p[None] = move_if('mov32bit', source='.DINT', destination='.data', initializer=lambda **kwds: [], state=i32d) fltd = octets_noop('endfloat', terminal=True) fltd[True] = fltp = REAL() fltp[None] = move_if('movfloat', source='.REAL', destination='.data', initializer=lambda **kwds: [], state=fltd) slct[None] = cpppo.decide('SINT', state=i_8p, predicate=lambda path=None, data=None, ** kwds: data[path + datatype] == SINT.tag_type) slct[None] = cpppo.decide('INT', state=i16p, predicate=lambda path=None, data=None, ** kwds: data[path + datatype] == INT.tag_type) slct[None] = cpppo.decide('DINT', state=i32p, predicate=lambda path=None, data=None, ** kwds: data[path + datatype] == DINT.tag_type) slct[None] = cpppo.decide('REAL', state=fltp, predicate=lambda path=None, data=None, ** kwds: data[path + datatype] == REAL.tag_type) super(typed_data, self).__init__(name=name, initial=slct, **kwds)
def test_decide(): """Allow state transition decisions based on collected context other than just the next source symbol. """ e = cpppo.state("enter") e["a"] = a = cpppo.state_input("a", context="a") a[" "] = s1 = cpppo.state_drop("s1") s1[" "] = s1 s1[None] = i1 = cpppo.integer("i1", context="i1") i1[" "] = s2 = cpppo.state_drop("s2") s2[" "] = s2 s2[None] = i2 = cpppo.integer("i2", context="i2") less = cpppo.state("less", terminal=True) greater = cpppo.state("greater", terminal=True) equal = cpppo.state("equal", terminal=True) i2[None] = cpppo.decide("isless", less, predicate=lambda machine, source, path, data: data.i1 < data.i2) i2[None] = cpppo.decide("isgreater", greater, predicate=lambda machine, source, path, data: data.i1 > data.i2) i2[None] = equal source = cpppo.peekable(str("a 1 2")) data = cpppo.dotdict() with cpppo.dfa("comparo", initial=e) as comparo: for i, (m, s) in enumerate(comparo.run(source=source, data=data)): log.info( "%s #%3d -> %10.10s; next byte %3d: %-10.10r: %r", m.name_centered(), i, s, source.sent, source.peek(), data, ) assert i == 11 assert s is less source = cpppo.peekable(str("a 33 33")) data = cpppo.dotdict() with cpppo.dfa("comparo", initial=e) as comparo: for i, (m, s) in enumerate(comparo.run(source=source, data=data)): log.info( "%s #%3d -> %10.10s; next byte %3d: %-10.10r: %r", m.name_centered(), i, s, source.sent, source.peek(), data, ) assert i == 13 assert s is equal
def __init__( self, name=None, **kwds ): name = name or kwds.setdefault( 'context', self.__class__.__name__ ) slct = octets_noop( 'select' ) usnd = USINT( context='service' ) usnd[True] = path = EPATH( context='path' ) # All Unconnected Send (0x52) encapsulated request.input have a length, followed by an # optional pad, and then a route path. path[True] = prio = USINT( context='priority' ) prio[True] = timo = USINT( context='timeout_ticks' ) timo[True] = leng = UINT( context='length' ) leng[None] = mesg = octets( context='request', repeat='..length' ) # Route segments, like path but for hops/links/keys... rout = route_path( terminal=True ) # If length is odd, drop the pad byte after the message, and then parse the route_path pad0 = octets_drop( 'pad', repeat=1 ) pad0[None] = rout mesg[None] = cpppo.decide( 'pad', state=pad0, predicate=lambda path=None, data=None, **kwds: data[path+'.length'] % 2 ) # But, if no pad, go parse the route path mesg[None] = rout # So; 0x52 Unconnected Send parses an request with a Route Path, but anything else is just # an opaque encapsulated request; just copy all remaining bytes to the request.input. slct[b'\x52'[0]] = usnd slct[True] = othr = octets( context='request', terminal=True ) othr[True] = othr super( unconnected_send, self ).__init__( name=name, initial=slct, **kwds )
def __init__( self, name=None, **kwds ): name = name or kwds.setdefault( 'context', self.__class__.__name__ ) # Parse the status, and status_ext.size stat = USINT( 'status', context=None ) stat[True] = size = USINT( '_ext.size', extension='_ext.size' ) # Prepare a state-machine to parse each UINT into .UINT, and move it onto the .data list exts = UINT( 'ext_status', extension='.ext_status' ) exts[None] = move_if( 'data', source='.ext_status', destination='.data', initializer=lambda **kwds: [] ) exts[None] = cpppo.state( 'done', terminal=True ) # Parse each status_ext.data in a sub-dfa, repeating status_ext.size times each = cpppo.dfa( 'each', extension='_ext', initial=exts, repeat='_ext.size', terminal=True ) # Only enter the state_ext.data dfa if status_ext.size is non-zero size[None] = cpppo.decide( '_ext.size', predicate=lambda path=None, data=None, **kwds: data[path+'_ext.size'], state=each ) # Otherwise, we're done! size[None] = octets_noop( 'done', terminal=True ) super( status, self ).__init__( name=name, initial=stat, **kwds )
def __init__(self, name=None, **kwds): name = name or kwds.setdefault('context', self.__class__.__name__) # Parse the status, and status_ext.size stat = USINT('status', context=None) stat[True] = size = USINT('_ext.size', extension='_ext.size') # Prepare a state-machine to parse each UINT into .UINT, and move it onto the .data list exts = UINT('ext_status', extension='.ext_status') exts[None] = move_if('data', source='.ext_status', destination='.data', initializer=lambda **kwds: []) exts[None] = cpppo.state('done', terminal=True) # Parse each status_ext.data in a sub-dfa, repeating status_ext.size times each = cpppo.dfa('each', extension='_ext', initial=exts, repeat='_ext.size', terminal=True) # Only enter the state_ext.data dfa if status_ext.size is non-zero size[None] = cpppo.decide('_ext.size', predicate=lambda path=None, data=None, ** kwds: data[path + '_ext.size'], state=each) # Otherwise, we're done! size[None] = octets_noop('done', terminal=True) super(status, self).__init__(name=name, initial=stat, **kwds)
def __init__(self, name=None, **kwds): """Parse CPF list items 'til .count reached, which should be simultaneous with symbol exhaustion, if caller specified a symbol limit. """ name = name or kwds.setdefault('context', self.__class__.__name__) # A number, and then each CPF item consistes of a type, length and then parsable data. ityp = UINT(context='type_id') ityp[True] = ilen = UINT(context='length') ilen[None] = cpppo.decide('empty', predicate=lambda path=None, data=None, ** kwds: not data[path].length, state=octets_noop('done', terminal=True)) # Prepare a parser for each recognized CPF item type. It must establish one level of # context, because we need to pass it a limit='..length' denoting the length we just parsed. for typ, cls in (self.item_parsers or {}).items(): ilen[None] = cpppo.decide(cls.__name__, state=cls(terminal=True, limit='..length'), predicate=lambda path=None, data=None, ** kwds: data[path].type_id == typ) # If we don't recognize the CPF item type, just parse remainder into .input (so we could re-generate) ilen[None] = urec = octets('unrecognized', context=None, terminal=True) urec[True] = urec # Each item is collected into '.item__', 'til no more input available, and then moved into # place into '.item' (init to []) item = cpppo.dfa('each', context='item__', initial=ityp) item[None] = move_if('move', source='.item__', destination='.item', initializer=lambda **kwds: []) item[None] = cpppo.state('done', terminal=True) # Parse count, and then exactly .count CPF items. loop = UINT(context='count') loop[None] = cpppo.dfa('all', initial=item, repeat='.count', terminal=True) super(CPF, self).__init__(name=name, initial=loop, **kwds)
def __init__( self, name=None, **kwds ): name = name or kwds.setdefault( 'context', self.__class__.__name__ ) slct = octets_noop( 'select' ) slct[None] = cpppo.decide( 'SendData', predicate=lambda path=None, data=None, **kwds: data[path+'..command'] in (0x006f,0x0070), state=send_data( limit='...length', terminal=True )) slct[None] = cpppo.decide( 'Register', predicate=lambda path=None, data=None, **kwds: data[path+'..command'] == 0x0065, state=register( limit='...length', terminal=True )) slct[None] = cpppo.decide( 'Unregister', predicate=lambda path=None, data=None, **kwds: data[path+'..command'] == 0x0066, state=unregister( limit='...length', terminal=True )) slct[None] = cpppo.decide( 'ListServices', predicate=lambda path=None, data=None, **kwds: data[path+'..command'] == 0x0004, state=list_services(limit='...length', terminal=True )) super( CIP, self ).__init__( name=name, initial=slct, **kwds )
def test_decide(): """Allow state transition decisions based on collected context other than just the next source symbol. """ e = cpppo.state("enter") e['a'] = a = cpppo.state_input("a", context='a') a[' '] = s1 = cpppo.state_drop("s1") s1[' '] = s1 s1[None] = i1 = cpppo.integer("i1", context='i1') i1[' '] = s2 = cpppo.state_drop("s2") s2[' '] = s2 s2[None] = i2 = cpppo.integer("i2", context='i2') less = cpppo.state("less", terminal=True) greater = cpppo.state("greater", terminal=True) equal = cpppo.state("equal", terminal=True) i2[None] = cpppo.decide( "isless", less, predicate=lambda machine, source, path, data: data.i1 < data.i2) i2[None] = cpppo.decide( "isgreater", greater, predicate=lambda machine, source, path, data: data.i1 > data.i2) i2[None] = equal source = cpppo.peekable(str('a 1 2')) data = cpppo.dotdict() with cpppo.dfa("comparo", initial=e) as comparo: for i, (m, s) in enumerate(comparo.run(source=source, data=data)): log.info("%s #%3d -> %10.10s; next byte %3d: %-10.10r: %r", m.name_centered(), i, s, source.sent, source.peek(), data) assert i == 12 assert s is less source = cpppo.peekable(str('a 33 33')) data = cpppo.dotdict() with cpppo.dfa("comparo", initial=e) as comparo: for i, (m, s) in enumerate(comparo.run(source=source, data=data)): log.info("%s #%3d -> %10.10s; next byte %3d: %-10.10r: %r", m.name_centered(), i, s, source.sent, source.peek(), data) assert i == 14 assert s is equal
def __init__( self, name=None, datatype=None, **kwds ): name = name or kwds.setdefault( 'context', self.__class__.__name__ ) assert datatype, "Must specify a relative path to the CIP data type; found: %r" % datatype slct = octets_noop( 'select' ) i_8d = octets_noop( 'end_8bit', terminal=True ) i_8d[True] = i_8p = SINT() i_8p[None] = move_if( 'mov_8bit', source='.SINT', destination='.data', initializer=lambda **kwds: [], state=i_8d ) i16d = octets_noop( 'end16bit', terminal=True ) i16d[True] = i16p = INT() i16p[None] = move_if( 'mov16bit', source='.INT', destination='.data', initializer=lambda **kwds: [], state=i16d ) i32d = octets_noop( 'end32bit', terminal=True ) i32d[True] = i32p = DINT() i32p[None] = move_if( 'mov32bit', source='.DINT', destination='.data', initializer=lambda **kwds: [], state=i32d ) fltd = octets_noop( 'endfloat', terminal=True ) fltd[True] = fltp = REAL() fltp[None] = move_if( 'movfloat', source='.REAL', destination='.data', initializer=lambda **kwds: [], state=fltd ) slct[None] = cpppo.decide( 'SINT', state=i_8p, predicate=lambda path=None, data=None, **kwds: data[path+datatype] == SINT.tag_type ) slct[None] = cpppo.decide( 'INT', state=i16p, predicate=lambda path=None, data=None, **kwds: data[path+datatype] == INT.tag_type ) slct[None] = cpppo.decide( 'DINT', state=i32p, predicate=lambda path=None, data=None, **kwds: data[path+datatype] == DINT.tag_type ) slct[None] = cpppo.decide( 'REAL', state=fltp, predicate=lambda path=None, data=None, **kwds: data[path+datatype] == REAL.tag_type ) super( typed_data, self ).__init__( name=name, initial=slct, **kwds )
def __init__( self, name=None, **kwds ): """Parse CPF list items 'til .count reached, which should be simultaneous with symbol exhaustion, if caller specified a symbol limit. """ name = name or kwds.setdefault( 'context', self.__class__.__name__ ) # A number, and then each CPF item consistes of a type, length and then parsable data. ityp = UINT( context='type_id' ) ityp[True] = ilen = UINT( context='length' ) ilen[None] = cpppo.decide( 'empty', predicate=lambda path=None, data=None, **kwds: not data[path].length, state=octets_noop( 'done', terminal=True )) # Prepare a parser for each recognized CPF item type. It must establish one level of # context, because we need to pass it a limit='..length' denoting the length we just parsed. for typ,cls in ( self.item_parsers or {} ).items(): ilen[None] = cpppo.decide( cls.__name__, state=cls( terminal=True, limit='..length' ), predicate=lambda path=None, data=None, **kwds: data[path].type_id == typ ) # If we don't recognize the CPF item type, just parse remainder into .input (so we could re-generate) ilen[None] = urec = octets( 'unrecognized', context=None, terminal=True ) urec[True] = urec # Each item is collected into '.item__', 'til no more input available, and then moved into # place into '.item' (init to []) item = cpppo.dfa( 'each', context='item__', initial=ityp ) item[None] = move_if( 'move', source='.item__', destination='.item', initializer=lambda **kwds: [] ) item[None] = cpppo.state( 'done', terminal=True ) # Parse count, and then exactly .count CPF items. loop = UINT( context='count' ) loop[None] = cpppo.dfa( 'all', initial=item, repeat='.count', terminal=True ) super( CPF, self ).__init__( name=name, initial=loop, **kwds )
def __read_tag_reply(): # Read Tag Service (reply). Remainder of symbols are typed data. srvc = USINT( context='service' ) srvc[True] = rsvd = octets_drop( 'reserved', repeat=1 ) rsvd[True] = stts = status() stts[None] = schk = octets_noop( 'check', terminal=True ) dtyp = UINT( 'type', context='read_tag', extension='.type' ) dtyp[True] = typed_data( 'data', context='read_tag', datatype='.type', terminal=True ) # For status 0x00 (Success), type/data follows. schk[None] = cpppo.decide( 'ok', state=dtyp, predicate=lambda path=None, data=None, **kwds: data[path+'.status' if path else 'status']== 0x00 ) return srvc
def __init__(self, name=None, **kwds): name = name or kwds.setdefault('context', self.__class__.__name__) slct = octets_noop('select') usnd = USINT(context='service') usnd[True] = path = EPATH(context='path') # All Unconnected Send (0x52) encapsulated request.input have a length, followed by an # optional pad, and then a route path. path[True] = prio = USINT(context='priority') prio[True] = timo = USINT(context='timeout_ticks') timo[True] = leng = UINT(context='length') leng[None] = mesg = octets(context='request', repeat='..length') # Route segments, like path but for hops/links/keys... rout = route_path(terminal=True) # If length is odd, drop the pad byte after the message, and then parse the route_path pad0 = octets_drop('pad', repeat=1) pad0[None] = rout mesg[None] = cpppo.decide('pad', state=pad0, predicate=lambda path=None, data=None, ** kwds: data[path + '.length'] % 2) # But, if no pad, go parse the route path mesg[None] = rout # So; 0x52 Unconnected Send parses an request with a Route Path, but anything else is just # an opaque encapsulated request; just copy all remaining bytes to the request.input. slct[b'\x52'[0]] = usnd slct[True] = othr = octets(context='request', terminal=True) othr[True] = othr super(unconnected_send, self).__init__(name=name, initial=slct, **kwds)
def __init__(self, name=None, **kwds): name = name or kwds.setdefault('context', self.__class__.__name__) # All command request/reply share the first 12 bytes: init = USINT(context='byt0') init[True] = byt1 = USINT(context='byt1') byt1[True] = dst = USINT(context='dst') dst[True] = byt3 = USINT(context='byt3') byt3[True] = byt4 = USINT(context='byt4') byt4[True] = byt5 = USINT(context='byt5') byt5[True] = src = USINT(context='src') src[True] = byt7 = USINT(context='byt7') byt7[True] = cmd = USINT(context='cmd') cmd[True] = sts = USINT(context='sts') sts[True] = tns = UINT(context='tns') # Reply has STS == 0xF0, then EXT STS is parsed rpy_extsts = USINT(context='extsts', terminal=True) # Reply has STS != 0x00, then reply is done rpy_sts = octets_noop(terminal=True) # Reply has STS == 0; collect remaining USINTs into .data (cannot parse, b/c replies not self-describing) rpy = typed_data(tag_type=USINT.tag_type, context='', terminal=True) # Split the replies off # For a Reply (CMD & 0xb01000000 set); If sts was !0, then the byte following TNS is EXT STS. tns[None] = cpppo.decide( 'RPY EXT STS?', state=rpy_extsts, predicate=lambda path=None, data=None, **kwds: \ bool( data[path].cmd & 0b01000000 ) and data[path].sts == 0xf0 ) tns[None] = cpppo.decide( 'RPY STS?', state=rpy_sts, predicate=lambda path=None, data=None, **kwds: \ bool( data[path].cmd & 0b01000000 ) and data[path].sts != 0x00 ) tns[None] = cpppo.decide( 'RPY?', state=rpy, predicate=lambda path=None, data=None, **kwds: \ bool( data[path].cmd & 0b01000000 ) and data[path].sts == 0x00 ) # Request follows TNS. May be a request w/o a FNC. See: # http://literature.rockwellautomation.com/idc/groups/literature/documents/rm/1770-rm516_-en-p.pdf # 7-2 for a table of all CMD/FNC codes. Just collect the rest into .data. req_nonfnc = typed_data(tag_type=USINT.tag_type, context='', terminal=True) tns[None] = cpppo.decide( 'REQ NON FNC', state=req_nonfnc, predicate=lambda path=None, data=None, **kwds: \ data[path].cmd in ( 0x00, # protected write 0x01, # unprotected read 0x02, # protected bit write 0x05, # physical read 0x05, # unprotected bit write 0x08, # unprotected write )) # ... 7-17: .read...: Protected Typed Logical Read w/ 3 Address Fields has (sub-)element w/ 1 or 2-byte values tlr3 = tlr3b = USINT(context='read', extension='.bytes') tlr3b[True] = tlr3fn = USINT(context='read', extension='.file') # 0-254 tlr3fn16 = UINT(context='read', extension='.file') tlr3fn[None] = cpppo.decide( 'FLN 16?', state=tlr3fn16, predicate=lambda path=None, data=None, **kwds: \ data[path].read.file == 0xFF ) tlr3fn16[True] = tlr3fn[True] = tlr3ft = USINT( context='read', extension='.type') # 0x80-8F; 89 = integer, 8A = float, ... tlr3ft[True] = tlr3el = USINT( context='read', extension='.element') # 0xFF ==> 16-bit element # follows tlr3el16 = UINT(context='read', extension='.element') tlr3el[None] = cpppo.decide( 'ELE 16?', state=tlr3el16, predicate=lambda path=None, data=None, **kwds: \ data[path].read.element == 0xFF ) tlr3el16[True] = tlr3el[True] = tlr3se = USINT(context='read', extension='.subelement', terminal=True) tlr3se16 = UINT(context='read', extension='.subelement', terminal=True) tlr3se[None] = cpppo.decide( 'SEL 16?', state=tlr3se16, predicate=lambda path=None, data=None, **kwds: \ data[path].read.subelement == 0xFF ) # 7-28: .read...: Typed Read PLC5 # 13-11: Uses PLC-5 Logical Binary Addressing. Too complex to implement right now. #trp5 = trp5o = UINT( context='read', extension='.offset' )# Offset to 1st item in range to return #trp5o[True] = trp5t = UINT( context='read', extension='.total' ) # Total number of data items in range # ... 7-6: .status: Diagnostic Status has no additional data; create (empty) .status diag = diagm = octets_noop(context='status', terminal=True) diagm.initial[None] = move_if('mark', initializer=True) # A Request CMD w/ a FNC code. See if we recognize it. tns[None] = fnc = USINT(context='fnc') #fnc[None] = cpppo.decide( 'Typed Read?', state=tlr3, # Typed Read (read block) w/ PLC-5 sys. addressing # predicate=lambda path=None, data=None, **kwds: \ # data[path].cmd in (0x0F, 0x2F) and data[path].fnc == 0x68 ) fnc[None] = cpppo.decide( 'Typed Read 3?', state=tlr3, # Protected Typed Logical Read w/ 3 Address Fields predicate=lambda path=None, data=None, **kwds: \ data[path].cmd in (0x0F, 0x2F) and data[path].fnc == 0xA2 ) fnc[None] = cpppo.decide( 'Diagnostic Status? ', state=diag, # Diagnostic Status predicate=lambda path=None, data=None, **kwds: \ data[path].cmd in (0x06, 0x26) and data[path].fnc == 0x03 ) # Unknown CMD/FNC; just harvest the rest of the request into .data (often no further command data) fnc[None] = typed_data(tag_type=USINT.tag_type, context='', terminal=True) super(ANC_120e_DF1, self).__init__(name=name, initial=init, **kwds)
def __init__( self, name=None, **kwds ): name = name or kwds.setdefault( 'context', self.__class__.__name__ ) # Get the size, and chain remaining machine onto rest. When used as a Route Path, the size # is padded, so insert a state to drop the pad, and chain rest to that instead. size = rest = USINT( context='size' ) if self.padsize: size[True] = rest = octets_drop( 'pad', repeat=1 ) # After capturing each segment__ (pseg), move it onto the path segment list, and loop pseg = octets_noop( 'type', terminal=True ) # ...segment parsers... pmov = move_if( 'move', initializer=lambda **kwds: [], source='..segment__', destination='..segment', state=pseg ) # Wire each different segment type parser between pseg and pmov pseg[b'\x28'[0]]= e_8t = octets_drop( 'type', repeat=1 ) e_8t[True] = e_8v = USINT( 'elem_8bit', context='element') e_8v[None] = pmov pseg[b'\x29'[0]]= e16t = octets_drop( 'type', repeat=2 ) e16t[True] = e16v = UINT( 'elem16bit', context='element') e16v[None] = pmov pseg[b'\x2a'[0]]= e32t = octets_drop( 'type', repeat=2 ) e32t[True] = e32v = UDINT( 'elem32bit', context='element') e32v[None] = pmov pseg[b'\x20'[0]]= c_8t = octets_drop( 'type', repeat=1 ) c_8t[True] = c_8v = USINT( 'clas_8bit', context='class') c_8v[None] = pmov pseg[b'\x21'[0]]= c16t = octets_drop( 'type', repeat=2 ) c16t[True] = c16v = UINT( 'clas16bit', context='class') c16v[None] = pmov pseg[b'\x24'[0]]= i_8t = octets_drop( 'type', repeat=1 ) i_8t[True] = i_8v = USINT( 'inst_8bit', context='instance') i_8v[None] = pmov pseg[b'\x25'[0]]= i16t = octets_drop( 'type', repeat=2 ) i16t[True] = i16v = UINT( 'inst16bit', context='instance') i16v[None] = pmov pseg[b'\x30'[0]]= a_8t = octets_drop( 'type', repeat=1 ) a_8t[True] = a_8v = USINT( 'attr_8bit', context='attribute') a_8v[None] = pmov pseg[b'\x31'[0]]= a16t = octets_drop( 'type', repeat=2 ) a16t[True] = a16v = UINT( 'attr16bit', context='attribute') a16v[None] = pmov pseg[b'\x91'[0]]= symt = octets_drop( 'type', repeat=1 ) symt[True] = syml = USINT( 'sym_len', context='symbolic.length' ) syml[None] = symv = cpppo.string_bytes( 'symbolic', context='symbolic', limit='.length', initial='.*', decode='iso-8859-1' ) # An odd-length ANSI Extended Symbolic name means an odd total. Pad symo = octets_drop( 'pad', repeat=1 ) symo[None] = pmov symv[None] = cpppo.decide( 'odd', predicate=lambda path=None, data=None, **kwds: len( data[path].symbolic ) % 2, state=symo ) symv[None] = pmov # Route Path port/link-address. See Vol 1-3.13, Table C-1.3 Port Segment Encoding. # segment: 0b000spppp # |\\\\+-> port number 0x01-0x0E; 0x0F=>extended # | # +------> link size+address; 0=>numeric, 1=>size+string # def port_fix( path=None, data=None, **kwds ): """Discard port values about 0x0F; return True (transition) if remaining port value is 0x0F (Optional Extended port)""" data[path].port &= 0x0F if data[path].port == 0x0F: # Port is extended; discard and prepare to collect new port number data[path].port = cpppo.dotdict() return True # Port is OK; don't transition return False # [01-0E][LL] port 01-0E, link-address #LL pseg[b'\x01'[0]]= pnum = USINT( 'port_num', context='port' ) pseg[b'\x02'[0]] = pnum pseg[b'\x03'[0]] = pnum pseg[b'\x04'[0]] = pnum pseg[b'\x05'[0]] = pnum pseg[b'\x06'[0]] = pnum pseg[b'\x07'[0]] = pnum pseg[b'\x08'[0]] = pnum pseg[b'\x09'[0]] = pnum pseg[b'\x0a'[0]] = pnum pseg[b'\x0b'[0]] = pnum pseg[b'\x0c'[0]] = pnum pseg[b'\x0d'[0]] = pnum pseg[b'\x0e'[0]] = pnum # [0F][PPPP][LL] port 0xPPPP, link-address 0xLL pseg[b'\x0f'[0]] = pnum # A big port#; re-scan a UINT into .port (won't work 'til port_fix is called) pnbg = UINT( 'port_nbg', context='port' ) pnbg[True] = pnlk = USINT( 'link_num', context='link' ) # Fix the port#; if 0x0F, setup for extended port and transition to pnbg. Otherwise, # (not extended port), just go the the port numeric link. pnum[None] = cpppo.decide( 'port_nfix', predicate=port_fix, state=pnbg ) pnum[None] = pnlk pnlk[None] = pmov # and done; move segment, get next # [11-1E][SS]'123.123.123.123'[00] port 0x01-0E, link address '123.123.123.123' (pad if size 0xSS odd) pseg[b'\x11'] = padr = USINT( 'port_adr', context='port' ) pseg[b'\x12'[0]] = padr pseg[b'\x13'[0]] = padr pseg[b'\x14'[0]] = padr pseg[b'\x15'[0]] = padr pseg[b'\x16'[0]] = padr pseg[b'\x17'[0]] = padr pseg[b'\x18'[0]] = padr pseg[b'\x19'[0]] = padr pseg[b'\x1a'[0]] = padr pseg[b'\x1b'[0]] = padr pseg[b'\x1c'[0]] = padr pseg[b'\x1d'[0]] = padr pseg[b'\x1e'[0]] = padr # [1F][SS][PPPP]'123.123.123.123'[00] port 0xPPPP, link address '123.123.123.123' (pad if size SS odd) pseg[b'\x1f'[0]] = padr # Harvest the addresses into .link adrv = cpppo.string_bytes( 'link_add', context='link', limit='.length', initial='.*', decode='iso-8859-1' ) # An odd-length link address means an odd total. Pad adro = octets_drop( 'link_pad', repeat=1 ) adro[None] = pmov adrv[None] = cpppo.decide( 'link_odd', predicate=lambda path=None, data=None, **kwds: len( data[path+'.link'] ) % 2, state=adro ) adrv[None] = pmov # A big port#; re-scan a UINT into .port (won't work 'til port_fix is called) pabg = UINT( 'port_abg', context='port' ) pabg[None] = adrv # padr[True] = adrl = USINT( 'link_len', context='link.length' ) adrl[None] = cpppo.decide( 'port_afix', predicate=port_fix, state=pabg ) adrl[None] = adrv # Parse all segments in a sub-dfa limited by the parsed path.size (in words; double) rest[None] = cpppo.dfa( 'each', context='segment__', initial=pseg, terminal=True, limit=lambda path=None, data=None, **kwds: data[path+'..size'] * 2 ) super( EPATH, self ).__init__( name=name, initial=size, **kwds )
def __init__(self, name=None, **kwds): name = name or kwds.setdefault('context', self.__class__.__name__) # Get the size, and chain remaining machine onto rest. When used as a Route Path, the size # is padded, so insert a state to drop the pad, and chain rest to that instead. size = rest = USINT(context='size') if self.padsize: size[True] = rest = octets_drop('pad', repeat=1) # After capturing each segment__ (pseg), move it onto the path segment list, and loop pseg = octets_noop('type', terminal=True) # ...segment parsers... pmov = move_if('move', initializer=lambda **kwds: [], source='..segment__', destination='..segment', state=pseg) # Wire each different segment type parser between pseg and pmov pseg[b'\x28'[0]] = e_8t = octets_drop('type', repeat=1) e_8t[True] = e_8v = USINT('elem_8bit', context='element') e_8v[None] = pmov pseg[b'\x29'[0]] = e16t = octets_drop('type', repeat=2) e16t[True] = e16v = UINT('elem16bit', context='element') e16v[None] = pmov pseg[b'\x2a'[0]] = e32t = octets_drop('type', repeat=2) e32t[True] = e32v = UDINT('elem32bit', context='element') e32v[None] = pmov pseg[b'\x20'[0]] = c_8t = octets_drop('type', repeat=1) c_8t[True] = c_8v = USINT('clas_8bit', context='class') c_8v[None] = pmov pseg[b'\x21'[0]] = c16t = octets_drop('type', repeat=2) c16t[True] = c16v = UINT('clas16bit', context='class') c16v[None] = pmov pseg[b'\x24'[0]] = i_8t = octets_drop('type', repeat=1) i_8t[True] = i_8v = USINT('inst_8bit', context='instance') i_8v[None] = pmov pseg[b'\x25'[0]] = i16t = octets_drop('type', repeat=2) i16t[True] = i16v = UINT('inst16bit', context='instance') i16v[None] = pmov pseg[b'\x30'[0]] = a_8t = octets_drop('type', repeat=1) a_8t[True] = a_8v = USINT('attr_8bit', context='attribute') a_8v[None] = pmov pseg[b'\x31'[0]] = a16t = octets_drop('type', repeat=2) a16t[True] = a16v = UINT('attr16bit', context='attribute') a16v[None] = pmov pseg[b'\x91'[0]] = symt = octets_drop('type', repeat=1) symt[True] = syml = USINT('sym_len', context='symbolic.length') syml[None] = symv = cpppo.string_bytes('symbolic', context='symbolic', limit='.length', initial='.*', decode='iso-8859-1') # An odd-length ANSI Extended Symbolic name means an odd total. Pad symo = octets_drop('pad', repeat=1) symo[None] = pmov symv[None] = cpppo.decide('odd', predicate=lambda path=None, data=None, ** kwds: len(data[path].symbolic) % 2, state=symo) symv[None] = pmov # Route Path port/link-address. See Vol 1-3.13, Table C-1.3 Port Segment Encoding. # segment: 0b000spppp # |\\\\+-> port number 0x01-0x0E; 0x0F=>extended # | # +------> link size+address; 0=>numeric, 1=>size+string # def port_fix(path=None, data=None, **kwds): """Discard port values about 0x0F; return True (transition) if remaining port value is 0x0F (Optional Extended port)""" data[path].port &= 0x0F if data[path].port == 0x0F: # Port is extended; discard and prepare to collect new port number data[path].port = cpppo.dotdict() return True # Port is OK; don't transition return False # [01-0E][LL] port 01-0E, link-address #LL pseg[b'\x01'[0]] = pnum = USINT('port_num', context='port') pseg[b'\x02'[0]] = pnum pseg[b'\x03'[0]] = pnum pseg[b'\x04'[0]] = pnum pseg[b'\x05'[0]] = pnum pseg[b'\x06'[0]] = pnum pseg[b'\x07'[0]] = pnum pseg[b'\x08'[0]] = pnum pseg[b'\x09'[0]] = pnum pseg[b'\x0a'[0]] = pnum pseg[b'\x0b'[0]] = pnum pseg[b'\x0c'[0]] = pnum pseg[b'\x0d'[0]] = pnum pseg[b'\x0e'[0]] = pnum # [0F][PPPP][LL] port 0xPPPP, link-address 0xLL pseg[b'\x0f'[0]] = pnum # A big port#; re-scan a UINT into .port (won't work 'til port_fix is called) pnbg = UINT('port_nbg', context='port') pnbg[True] = pnlk = USINT('link_num', context='link') # Fix the port#; if 0x0F, setup for extended port and transition to pnbg. Otherwise, # (not extended port), just go the the port numeric link. pnum[None] = cpppo.decide('port_nfix', predicate=port_fix, state=pnbg) pnum[None] = pnlk pnlk[None] = pmov # and done; move segment, get next # [11-1E][SS]'123.123.123.123'[00] port 0x01-0E, link address '123.123.123.123' (pad if size 0xSS odd) pseg[b'\x11'] = padr = USINT('port_adr', context='port') pseg[b'\x12'[0]] = padr pseg[b'\x13'[0]] = padr pseg[b'\x14'[0]] = padr pseg[b'\x15'[0]] = padr pseg[b'\x16'[0]] = padr pseg[b'\x17'[0]] = padr pseg[b'\x18'[0]] = padr pseg[b'\x19'[0]] = padr pseg[b'\x1a'[0]] = padr pseg[b'\x1b'[0]] = padr pseg[b'\x1c'[0]] = padr pseg[b'\x1d'[0]] = padr pseg[b'\x1e'[0]] = padr # [1F][SS][PPPP]'123.123.123.123'[00] port 0xPPPP, link address '123.123.123.123' (pad if size SS odd) pseg[b'\x1f'[0]] = padr # Harvest the addresses into .link adrv = cpppo.string_bytes('link_add', context='link', limit='.length', initial='.*', decode='iso-8859-1') # An odd-length link address means an odd total. Pad adro = octets_drop('link_pad', repeat=1) adro[None] = pmov adrv[None] = cpppo.decide('link_odd', predicate=lambda path=None, data=None, ** kwds: len(data[path + '.link']) % 2, state=adro) adrv[None] = pmov # A big port#; re-scan a UINT into .port (won't work 'til port_fix is called) pabg = UINT('port_abg', context='port') pabg[None] = adrv # padr[True] = adrl = USINT('link_len', context='link.length') adrl[None] = cpppo.decide('port_afix', predicate=port_fix, state=pabg) adrl[None] = adrv # Parse all segments in a sub-dfa limited by the parsed path.size (in words; double) rest[None] = cpppo.dfa('each', context='segment__', initial=pseg, terminal=True, limit=lambda path=None, data=None, **kwds: data[ path + '..size'] * 2) super(EPATH, self).__init__(name=name, initial=size, **kwds)