Messaging#

Addressing (EPATH), Message Router requests/replies (explicit messaging, including Multiple_Service_Packet), Connection Manager services (Forward_Open/Forward_Close/Unconnected_Send), and Class 0/1 cyclic I/O.

CIP EPATH segment codecs.

class icspacket.proto.cip.epath.EPATH(*segments: PortSegment | LogicalSegment | NetworkSegment | DataSegment | ElectronicKeySegment | SymbolicSegment)#

A sequence of CIP path segments.

padded follows the normal CIP path representation: each segment ends on a 16-bit boundary. packed is accepted as a convenience alias for the unpadded representation used by a few encapsulating services.

classmethod from_bytes(data: bytes, *, padded: bool = True, packed: bool | None = None, length: int | None = None) Self#

Decode a padded or packed EPATH byte string.

to_bytes(*, padded: bool = True, packed: bool | None = None) bytes#

Encode the path as padded or packed EPATH bytes.

property word_length: int#

Length of the padded EPATH in 16-bit words.

class icspacket.proto.cip.epath.DataSegment(*, tag: int = 128, words: int = 0, data: bytes = b'')#

Simple data segment (See CIP Volume 1, §3.5.7).

words counts data in 16-bit words, so data is always word-aligned. A trailing zero octet is indistinguishable from a genuine data byte once round-tripped - new() pads an odd-length input itself, matching the wire format’s own inability to carry the original, pre-padding byte count.

static new(data: bytes) DataSegment#

Builds a data segment, padding odd-length input to a whole word.

class icspacket.proto.cip.epath.ElectronicKeySegment(*, tag: int = 52, key_format: int = 4, vendor_id: int = 0, device_type: int = 0, product_code: int = 0, major_revision: int = 0, minor_revision: int = 0)#

Electronic key segment (See CIP Volume 1, §3.5.8).

class icspacket.proto.cip.epath.LogicalSegment(s_type: int = 1, logical_type: LogicalType = LogicalType.CLASS, format_bits: int = 0, value: int = 0)#

Logical segment for class, instance, member, and related identifiers.

(See CIP Volume 1, §3.5.3.) format_bits selects value’s wire width (0/1/2 for 8-/16-/32-bit). Use new() or one of the per-kind factories below instead of setting format_bits directly - they derive it from a plain width in octets, or pick the narrowest fit automatically when width is omitted.

static attribute_id(value: int, width: int | None = None) LogicalSegment#

Construct an Attribute ID logical segment.

static class_id(value: int, width: int | None = None) LogicalSegment#

Construct a Class ID logical segment.

static connection_point(value: int, width: int | None = None) LogicalSegment#

Construct a Connection Point logical segment.

static instance_id(value: int, width: int | None = None) LogicalSegment#

Construct an Instance ID logical segment.

static member_id(value: int, width: int | None = None) LogicalSegment#

Construct a Member ID logical segment.

static new(kind: LogicalType | int, value: int, width: int | None = None) LogicalSegment#

Builds a logical segment, auto-selecting the narrowest value width.

static service_id(value: int, width: int | None = None) LogicalSegment#

Construct a Service ID logical segment.

static special(value: int, width: int | None = None) LogicalSegment#

Construct a Special logical segment.

property width: int#

The width of value on the wire, in octets.

class icspacket.proto.cip.epath.LogicalType(*values)#

Logical segment identifier (See CIP Volume 1, §3.5.3).

class icspacket.proto.cip.epath.NetworkSegment(s_type: int = 2, subtype: int = 0, words: int | None = None, data: bytes = b'')#

Network segment with a subtype and opaque network-specific data.

(See CIP Volume 1, §3.5.4.) A subtype below 0x10 (“fixed”) always carries exactly one data octet with no length prefix; 0x10 and above (“variable”) is preceded by a words octet counting the trailing data in 16-bit words.

static new(subtype: int, data: bytes = b'') NetworkSegment#

Builds a network segment from a subtype and its payload.

class icspacket.proto.cip.epath.PortSegment(s_type: int = 0, ext_link: bool = False, port_low: int = 0, ext_len: int | None = None, ext_port: int | None = None, link_address: bytes = b'\x00')#

Port segment, including short and extended link addresses.

(See CIP Volume 1, §3.5.5.2.) port_low equal to 0x0F is a reserved marker meaning the real port number is carried in the following 16-bit ext_port field instead of these 4 bits; ext_link marks a length-prefixed variable link address instead of the fixed single octet.

This class does not subclass Segment – a @bitfield model can never be a frozen dataclass, and Segment is frozen – but it carries the same TYPE_ID tag EPATH dispatches on, and StructDefMixin gives it to_bytes()/ from_bytes() directly, with no separate encode/decode step.

static new(port_id: int, link_address: int | bytes = 0) PortSegment#

Builds a port segment from a (port_id, link_address) pair.

property port_id: int#

The logical port number, reassembled from port_low/ext_port.

class icspacket.proto.cip.epath.SymbolicSegment(*, tag: int = 145, value: bytes = b'')#

ANSI extended symbolic segment (See CIP Volume 1, §3.5.6).

static new(symbol: str | bytes) SymbolicSegment#

Builds a symbolic segment from a str or raw bytes name.

property symbol: str | bytes#

The symbol name, decoded as ASCII when possible.

icspacket.proto.cip.epath.decode_epath(data: bytes, *, padded: bool = True, packed: bool | None = None, length: int | None = None) EPATH#

Decode an EPATH byte string into segment objects.

icspacket.proto.cip.epath.encode_epath(*segments: PortSegment | LogicalSegment | NetworkSegment | DataSegment | ElectronicKeySegment | SymbolicSegment, padded: bool = True, packed: bool | None = None) bytes#

Encode segments into an EPATH byte string.

[ODVA CIP Vol 1] CIP Message Router request and response codecs.

The layouts implemented here are defined according to CIP Volume 1, clause 2-4, Tables 2-4.1 and 2-4.2.

class icspacket.proto.cip.msgrouter.MessageRouterRequest(*, service: int = 0, path_size: int = 0, path: bytes = b'', request_data: bytes = b'')[source]#

A Message Router request (See CIP Vol 1, clause 2-4, Table 2-4.1).

service: int = 0#

Numeric code naming the CIP service this request invokes.

path_size: int = 0#

Length of path, counted in 16-bit words.

path: bytes = b''#

Raw, padded EPATH bytes naming the request’s target. Use new()/epath for the rich EPATH view.

request_data: bytes = b''#

Service-specific request data following the request path.

property epath: EPATH#

path, parsed into an EPATH.

classmethod new(service: int, path: EPATH | bytes | bytearray | Iterable[PortSegment | LogicalSegment | NetworkSegment | SymbolicSegment | DataSegment | ElectronicKeySegment] = b'', request_data: bytes = b'') Self[source]#

Builds a request, accepting a rich path (EPATH, raw bytes, or an iterable of segments) instead of pre-encoded, word-counted bytes.

class icspacket.proto.cip.msgrouter.MessageRouterResponse(*, reply_service: int = 0, reserved: int = 0, general_status: int = 0, additional_status: list[int] = <factory>, response_data: bytes = b'')[source]#

A Message Router response (See CIP Vol 1, clause 2-4, Table 2-4.2).

reply_service: int = 0#

Service code being answered, echoed back with its top reply bit set.

reserved: int = 0#

Byte reserved for future use; must always be encoded as zero.

general_status: int = 0#

Outcome of the request; compare against GeneralStatus values.

additional_status: list[int]#

Extra 16-bit status words supplying detail beyond the single general-status byte, when the responder provides any.

response_data: bytes = b''#

Service-specific reply data following the fixed status fields.

property service: int#

Original request service code with the reply bit removed.

property is_success: bool#

Whether the response general status is Success.

classmethod new(service: int, general_status: GeneralStatus | int = GeneralStatus.SUCCESS, additional_status: Iterable[int] = (), response_data: bytes = b'') Self[source]#

Builds a response, deriving reply_service from service (i.e. service with the reply bit set).

class icspacket.proto.cip.msgrouter.MultipleServicePacket(*, offsets: list[int] = <factory>, payload: bytes = b'')[source]#

Helper for common service 0x0A (See CIP Vol 1, clause A-4.10, Tables A-4.17 and A-4.18).

Each embedded Message Router request/reply carries no length of its own on the wire, so payload cannot be split back into individual entries by a declarative field alone; use build()/decode_requests()/decode_replies() to move between payload and rich request/reply lists.

offsets: list[int]#

Byte offset of each embedded service, measured from the start of this packet. Populated by build().

payload: bytes = b''#

Raw, concatenated embedded-service bytes. Populated by build(); split back into entries via decode_requests()/ decode_replies().

build(requests: Iterable[MessageRouterRequest]) bytes[source]#

Encodes requests, recomputing offsets/ payload, and returns the fully encoded packet.

request(path: EPATH | bytes | bytearray | Iterable[PortSegment | LogicalSegment | NetworkSegment | SymbolicSegment | DataSegment | ElectronicKeySegment] = b'') MessageRouterRequest[source]#

Wrap this packet in a Message Router service 0x0A request.

decode_requests() list[MessageRouterRequest][source]#

Splits payload back into individual embedded requests.

decode_replies() list[MessageRouterResponse][source]#

Splits payload back into individual embedded replies (See CIP Volume 1, clause A-4.10.3, Table A-4.18).

[ODVA CIP Vol 1] CIP Connection Manager service codecs.

The request and response layouts are defined according to CIP Volume 1, clause 3-5.5, Tables 3-5.8 through 3-5.24. Connection Manager fields are little-endian and all EPATHs are represented as an even number of octets.

class icspacket.proto.cip.connmgr.ForwardCloseRequest(*, priority: int = 0, timeout_ticks: int = 0, connection_serial_number: int = 0, originator_vendor_id: int = 0, originator_serial_number: int = 0, connection_path_size: int = 0, reserved: int = 0, connection_path: bytes = b'')[source]#

Builds and parses the body of a Forward_Close request used to tear down a previously opened CIP connection (See CIP Vol 1, clause 3-5.5.3, Table 3-5.19).

priority: int = 0#

Priority/Time_tick value that, together with timeout_ticks, determines how long the target waits before this request times out.

timeout_ticks: int = 0#

Tick count that, together with priority’s Time_tick value, determines how long the target waits before this request times out.

connection_serial_number: int = 0#

Serial number identifying the specific connection instance being closed.

originator_vendor_id: int = 0#

Vendor ID identifying the originating device.

originator_serial_number: int = 0#

Serial number identifying the originating device.

connection_path_size: int = 0#

Length of connection_path, counted in 16-bit words.

reserved: int = 0#

Byte reserved for future use; must always be encoded as zero.

connection_path: bytes = b''#

Raw, padded EPATH bytes identifying the route to close. Use new()/connection_epath for the rich EPATH view.

class icspacket.proto.cip.connmgr.ForwardCloseResponse(*, connection_serial_number: int = 0, originator_vendor_id: int = 0, originator_serial_number: int = 0, application_reply_data_size: int = 0, reserved: int = 0, application_reply_data: bytes = b'')[source]#

Builds and parses the body of a successful Forward_Close reply (See CIP Vol 1, clause 3-5.5.3, Table 3-5.20).

connection_serial_number: int = 0#

Echo of the connection_serial_number sent in the matching Forward_Close request.

originator_vendor_id: int = 0#

Echo of the originator_vendor_id sent in the matching Forward_Close request.

originator_serial_number: int = 0#

Echo of the originator_serial_number sent in the matching Forward_Close request.

application_reply_data_size: int = 0#

Length of application_reply_data, counted in 16-bit words.

reserved: int = 0#

Byte reserved for future use; must always be encoded as zero.

application_reply_data: bytes = b''#

Application-specific reply data following the fixed header. Use new() to pad odd-length input automatically.

class icspacket.proto.cip.connmgr.ForwardOpenRequest(*, priority: int = 0, timeout_ticks: int = 0, o_to_t_connection_id: int = 0, t_to_o_connection_id: int = 0, connection_serial_number: int = 0, originator_vendor_id: int = 0, originator_serial_number: int = 0, timeout_multiplier: int = 0, reserved_timeout: list[int] = <factory>, o_to_t_rpi: int = 0, o_to_t_parameters: NetworkConnectionParameters = <factory>, t_to_o_rpi: int = 0, t_to_o_parameters: NetworkConnectionParameters = <factory>, transport_trigger: int = 0, connection_path_size: int = 0, connection_path: bytes = b'')[source]#

Builds and parses the standard-size Forward_Open request body used to open a new CIP connection (See CIP Vol 1, clause 3-5.5.2, Table 3-5.16).

o_to_t_parameters: NetworkConnectionParameters#

Network Connection Parameters bitfield describing the originator-to-target data flow.

t_to_o_rpi: int = 0#

Requested packet interval, in microseconds, for data flowing from target to originator.

t_to_o_parameters: NetworkConnectionParameters#

Network Connection Parameters bitfield describing the target-to-originator data flow.

transport_trigger: int = 0#

Byte selecting this connection’s transport class together with its production trigger behavior.

connection_path_size: int = 0#

Length of connection_path, counted in 16-bit words.

connection_path: bytes = b''#

Raw, padded EPATH bytes identifying the route to establish. Use new()/connection_epath for the rich EPATH view.

class icspacket.proto.cip.connmgr.ForwardOpenResponse(*, o_to_t_connection_id: int = 0, t_to_o_connection_id: int = 0, connection_serial_number: int = 0, originator_vendor_id: int = 0, originator_serial_number: int = 0, o_to_t_api: int = 0, t_to_o_api: int = 0, application_reply_data_size: int = 0, reserved: int = 0, application_reply_data: bytes = b'')[source]#

Builds and parses the body of a successful Forward_Open or Large_Forward_Open reply, shared by both request sizes (See CIP Vol 1, clause 3-5.5.2, Table 3-5.17).

o_to_t_connection_id: int = 0#

Connection ID the target assigned to the originator-to-target direction; the target’s Consumed Connection ID.

t_to_o_connection_id: int = 0#

Connection ID the target assigned to the target-to-originator direction; the target’s Produced Connection ID.

connection_serial_number: int = 0#

Echo of the connection_serial_number sent in the matching Forward_Open request.

originator_vendor_id: int = 0#

Echo of the originator_vendor_id sent in the matching Forward_Open request.

originator_serial_number: int = 0#

Echo of the originator_serial_number sent in the matching Forward_Open request.

o_to_t_api: int = 0#

Actual packet rate, in microseconds, that will be used from originator to target.

t_to_o_api: int = 0#

Actual packet rate, in microseconds, that will be used from target to originator.

application_reply_data_size: int = 0#

Length of application_reply_data, counted in 16-bit words.

reserved: int = 0#

Byte reserved for future use; must always be encoded as zero.

application_reply_data: bytes = b''#

Application-specific reply data following the fixed header. Use new() to pad odd-length input automatically.

class icspacket.proto.cip.connmgr.LargeForwardOpenRequest(*, priority: int = 0, timeout_ticks: int = 0, o_to_t_connection_id: int = 0, t_to_o_connection_id: int = 0, connection_serial_number: int = 0, originator_vendor_id: int = 0, originator_serial_number: int = 0, timeout_multiplier: int = 0, reserved_timeout: list[int] = <factory>, o_to_t_rpi: int = 0, o_to_t_parameters: LargeNetworkConnectionParameters = <factory>, t_to_o_rpi: int = 0, t_to_o_parameters: LargeNetworkConnectionParameters = <factory>, transport_trigger: int = 0, connection_path_size: int = 0, connection_path: bytes = b'')[source]#

Builds and parses the Forward_Open request body used when the connection needs a larger connection-size range than the standard format allows (See CIP Vol 1, clause 3-5.5.2, Table 3-5.16).

o_to_t_parameters: LargeNetworkConnectionParameters#

Large-format Network Connection Parameters bitfield describing the originator-to-target data flow.

t_to_o_rpi: int = 0#

Requested packet interval, in microseconds, for data flowing from target to originator.

t_to_o_parameters: LargeNetworkConnectionParameters#

Large-format Network Connection Parameters bitfield describing the target-to-originator data flow.

transport_trigger: int = 0#

Byte selecting this connection’s transport class together with its production trigger behavior.

connection_path_size: int = 0#

Length of connection_path, counted in 16-bit words.

connection_path: bytes = b''#

Raw, padded EPATH bytes identifying the route to establish. Use new()/connection_epath for the rich EPATH view.

class icspacket.proto.cip.connmgr.LargeNetworkConnectionParameters(redundant_owner: bool = False, connection_type: NetworkConnectionType = NetworkConnectionType.NULL, reserved_high: int = 0, priority: int = 0, variable: bool = False, reserved_low: int = 0, connection_size: int = 0)[source]#

Wire layout for the UDINT-sized form of Network Connection Parameters, as carried by a Large_Forward_Open (See CIP Vol 1, clause 3-5.5.1.1, Table 3-5.9).

redundant_owner: bool = False#

True allows this connection to be held open by more than one owner at the same time.

connection_type: NetworkConnectionType = 0#

Selects the NetworkConnectionType value (Null, Multicast, or Point to Point) for this connection.

reserved_high: int = 0#

High-order bits reserved for future use; must always be encoded as zero.

priority: int = 0#

Connection priority level: one of Low, High, Scheduled, or Urgent.

variable: bool = False#

Data-size framing mode for the connection: False for fixed size, True for variable size.

reserved_low: int = 0#

Low-order bits reserved for future use; must always be encoded as zero.

connection_size: int = 0#

Largest data size, in bytes, that this connection will carry.

class icspacket.proto.cip.connmgr.NetworkConnectionParameters(redundant_owner: bool = False, connection_type: NetworkConnectionType = NetworkConnectionType.NULL, reserved: bool = False, priority: int = 0, variable: bool = False, connection_size: int = 0)[source]#

Wire layout for the UINT-sized form of Network Connection Parameters, as carried by a standard Forward_Open (See CIP Vol 1, clause 3-5.5.1.1, Table 3-5.8).

redundant_owner: bool = False#

True allows this connection to be held open by more than one owner at the same time.

connection_type: NetworkConnectionType = 0#

Selects the NetworkConnectionType value (Null, Multicast, or Point to Point) for this connection.

reserved: bool = False#

Reserved for future use; must always be encoded as zero.

priority: int = 0#

Connection priority level: one of Low, High, Scheduled, or Urgent.

variable: bool = False#

Data-size framing mode for the connection: False for fixed size, True for variable size.

connection_size: int = 0#

Largest data size, in bytes, that this connection will carry.

class icspacket.proto.cip.connmgr.NetworkConnectionType(*values)[source]#

Values this library accepts for the Connection Type subfield packed into Network Connection Parameters (See CIP Vol 1, clause 3-5.5.1.1, Tables 3-5.8 and 3-5.9).

class icspacket.proto.cip.connmgr.UnconnectedSendRequest(*, priority: int = 0, timeout_ticks: int = 0, message_request_size: int = 0, message_request: bytes = b'', route_path_size: int = 0, reserved: int = 0, route_path: bytes = b'')[source]#

Builds and parses the body of an Unconnected_Send request, which wraps another Message Router request for routing over an unconnected path (See CIP Vol 1, clause 3-5.5.4, Table 3-5.22).

priority: int = 0#

Priority/Time_tick value that, together with timeout_ticks, determines how long the target waits before this request times out.

timeout_ticks: int = 0#

Tick count that, together with priority’s Time_tick value, determines how long the target waits before this request times out.

message_request_size: int = 0#

Length, in bytes, of message_request (not 16-bit words, as route_path_size below counts).

message_request: bytes = b''#

Embedded Message Router request bytes; a pad byte follows on the wire whenever message_request_size is odd.

route_path_size: int = 0#

Length of route_path, counted in 16-bit words.

reserved: int = 0#

Byte reserved for future use; must always be encoded as zero.

route_path: bytes = b''#

Raw, padded EPATH bytes identifying the route to the embedded request’s target. Use new()/route_epath for the rich EPATH view.

property route_epath: EPATH#

route_path, parsed into an EPATH.

classmethod new(message_request: MessageRouterRequest | bytes | bytearray = b'', route_path: EPATH | bytes | bytearray | Iterable[PortSegment | LogicalSegment | NetworkSegment | SymbolicSegment | DataSegment | ElectronicKeySegment] = b'', *, priority: int = 0, timeout_ticks: int = 0) Self[source]#

Builds a request from a MessageRouterRequest (or raw bytes) and a rich route_path (EPATH, raw bytes, or an iterable of segments).

EtherNet/IP Class 0/1 cyclic I/O connections (UDP transport).

A Class 0/1 connection carries the raw I/O data negotiated by a prior Forward_Open/Large_Forward_Open exchange (see icspacket.proto.cip.connmgr); this module does not perform any Connection Manager services itself, it only transports the cyclic data once a connection has been opened.

exception icspacket.proto.cip.io.CIPIOError[source]#

Raised for malformed or failed Class 0/1 I/O exchanges.

class icspacket.proto.cip.io.CIPIO_Connection(timeout: float = 1.0, *, header_format: bool = True, sequence_format: bool = True, bind_address: str | None = None)[source]#

UDP transport for a Class 0/1 (cyclic) CIP I/O connection.

RUN: ClassVar[int] = 1#

Value this class writes into the Run/Idle header to mark the O->T payload as live data the target should act on (the “Run” state).

IDLE: ClassVar[int] = 0#

Value this class writes into the Run/Idle header to tell the target to disregard the accompanying O->T payload (the “Idle” state).

header_format: bool#

Whether O->T datagrams carry a 4-byte Run/Idle header (See CIP Volume 1, clause 3-6.1.4 “32-Bit Header Format”), as used by exclusive-owner/input-output connections. Disable for connections that use the plain “Modeless” O->T format.

sequence_format: bool#

Whether O->T datagrams carry a 16-bit connected sequence count prefix identifying a class 1 real-time format (See CIP Volume 1, clause 3-6.1). Some targets omit this prefix and rely solely on the CPF-level sequence number instead; disable for those connections.

classmethod from_forward_open(response: ForwardOpenResponse, address: tuple[str, int], *, timeout: float = 1.0, header_format: bool = True, sequence_format: bool = True) CIPIO_Connection[source]#

Open a Class 0/1 connection using connection IDs from a Forward_Open response.

open(response: ForwardOpenResponse, address: tuple[str, int]) None[source]#

Open the UDP socket using connection IDs from response.

close() None[source]#

Close the UDP socket and clear negotiated connection IDs.

send(data: bytes, *, run_idle: int | None = None) None[source]#

Send one cyclic O->T datagram carrying data as the assembly payload.

run_idle overrides header_format’s default RUN value for this datagram only; pass it explicitly to send an IDLE datagram. Ignored entirely when header_format is False.

recv() bytes[source]#

Receive one cyclic T->O datagram and return its raw assembly data.