pjkundert / cpppo

Communications Protocol Python Parser and Originator -- EtherNet/IP CIP
https://hardconsulting.com/products/6-cpppo-enip-api
Other
328 stars 108 forks source link

Sending SINT write request using client results in error #109

Open mgomez12 opened 2 years ago

mgomez12 commented 2 years ago

I'm attempting to use the client to send EIP requests, but I had an issue pop up when the data type is an SINT. Essentially, I send the operation @0x64/1/0x05 = (SINT)-1, which fails with the following trace:

/usr/local/lib/python3.8/dist-packages/cpppo/server/enip/get_attribute.py:431: in read
    for val,(sts,(att,typ,uni)) in reader:
/usr/local/lib/python3.8/dist-packages/cpppo/server/enip/get_attribute.py:618: in read_details
    for i,(idx,dsc,req,rpy,sts,val) in enumerate( connection.operate(
/usr/local/lib/python3.8/dist-packages/cpppo/server/enip/client.py:1749: in operate
    for idx,dsc,req,rpy,sts,val in harvested:
/usr/local/lib/python3.8/dist-packages/cpppo/server/enip/client.py:1599: in pipeline
    iss         = next( issuer )
/usr/local/lib/python3.8/dist-packages/cpppo/server/enip/client.py:1348: in issue
    req         = self.set_attribute_single( timeout=timeout, send=not multiple, **op )
/usr/local/lib/python3.8/dist-packages/cpppo/server/enip/client.py:903: in set_attribute_sing
e
    self.req_send(
/usr/local/lib/python3.8/dist-packages/cpppo/server/enip/client.py:1188: in req_send
    return self.unconnected_send( request, **kwds )
/usr/local/lib/python3.8/dist-packages/cpppo/server/enip/client.py:1074: in unconnected_send
    us.request.input= bytearray( dialect.produce( us.request )) # eg. logix.Logix
/usr/local/lib/python3.8/dist-packages/cpppo/server/enip/logix.py:539: in produce
    result              = super( Logix, cls ).produce( data )
/usr/local/lib/python3.8/dist-packages/cpppo/server/enip/device.py:1797: in produce
    result              = super( Message_Router, cls ).produce( data )
/usr/local/lib/python3.8/dist-packages/cpppo/server/enip/device.py:1235: in produce
    result             += typed_data.produce(   data.set_attribute_single,
/usr/local/lib/python3.8/dist-packages/cpppo/server/enip/parser.py:2103: in produce
    result             += b''.join( map( producer, data.get( 'data' )))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

cls = <class 'cpppo.server.enip.parser.USINT'>, value = -1

    @classmethod
    def produce( cls, value ):
>       return struct.pack( cls.struct_format, value )
E       struct.error: ubyte format requires 0 <= number <= 255

/usr/local/lib/python3.8/dist-packages/cpppo/server/enip/parser.py:175: error

This is was unexpected because if the data was not valid for the specific type to begin with, it would have been caught with in the earlier int_validate step, however it doesn't fail until the produce function is called.

After digging a little more, I came across the following:

https://github.com/pjkundert/cpppo/blob/4c217b6c06b88bede3888cc5ea2731f271a95086/server/enip/client.py#L888

What I'm understanding is that if the data type is more than a byte long (aka USINT or SINT), it converts it to a byte array of USINTs that it can later send. However, since the data isn't getting formatted, the SINT retains its negative value, which then causes the issue down the line when it tries to pack the data as a USINT. This seems like a quick fix (just removing the SINT check from the quoted line), but I was wondering if there was an intended reason for this behavior since i don't think it's been mentioned before.