Master

class icspacket.proto.dnp3.master.DNP3_Task[source]

Abstract base class for a DNP3 task.

A DNP3 task represents a unit of work that the Master initiates, such as sending a request and handling its response. Each task is responsible for preparing an APDU for transmission and for processing the received APDU messages once a response arrives.

Subclasses implement specific task behavior, such as blocking until a response is received or executing a callback when a response is available.

Variables:

sequence (int) – Application layer sequence number associated with the task. Initialized to -1 and updated when the APDU is transmitted.

prepare_transmit(master: DNP3_Master) APDU[source]

Prepare the APDU for transmission.

Subclasses must implement this to return the request APDU that will be sent to the outstation.

Parameters:

master (DNP3_Master) – The DNP3 master instance initiating the transmission.

Returns:

The APDU to transmit.

Return type:

APDU

Raises:

NotImplementedError – If the method is not overridden in a subclass.

on_message(master: DNP3_Master, message: APDU) None[source]

Handle a received APDU message.

This is called when the Master receives a response APDU for the task. The default implementation does nothing.

Parameters:
  • master (DNP3_Master) – The DNP3 master instance handling the message.

  • message (APDU) – The received APDU response.

Implements the DNP3 Link Layer.

The Link Layer provides reliable delivery of Link Protocol Data Units (LPDUs) between a DNP3 master and outstation over a transport medium such as TCP. This class is responsible for:

  • Encapsulation of octet streams into LPDUs.

  • Validation of source/destination addresses.

  • Handling link-layer function codes (e.g., UNCONFIRMED_USER_DATA, REQUEST_LINK_STATUS, LINK_STATUS, NOT_SUPPORTED).

  • Forwarding valid user data to the upper layer.

The class maintains an internal input queue of received LPDUs that have passed validation and are intended for the upper layers.

Parameters:
  • src (int) – The source link-layer address of this endpoint.

  • dst (int) – The expected destination address of the remote peer.

  • sock (socket.socket | None) – Optional pre-initialized TCP socket. If None, a new socket will be created.

  • mode (LinkDirection) – Link direction, either MASTER or OUTSTATION.

property in_queue: Queue[LPDU]

Queue of validated inbound LPDUs.

Only LPDUs that pass address and direction checks are placed into this queue for higher-layer consumption.

Return type:

Queue[LPDU]

property mode: LinkDirection

Link direction of this endpoint.

Return type:

LinkDirection

property source: int

The source address of this endpoint.

Return type:

int

property destination: int

The expected destination address of the peer.

Return type:

int

send_data(octets: bytes, /) None[source]

Send user data as an unconfirmed LPDU.

The octets are wrapped into a Link Layer frame with the function code UNCONFIRMED_USER_DATA and transmitted.

Parameters:

octets (bytes) – The application/user data to send.

Raises:

ConnectionError – If the socket is not connected.

send_lpdu(lpdu: LPDU) None[source]

Send a fully constructed LPDU.

This method sets the LPDU’s source and destination before serializing and transmitting it over the socket.

Parameters:

lpdu (LPDU) – The LPDU to send.

Raises:

ConnectionError – If the socket is not connected.

recv_data() bytes[source]

Receive raw LPDU octets from the socket.

Reads up to 292 bytes, which is the maximum LPDU size (per Link Layer specification, length field max 255 plus headers).

Returns:

Raw bytes read from the socket.

Return type:

bytes

Raises:

ConnectionError – If the socket is not connected.

Send a link status response.

Constructs an LPDU with function code LINK_STATUS and transmits it.

Parameters:

request (LPDU) – The received REQUEST_LINK_STATUS LPDU.

Raises:

ConnectionError – If the socket is not connected.

Changed in version 0.2.2: Added request parameter.

send_not_supported() None[source]

Send a ‘Not Supported’ response.

Constructs an LPDU with function code NOT_SUPPORTED and transmits it.

Raises:

ConnectionError – If the socket is not connected.

recv_lpdu() LPDU[source]

Receive and parse the next valid LPDU.

This method handles fragmented reception (multiple LPDUs in one read) and incomplete frames. Valid frames that pass link validation are placed in the inbound queue.

Returns:

The next valid LPDU for the upper layer.

Return type:

LPDU

Raises:

ConnectionError – If the socket is not connected.

connect(address: tuple[str, int]) None[source]

Connect the socket to the specified peer address.

Parameters:

address (tuple[str, int]) – Peer address as a tuple of (host, port).

close() None[source]

Close the underlying socket connection.

class icspacket.proto.dnp3.master.DNP3_Transport(link: DNP3_Link, unsolicited_callback: Callable[[APDU], None] | None = None)[source]

DNP3 Transport Layer implementation.

This class implements the transport layer of the DNP3 protocol, providing fragmentation and reassembly of Application Protocol Data Units (APDUs) into Transport Protocol Data Units (TPDUs). It sits above the Link Layer (DNP3_Link) and ensures that application data is transmitted in compliance with the transport rules defined in the DNP3 standard.

Key responsibilities of this layer include:

  • Fragmenting application data into transport segments (1-249 octets each).

  • Wrapping each fragment into a TPDU with proper sequence numbering.

  • Reassembling received TPDUs into complete APDUs.

  • Detecting and handling unsolicited responses, optionally invoking a user-provided callback.

Parameters:
  • link (DNP3_Link) – The link layer object that provides LPDU transmission and reception.

  • unsolicited_callback (Callable[[APDU], None] | None) – Optional callback function invoked when an unsolicited response APDU is received from the outstation.

Return the underlying link layer object.

Returns:

The active DNP3_Link instance.

Return type:

DNP3_Link

connect(address: tuple[str, int]) None[source]

Establish a connection through the link layer.

This method delegates the connection setup to the underlying DNP3_Link object, updating the transport connection state.

Parameters:

address (tuple[str, int]) – A tuple containing the IP address and port number of the outstation.

close() None[source]

Close the transport connection.

This method closes the link layer connection and updates the internal connection status.

next_sequence() int[source]

Get the next transport sequence number.

The transport layer maintains an internal 6-bit sequence counter used for ordering TPDUs. After reaching the maximum value, the counter wraps around to zero.

Returns:

The current transport sequence number before incrementing.

Return type:

int

send_data(octets: bytes, /) None[source]

Send application data through the transport layer.

This method fragments the given application data into one or more TPDUs (each up to 249 octets). Each TPDU is assigned sequence numbers and FIR/FIN flags. The fragments are then transmitted via the link layer.

Parameters:

octets (bytes) – The application data bytes to transmit.

Raises:

ConnectionError – If the transport connection is not established.

recv_data() bytes[source]

Receive and reassemble application data from the transport layer.

This method collects one or more TPDUs received from the link layer and reconstructs the original APDU. It validates transport sequence numbers to ensure proper ordering. Unsolicited responses are detected and optionally passed to the callback without being returned to the caller.

Returns:

The fully reassembled APDU bytes.

Return type:

bytes

Raises:

ConnectionError – If the transport connection is not established.

class icspacket.proto.dnp3.master.DNP3_Master(link_addr: int, initial_seq: int | None = None, sock: socket = None)[source]

DNP3 Master (Application Layer interface).

This class provides the master-side interface of the DNP3 protocol stack, built on top of the transport and link layers. It manages request/response exchanges with an outstation, schedules tasks, and assigns application sequence numbers. The master does not implement actual application logic — instead, it delegates handling to user-defined DNP3_Task objects.

This implementation supports:

  • Establishing and releasing associations with outstations.

  • Submitting tasks that generate and transmit APDUs.

  • Assigning and cycling application sequence numbers.

  • Supporting both blocking and non-blocking request patterns.

  • Optionally dispatching requests without expecting a return (submit_noreturn()).

Example:

master = DNP3_Master(link_addr=0x0001)
# connect to remote at 127.0.0.1:20000 with link addr 1024
master.associate((1024, "127.0.0.1", 20000))

# request class 1 objects
objects = new_class_data_request(1)
result = master.request(FunctionCode.READ, objects)
Parameters:
  • link_addr (int) – The source link-layer address of the master.

  • initial_seq (int | None) – Optional initial application sequence number. Defaults to 0 if not provided.

  • sock (socket.socket | None) – Optional socket to bind the link layer to. If omitted, a new socket will be created when associating with an outstation.

property tasks: Collection[DNP3_Task]

Return the currently registered tasks.

Returns:

A collection of active tasks indexed by sequence number.

Return type:

Collection[DNP3_Task]

property transport: DNP3_Transport

Return the transport layer instance.

Returns:

The active DNP3_Transport object.

Return type:

DNP3_Transport

property sequence: int

Return the current application sequence number.

The sequence number is incremented and wrapped automatically whenever a request is submitted.

Returns:

The current APDU sequence number.

Return type:

int

get_task(sequence: int) DNP3_Task | None[source]

Retrieve a task by its sequence number.

Parameters:

sequence (int) – The sequence number of the task to fetch.

Returns:

The corresponding DNP3_Task, or None if not found.

Return type:

DNP3_Task | None

pop_task(sequence: int) DNP3_Task | None[source]

Remove and return a task by its sequence number.

Parameters:

sequence (int) – The sequence number of the task to remove.

Returns:

The removed DNP3_Task, or None if not found.

Return type:

DNP3_Task | None

associate(address: tuple[int, str, int], link_cls: type[DNP3_Link] | None = None) None[source]

Establish an association with an outstation.

This method sets up the underlying link and transport layers, connects to the given host/port, and starts background processing of incoming messages.

Parameters:
  • address (tuple[int, str, int]) – A tuple of the form (link_addr, host, port).

  • link_cls (type[DNP3_Link]) – Optional link layer class to use. If not provided, the default DNP3_Link will be used.

Raises:

ValueError – If the provided address tuple is invalid.

Changed in version 0.2.2: Added link_cls parameter.

release(timeout: float | None = None) None[source]

Release the association with the outstation.

This method stops the background thread, closes the transport connection, and invalidates the master instance.

Parameters:

timeout (float | None) – Optional timeout (in seconds) to wait for the background thread to join.

submit_task(task: DNP3_Task) None[source]

Submit a task and register it for response handling.

The task will be assigned the current application sequence number, which is then incremented. The request APDU is built by the task and transmitted through the transport layer.

Parameters:

task (DNP3_Task) – The task to be submitted.

Raises:

ConnectionError – If the task fails to produce a valid APDU.

submit_noreturn(task: DNP3_Task) None[source]

Submit a task without expecting a response.

Unlike submit_task(), the task is not registered for later lookup, meaning no response will be matched or delivered.

Parameters:

task (DNP3_Task) – The task to be submitted.

Raises:

ConnectionError – If the task fails to produce a valid APDU.

transmit(request: APDU, block: bool = True, callback: Callable[[DNP3_Master, APDU], None] | None = None, noreturn: bool = False) APDU | None[source]

Transmit a request APDU to the outstation.

This method provides both blocking and non-blocking transmission modes. In blocking mode, it waits for the corresponding response before returning. In non-blocking mode, the provided callback will be invoked when the response is received.

Parameters:
  • request (APDU) – The APDU to transmit.

  • block (bool) – Whether to wait for the response. Defaults to True.

  • callback (Callable[["DNP3_Master", APDU], None] | None) – Optional callback to invoke with the response in non-blocking mode.

  • noreturn (bool) – If True, the request is sent without expecting a response.

Returns:

The received APDU if in blocking mode, otherwise None.

Return type:

APDU | None

request(function: FunctionCode, objects: DNP3Objects | None = None, need_confirm: bool = False, block: bool = True, callback: Callable[[DNP3_Master, APDU], None] | None = None, noreturn: bool = False) APDU | None[source]

Build and transmit a request APDU.

This is the high-level entry point for sending function code requests to an outstation. Optionally, objects can be included, confirmation can be requested, and the call may be synchronous or asynchronous.

Parameters:
  • function (FunctionCode) – The DNP3 application function code.

  • objects (DNP3Objects | None) – Optional collection of DNP3 objects to include.

  • need_confirm (bool) – Whether a confirmation is required.

  • block (bool) – Whether to wait for the response.

  • callback (Callable[["DNP3_Master", APDU], None] | None) – Callback to invoke in asynchronous mode.

  • noreturn (bool) – If True, send without expecting a response.

Returns:

The APDU response in blocking mode, otherwise None.

Return type:

APDU | None

Raises:

ValueError – If both need_confirm and noreturn are set.