def unconnected_send(self, path, route_path=None, send_path=None, timeout=None, read_frag=None, write_frag=None): if route_path is None: # Default to the CPU in chassis (link 0), port 1 route_path = [{'link': 0, 'port': 1}] if send_path is None: # Default to the Connection Manager send_path = [{'class': 6}, {'instance': 1}] assert isinstance(path, list) data = cpppo.dotdict() data.enip = {} data.enip.session_handle = self.session data.enip.options = 0 data.enip.status = 0 data.enip.sender_context = {} data.enip.sender_context.input = bytearray([0x00] * 8) data.enip.CIP = {} data.enip.CIP.send_data = {} sd = data.enip.CIP.send_data sd.interface = 0 sd.timeout = 0 sd.CPF = {} sd.CPF.item = [cpppo.dotdict(), cpppo.dotdict()] sd.CPF.item[0].type_id = 0 sd.CPF.item[1].type_id = 178 sd.CPF.item[1].unconnected_send = {} us = sd.CPF.item[1].unconnected_send us.service = 82 us.status = 0 us.priority = 5 us.timeout_ticks = 157 us.path = {'segment': [cpppo.dotdict(d) for d in send_path]} us.route_path = {'segment': [cpppo.dotdict(d) for d in route_path]} us.request = {} us.request.path = {'segment': [cpppo.dotdict(d) for d in path]} if read_frag: us.request.read_frag = read_frag elif write_frag: us.request.write_frag = write_frag else: raise ValueError("Expected a Read/Write Tag [Fragmented] request") us.request.input = bytearray(logix.Logix.produce(us.request)) sd.input = bytearray(enip.CPF.produce(sd.CPF)) data.enip.input = bytearray(enip.CIP.produce(data.enip)) data.input = bytearray(enip.enip_encode(data.enip)) self.send(data.input, timeout=timeout) return data
def logix_remote( count, svraddr, kwargs ): try: time.sleep(.25) # Wait for server to be established # Confirm that a known Register encodes as expected data = cpppo.dotdict() data.enip = {} data.enip.options = 0 data.enip.session_handle = 0 data.enip.status = 0 data.enip.sender_context = {} data.enip.sender_context.input = bytearray( [0x00] * 8 ) data.enip.CIP = {} data.enip.CIP.register = {} data.enip.CIP.register.options = 0 data.enip.CIP.register.protocol_version = 1 data.enip.input = bytearray( enip.CIP.produce( data.enip )) data.input = bytearray( enip.enip_encode( data.enip )) log.normal( "Register Request: %r" % data ) assert bytes( data.input ) == rss_004_request # Try to Register a real session, followed by commands timeout = 5 begun = cpppo.timer() cli = client.client( host=svraddr[0], port=svraddr[1] ) assert cli.writable( timeout=timeout ) elapsed = cpppo.timer() - begun log.normal( "Client Connected in %7.3f/%7.3fs" % ( elapsed, timeout )) begun = cpppo.timer() with cli: cli.register( timeout=timeout ) data,elapsed = client.await_response( cli, timeout=timeout ) log.normal( "Client Register Rcvd %7.3f/%7.3fs: %r", elapsed, timeout, data ) assert data is not None and 'enip.CIP.register' in data, "Failed to receive Register response" assert data.enip.status == 0, "Register response indicates failure: %s" % data.enip.status # Establish the EtherNet/IP "session handle" used by all further requests cli.session = data.enip.session_handle start = cpppo.timer() with cli: for _ in range( count ): begun = cpppo.timer() cli.read( path=[{'symbolic': 'SCADA'}, {'element': 12}], elements=201, offset=2, timeout=timeout ) data,elapsed = client.await_response( cli, timeout=timeout ) log.normal( "Client ReadFrg. Rcvd %7.3f/%7.3fs: %r", elapsed, timeout, data ) duration = cpppo.timer() - start log.warning( "Client ReadFrg. Average %7.3f TPS (%7.3fs ea)." % ( count / duration, duration / count )) log.normal( "Signal shutdown w/ server.control in object %s", id( kwargs['server']['control'] )) finally: kwargs['server']['control'].done= True # Signal the server to terminate
def logix_remote( count, svraddr, kwargs ): try: time.sleep(.25) # Wait for server to be established # Confirm that a known Register encodes as expected data = cpppo.dotdict() data.enip = {} data.enip.options = 0 data.enip.session_handle = 0 data.enip.status = 0 data.enip.sender_context = {} data.enip.sender_context.input = bytearray( [0x00] * 8 ) data.enip.CIP = {} data.enip.CIP.register = {} data.enip.CIP.register.options = 0 data.enip.CIP.register.protocol_version = 1 data.enip.input = bytearray( enip.CIP.produce( data.enip )) data.input = bytearray( enip.enip_encode( data.enip )) log.normal( "Register Request: %r" % data ) assert bytes( data.input ) == rss_004_request # Try to Register a real session, followed by commands timeout = 5 begun = cpppo.timer() cli = client.client( host=svraddr[0], port=svraddr[1] ) assert cli.writable( timeout=timeout ) elapsed = cpppo.timer() - begun log.normal( "Client Connected in %7.3f/%7.3fs" % ( elapsed, timeout )) begun = cpppo.timer() with cli: cli.register( timeout=timeout ) data,elapsed = client.await( cli, timeout=timeout ) log.normal( "Client Register Rcvd %7.3f/%7.3fs: %r", elapsed, timeout, data ) assert data is not None and 'enip.CIP.register' in data, "Failed to receive Register response" assert data.enip.status == 0, "Register response indicates failure: %s" % data.enip.status # Establish the EtherNet/IP "session handle" used by all further requests cli.session = data.enip.session_handle start = cpppo.timer() with cli: for _ in range( count ): begun = cpppo.timer() cli.read( path=[{'symbolic': 'SCADA'}, {'element': 12}], elements=201, offset=2, timeout=timeout ) data,elapsed = client.await( cli, timeout=timeout ) log.normal( "Client ReadFrg. Rcvd %7.3f/%7.3fs: %r", elapsed, timeout, data ) duration = cpppo.timer() - start log.warning( "Client ReadFrg. Average %7.3f TPS (%7.3fs ea)." % ( count / duration, duration / count )) log.normal( "Signal shutdown w/ server.control in object %s", id( kwargs['server']['control'] )) finally: kwargs['server']['control'].done= True # Signal the server to terminate
def unconnected_send( self, path, route_path=None, send_path=None, timeout=None, read_frag=None, write_frag=None ): if route_path is None: # Default to the CPU in chassis (link 0), port 1 route_path = [{'link': 0, 'port': 1}] if send_path is None: # Default to the Connection Manager send_path = [{'class': 6}, {'instance': 1}] assert isinstance( path, list ) data = cpppo.dotdict() data.enip = {} data.enip.session_handle= self.session data.enip.options = 0 data.enip.status = 0 data.enip.sender_context= {} data.enip.sender_context.input = bytearray( [0x00] * 8 ) data.enip.CIP = {} data.enip.CIP.send_data = {} sd = data.enip.CIP.send_data sd.interface = 0 sd.timeout = 0 sd.CPF = {} sd.CPF.item = [ cpppo.dotdict(), cpppo.dotdict() ] sd.CPF.item[0].type_id = 0 sd.CPF.item[1].type_id = 178 sd.CPF.item[1].unconnected_send = {} us = sd.CPF.item[1].unconnected_send us.service = 82 us.status = 0 us.priority = 5 us.timeout_ticks = 157 us.path = { 'segment': [ cpppo.dotdict( d ) for d in send_path ]} us.route_path = { 'segment': [ cpppo.dotdict( d ) for d in route_path ]} us.request = {} us.request.path = { 'segment': [ cpppo.dotdict( d ) for d in path ]} if read_frag: us.request.read_frag= read_frag elif write_frag: us.request.write_frag= write_frag else: raise ValueError( "Expected a Read/Write Tag [Fragmented] request" ) us.request.input = bytearray( logix.Logix.produce( us.request )) sd.input = bytearray( enip.CPF.produce( sd.CPF )) data.enip.input = bytearray( enip.CIP.produce( data.enip )) data.input = bytearray( enip.enip_encode( data.enip )) self.send( data.input, timeout=timeout ) return data
def unconnected_send(self, request, route_path=None, send_path=None, timeout=None): if route_path is None: # Default to the CPU in chassis (link 0), port 1 route_path = [{"link": 0, "port": 1}] if send_path is None: # Default to the Connection Manager send_path = [{"class": 6}, {"instance": 1}] assert isinstance(request, dict) data = cpppo.dotdict() data.enip = {} data.enip.session_handle = self.session data.enip.options = 0 data.enip.status = 0 data.enip.sender_context = {} data.enip.sender_context.input = bytearray([0x00] * 8) data.enip.CIP = {} data.enip.CIP.send_data = {} sd = data.enip.CIP.send_data sd.interface = 0 sd.timeout = 0 sd.CPF = {} sd.CPF.item = [cpppo.dotdict(), cpppo.dotdict()] sd.CPF.item[0].type_id = 0 sd.CPF.item[1].type_id = 178 sd.CPF.item[1].unconnected_send = {} us = sd.CPF.item[1].unconnected_send us.service = 82 us.status = 0 us.priority = 5 us.timeout_ticks = 157 us.path = {"segment": [cpppo.dotdict(d) for d in send_path]} us.route_path = {"segment": [cpppo.dotdict(d) for d in route_path]} us.request = request log.detail("Client Unconnected Send: %s", enip.enip_format(data)) us.request.input = bytearray(logix.Logix.produce(us.request)) sd.input = bytearray(enip.CPF.produce(sd.CPF)) data.enip.input = bytearray(enip.CIP.produce(data.enip)) data.input = bytearray(enip.enip_encode(data.enip)) self.send(data.input, timeout=timeout) return data
def register(self, timeout=None): data = cpppo.dotdict() data.enip = {} data.enip.session_handle = 0 data.enip.options = 0 data.enip.status = 0 data.enip.sender_context = {} data.enip.sender_context.input = bytearray([0x00] * 8) data.enip.CIP = {} data.enip.CIP.register = {} data.enip.CIP.register.options = 0 data.enip.CIP.register.protocol_version = 1 data.enip.input = bytearray(enip.CIP.produce(data.enip)) data.input = bytearray(enip.enip_encode(data.enip)) self.send(data.input, timeout=timeout) return data
def logix_remote( count, svraddr, kwargs ): time.sleep(.25) data = cpppo.dotdict() data.enip = {} data.enip.options = 0 data.enip.session_handle = 0 data.enip.status = 0 data.enip.sender_context = {} data.enip.sender_context.input = bytearray( [0x00] * 8 ) #array.array( cpppo.type_bytes_array_symbol, "\x00" * 8 ) data.enip.CIP = {} data.enip.CIP.register = {} data.enip.CIP.register.options = 0 data.enip.CIP.register.protocol_version = 1 data.enip.input = bytearray( enip.CIP.produce( data.enip )) data.input = bytearray( enip.enip_encode( data.enip )) log.normal( "Register Request: %r" % data ) assert bytes( data.input ) == rss_004_request timeout = 5 begun = cpppo.timer() cli = client.client( host=svraddr[0], port=svraddr[1] ) assert cli.writable( timeout=timeout ) elapsed = cpppo.timer() - begun log.normal( "Client Connected in %7.3f/%7.3fs" % ( elapsed, timeout )) begun = cpppo.timer() request = cli.register( timeout=timeout ) elapsed = cpppo.timer() - begun log.normal( "Client Register Sent %7.3f/%7.3fs: %r" % ( elapsed, timeout, request )) for data in cli: elapsed = cpppo.timer() - begun log.detail( "Client Register Resp %7.3f/%7.3fs: %r" % ( elapsed, timeout, data )) if data is None: if elapsed <= timeout: cli.readable( timeout=timeout - elapsed ) continue break elapsed = cpppo.timer() - begun log.normal( "Client Register Rcvd %7.3f/%7.3fs: %r" % ( elapsed, timeout, data )) assert data is not None and 'enip.CIP.register' in data, "Failed to receive Register response" assert data.enip.status == 0, "Register response indicates failure: %s" % data.enip.status cli.session = data.enip.session_handle start = cpppo.timer() for _ in range( count ): begun = cpppo.timer() request = cli.read( path=[{'symbolic': 'SCADA'}, {'element': 12}], elements=1, offset=0, timeout=timeout ) elapsed = cpppo.timer() - begun log.normal( "Client ReadFrg. Sent %7.3f/%7.3fs: %r" % ( elapsed, timeout, request )) for data in cli: elapsed = cpppo.timer() - begun log.detail( "Client ReadFrg. Resp %7.3f/%7.3fs: %r" % ( elapsed, timeout, data )) if data is None: if elapsed <= timeout: cli.readable( timeout=timeout - elapsed ) continue break elapsed = cpppo.timer() - begun log.normal( "Client ReadFrg. Rcvd %7.3f/%7.3fs: %r" % ( elapsed, timeout, data )) duration = cpppo.timer() - start log.warning( "Client ReadFrg. Average %7.3f TPS (%7.3fs ea)." % ( count / duration, duration / count )) kwargs['server'].control.done= True
def logix_remote(count, svraddr, kwargs): time.sleep(.25) data = cpppo.dotdict() data.enip = {} data.enip.options = 0 data.enip.session_handle = 0 data.enip.status = 0 data.enip.sender_context = {} data.enip.sender_context.input = bytearray([0x00] * 8) #array.array( cpppo.type_bytes_array_symbol, "\x00" * 8 ) data.enip.CIP = {} data.enip.CIP.register = {} data.enip.CIP.register.options = 0 data.enip.CIP.register.protocol_version = 1 data.enip.input = bytearray(enip.CIP.produce(data.enip)) data.input = bytearray(enip.enip_encode(data.enip)) log.normal("Register Request: %r" % data) assert bytes(data.input) == rss_004_request timeout = 5 begun = cpppo.timer() cli = client.client(host=svraddr[0], port=svraddr[1]) assert cli.writable(timeout=timeout) elapsed = cpppo.timer() - begun log.normal("Client Connected in %7.3f/%7.3fs" % (elapsed, timeout)) begun = cpppo.timer() request = cli.register(timeout=timeout) elapsed = cpppo.timer() - begun log.normal("Client Register Sent %7.3f/%7.3fs: %r" % (elapsed, timeout, request)) for data in cli: elapsed = cpppo.timer() - begun log.detail("Client Register Resp %7.3f/%7.3fs: %r" % (elapsed, timeout, data)) if data is None: if elapsed <= timeout: cli.readable(timeout=timeout - elapsed) continue break elapsed = cpppo.timer() - begun log.normal("Client Register Rcvd %7.3f/%7.3fs: %r" % (elapsed, timeout, data)) assert data is not None and 'enip.CIP.register' in data, "Failed to receive Register response" assert data.enip.status == 0, "Register response indicates failure: %s" % data.enip.status cli.session = data.enip.session_handle start = cpppo.timer() for _ in range(count): begun = cpppo.timer() request = cli.read(path=[{ 'symbolic': 'SCADA' }, { 'element': 12 }], elements=1, offset=0, timeout=timeout) elapsed = cpppo.timer() - begun log.normal("Client ReadFrg. Sent %7.3f/%7.3fs: %r" % (elapsed, timeout, request)) for data in cli: elapsed = cpppo.timer() - begun log.detail("Client ReadFrg. Resp %7.3f/%7.3fs: %r" % (elapsed, timeout, data)) if data is None: if elapsed <= timeout: cli.readable(timeout=timeout - elapsed) continue break elapsed = cpppo.timer() - begun log.normal("Client ReadFrg. Rcvd %7.3f/%7.3fs: %r" % (elapsed, timeout, data)) duration = cpppo.timer() - start log.warning("Client ReadFrg. Average %7.3f TPS (%7.3fs ea)." % (count / duration, duration / count)) kwargs['server'].control.done = True
def test_CIP_HART(repeat=1): """HART protocol enip CIP messages """ enip.lookup_reset() # Flush out any existing CIP Objects for a fresh start ENIP = enip.enip_machine(context='enip') CIP = enip.CIP() # We'll use a HART Message Router, to handle its expanded porfolio of commands MR = HART(instance_id=1) for pkt, tst in client.recycle(CIP_HART_tests, times=repeat): # Parse just the CIP portion following the EtherNet/IP encapsulation header data = cpppo.dotdict() source = cpppo.chainable(pkt) with ENIP as machine: for i, (m, s) in enumerate(machine.run(source=source, data=data)): log.detail("%s #%3d -> %10.10s; next byte %3d: %-10.10r: %r", machine.name_centered(), i, s, source.sent, source.peek(), data) # In a real protocol implementation, an empty header (EOF with no input at all) is # acceptable; it indicates a session closed by the client. if not data: log.normal("EtherNet/IP Request: Empty (session terminated): %s", enip.enip_format(data)) continue if log.isEnabledFor(logging.NORMAL): log.normal("EtherNet/IP Request: %s", enip.enip_format(data)) # Parse the encapsulated .input with CIP as machine: for i, (m, s) in enumerate( machine.run(path='enip', source=cpppo.peekable( data.enip.get('input', b'')), data=data)): log.detail("%s #%3d -> %10.10s; next byte %3d: %-10.10r: %r", machine.name_centered(), i, s, source.sent, source.peek(), data) if log.isEnabledFor(logging.NORMAL): log.normal("EtherNet/IP CIP Request: %s", enip.enip_format(data)) # Assume the request in the CIP's CPF items are HART requests. # Now, parse the encapsulated message(s). We'll assume it is destined for a HART Object. if 'enip.CIP.send_data' in data: for item in data.enip.CIP.send_data.CPF.item: if 'unconnected_send.request' in item: # An Unconnected Send that contained an encapsulated request (ie. not just a Get # Attribute All) with MR.parser as machine: if log.isEnabledFor(logging.NORMAL): log.normal( "Parsing %3d bytes using %s.parser, from %s", len(item.unconnected_send.request.input), MR, enip.enip_format(item)) # Parse the unconnected_send.request.input octets, putting parsed items into the # same request context for i, (m, s) in enumerate( machine.run( source=cpppo.peekable( item.unconnected_send.request.input), data=item.unconnected_send.request)): log.detail( "%s #%3d -> %10.10s; next byte %3d: %-10.10r: %r", machine.name_centered(), i, s, source.sent, source.peek(), data) # Post-processing of some parsed items is only performed after lock released! if log.isEnabledFor(logging.NORMAL): log.normal( "Parsed %3d bytes using %s.parser, into %s", len(item.unconnected_send.request.input), MR, enip.enip_format(data)) try: for k, v in tst.items(): assert data[k] == v, ("data[%r] == %r\n" "expected: %r" % (k, data[k], v)) except: log.warning("%r not in data, or != %r: %s", k, v, enip.enip_format(data)) raise # Ensure that we can get the original EtherNet/IP CIP back for k in list(data.keys()): if k.endswith('input') and 'sender_context' not in k: log.detail("del data[%r]", k) del data[k] try: # First reconstruct any SendRRData CPF items, containing encapsulated requests/responses if 'enip.CIP.send_data' in data: cpf = data.enip.CIP.send_data for item in cpf.CPF.item: if 'unconnected_send' in item: item.unconnected_send.request.input = bytearray( MR.produce(item.unconnected_send.request)) log.normal("Produce HART message from: %r", item.unconnected_send.request) # Next, reconstruct the CIP Register, ListIdentity, ListServices, or SendRRData. The CIP.produce must # be provided the EtherNet/IP header, because it contains data (such as .command) # relevant to interpreting the .CIP... contents. data.enip.input = bytearray(enip.CIP.produce(data.enip)) # And finally the EtherNet/IP encapsulation itself data.input = bytearray(enip.enip_encode(data.enip)) log.detail("EtherNet/IP CIP Request produced payload: %r", bytes(data.input)) assert data.input == pkt, "original:\n" + hexdump( pkt) + "\nproduced:\n" + hexdump(data.input) except Exception as exc: log.warning( "Exception %s; Invalid packet produced from EtherNet/IP CIP data: %s", exc, enip.enip_format(data)) raise