@@ -1231,8 +1231,8 @@ def __init__( self, name=None, **kwds ):
12311231 # But, if no pad, go parse the route path
12321232 mesg [None ] = rout
12331233
1234- # Parser for an Unconnected Send response (only seen if Unconnected Send itself fails, eg.
1235- # was sent to a Non-Routing (simple) CIP device, or was otherwise malformed.
1234+ # Parser for an Unconnected Send error response (only seen if Unconnected Send itself fails, eg.
1235+ # was sent to a Non-Routing (simple) CIP device, or was otherwise malformed. From
12361236 uerr = USINT ( context = 'service' )
12371237 uerr [True ] = uers = octets_drop ( 'reserved' , repeat = 1 )
12381238 uers [True ] = uest = status ()
@@ -1245,14 +1245,42 @@ def __init__( self, name=None, **kwds ):
12451245 # single enecapsulated C*Logix Read Tag Fragmented response (0x52/0xD2) carrying an error
12461246 # status. In fact, it is impossible to distinguish -- they are exactly the same size and
12471247 # carry the same server == 0xD2 and status coded.
1248- def u_s_err ( path = None , data = None , ** kwds ):
1249- log .normal ( "%s -- checking data[%r] for unconnected_send error: %s" , self , path , enip_format ( data ) )
1250- return False
1248+ def u_s_err ( path = None , data = None , source = None , ** kwds ):
1249+ log .isEnabledFor ( logging .INFO ) and log .info (
1250+ "%s -- checking data[%r] (kwds %r) for unconnected_send error: %s" ,
1251+ self , path , kwds , enip_format ( data )
1252+ )
1253+ if data [path + '..length' ] > 6 :
1254+ return False
1255+ # Might be a Unconnected Send error, perhaps with a remaining path size; peek at the
1256+ # error code and extended status; if the code is < 0x10 and there is NO extended status,
1257+ # then it must *not* be a Read Tag Fragmented error code, as ALL of its <0x10 status
1258+ # codes are required to have an extended status (which may be 0x0000, but must be
1259+ # there). HOWEVER, there are many (many) devices that DO NOT comply with the subtleties
1260+ # of the ODVA EtherNet/IP CIP spec -- and simply return eg. a status = 0x05 WITHOUT a
1261+ # 0x0000 extended status code. This makes it *impossible* to determine whether or not a
1262+ # Read Tag Fragmented response status belongs to the encapsulating Unconnected Send, or
1263+ # not. The only possible option is for the CLIENT to avoid sending Read Tag Fragmented
1264+ # requests in an Unconnected Send, or use a Multiple Service packet to encapsulate them.
1265+ svc = next ( source )
1266+ pad = next ( source )
1267+ sts = next ( source )
1268+ ext_siz = next ( source )
1269+ source .push ( ext_siz )
1270+ source .push ( sts )
1271+ source .push ( pad )
1272+ source .push ( svc )
1273+ log .isEnabledFor ( logging .DETAIL ) and log .detail (
1274+ "{} -- found an Unconnected Send response error status 0x{:02x}: {}" .format (
1275+ self , sts , enip_format ( data ))
1276+ )
1277+ return sts < 0x10 and ext_siz == 0
1278+
12511279
12521280 slct [b'\x52 ' [0 ]] = usnd
12531281 slct [b'\xD2 ' [0 ]] = decide (
12541282 'u_s_err' ,
1255- predicate = u_s_err , #lambda path=None, data=None, **kwds: data[path].length == 4,
1283+ predicate = u_s_err ,
12561284 state = uerr
12571285 )
12581286 slct [True ] = othr = octets ( context = 'request' , terminal = True )
@@ -1263,16 +1291,24 @@ def u_s_err( path=None, data=None, **kwds ):
12631291 @classmethod
12641292 def produce ( cls , data ):
12651293 result = b''
1266- if data .get ( 'service' ) == 0x52 :
1267- result += USINT .produce ( data .service )
1268- result += EPATH .produce ( data .path )
1269- result += USINT .produce ( data .priority )
1270- result += USINT .produce ( data .timeout_ticks )
1271- result += UINT .produce ( len ( data .request .input ))
1272- result += octets_encode ( data .request .input )
1294+ service = data .get ( 'service' )
1295+ if service == 0x52 :
1296+ # An Unconnected Send request
1297+ result += USINT .produce ( data .service )
1298+ result += EPATH .produce ( data .path )
1299+ result += USINT .produce ( data .priority )
1300+ result += USINT .produce ( data .timeout_ticks )
1301+ result += UINT .produce ( len ( data .request .input ))
1302+ result += octets_encode ( data .request .input )
12731303 if len ( data .request .input ) % 2 :
12741304 result += b'\x00 '
12751305 result += route_path .produce ( data .get ( 'route_path' , {} ))
1306+ elif service == 0x52 | 0x80 and data .get ( 'status' ):
1307+ # An Unconnected Send response w/ a non-zero status code
1308+ result += USINT .produce ( data .service )
1309+ result += b'\x00 ' # reserved
1310+ result += status .produce ( data )
1311+ #TODO: Support optional "path remaining" words
12761312 else :
12771313 # Not an Unconnected Send; just return the encapsulated request.input payload
12781314 result += octets_encode ( data .request .input )
@@ -1848,14 +1884,31 @@ class CIP( dfa ):
18481884 (0x0066 ,): unregister ,
18491885 (0x006f ,0x0070 ): send_data , # 0x006f (SendRRData) is default if CIP.send_data seen
18501886 }
1887+
18511888 def __init__ ( self , name = None , ** kwds ):
18521889 name = name or kwds .setdefault ( 'context' , self .__class__ .__name__ )
18531890
18541891 slct = octets_noop ( 'sel_CIP' )
18551892 for cmd ,cls in self .COMMAND_PARSERS .items ():
1856- slct [None ] = decide ( cls .__name__ ,
1857- state = cls ( limit = '...length' , terminal = True ),
1858- predicate = lambda path = None , data = None , cmd = cmd , ** kwds : data [path + '..command' ] in cmd )
1893+ slct [None ] = decide (
1894+ cls .__name__ ,
1895+ state = cls ( limit = '...length' , terminal = True ),
1896+ predicate = lambda path = None , data = None , cmd = cmd , ** kwds : data [path + '..command' ] in cmd
1897+ )
1898+
1899+ def unrec_CIP ( path = None , data = None , source = None , ** kwds ):
1900+ log .warning (
1901+ "{} -- unrecognized CIP command in data[{}]: 0x{:04x} in: {}" .format (
1902+ self , path + '..command' , data .get (path + '..command' , 0 ), enip_format ( data ))
1903+ )
1904+ return False
1905+
1906+ # Unrecognized CIP request code? Log a reasonable error
1907+ slct [True ] = decide (
1908+ 'unrec_CIP' ,
1909+ state = None ,
1910+ predicate = unrec_CIP ,
1911+ )
18591912 super ( CIP , self ).__init__ ( name = name , initial = slct , ** kwds )
18601913
18611914 @classmethod
0 commit comments