Metadata-Version: 2.4
Name: dnp3-simulation
Version: 0.1.2
Summary: Lightweight DNP3 TCP simulator for testing master/client workflows.
Author: Jenil Vekariya
Maintainer: Jenil Vekariya
License-Expression: MIT
Keywords: dnp3,dnp3-simulator,scada,ics,outstation,tcp,protocol-testing
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications
Classifier: Topic :: Internet
Classifier: Topic :: Software Development :: Testing
Classifier: Topic :: System :: Networking
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Dynamic: license-file

# DNP3 Simulation

[![PyPI version](https://img.shields.io/pypi/v/dnp3-simulation.svg)](https://pypi.org/project/dnp3-simulation/)
[![Python versions](https://img.shields.io/pypi/pyversions/dnp3-simulation.svg)](https://pypi.org/project/dnp3-simulation/)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

`dnp3-simulation` is a lightweight Python TCP simulator for a small, practical
subset of DNP3 binary outstation behavior. It is useful for testing DNP3
master/client workflows without a physical outstation device.

The package provides three command-line tools:

- `dnp3-sim-server`: start the TCP simulator server
- `dnp3-sim-client`: send DNP3 binary client requests
- `dnp3-sim`: run helper commands and JSON debugging requests

This project is a compact simulator subset, not a full DNP3 stack.

## Features

- DNP3 TCP binary frames beginning with `05 64`
- Link-layer length, control, source, destination, and CRC blocks
- Single-fragment transport/application messages
- Binary Output Status read/write support with `Group 10 Variation 2`
- Control Relay Output Block direct operate support with `Group 12 Variation 1`
- Cold restart and warm restart responses
- JSON output from the client, including transmitted and received frame hex
- Small configuration files for outstation settings and simulated points

## Supported Function Codes

| Function code | Name | Support |
| --- | --- | --- |
| FC 1 | Read | Read binary output status points |
| FC 2 | Write | Write binary output status points |
| FC 5 | Direct Operate | Operate binary output points and return a response |
| FC 6 | Direct Operate No Acknowledge | Operate binary output points without a normal application response |
| FC 13 | Cold Restart | Reset point values to startup defaults |
| FC 14 | Warm Restart | Restart while preserving point values by default |

## Requirements

- Python 3.10 or newer

## Installation

```bash
python -m pip install dnp3-simulation
```

Upgrade an existing install:

```bash
python -m pip install --upgrade dnp3-simulation
```

For local development from a cloned repository:

```bash
python -m pip install -e ".[dev]"
```

## Quick Start

Start the simulator. No config files are required for the default demo
outstation.

```bash
dnp3-sim-server
```

In another terminal, read all binary output status points:

```bash
dnp3-sim-client read --host 127.0.0.1 --port 20000
```

Write point `0` on:

```bash
dnp3-sim-client write --host 127.0.0.1 --port 20000 --index 0 --value on
```

Read point `0` again:

```bash
dnp3-sim-client read --host 127.0.0.1 --port 20000 --index 0
```

## Configuration

The server includes a built-in default outstation and two binary output points,
so `dnp3-sim-server` can run immediately after installation.

To create editable config files:

```bash
dnp3-sim init-config
```

This writes:

```text
config/outstation.json
config/points.json
```

Overwrite existing sample config files:

```bash
dnp3-sim init-config --force
```

Example `config/outstation.json`:

```json
{
  "outstation": {
    "name": "dnp3-simulator",
    "local_address": 1024,
    "remote_master_address": 1,
    "transport": {
      "type": "tcp",
      "host": "127.0.0.1",
      "port": 20000
    },
    "restart": {
      "cold_restart_delay_ms": 1000,
      "warm_restart_delay_ms": 300,
      "reject_commands_while_restarting": true,
      "warm_restart_preserve_point_values": true,
      "clear_events_on_cold_restart": false
    }
  }
}
```

Example `config/points.json`:

```json
{
  "binary_outputs": [
    {
      "index": 0,
      "name": "breaker_1",
      "startup_value": false,
      "commandable": true,
      "allowed_operations": ["latch_on", "latch_off", "pulse_on", "pulse_off"]
    },
    {
      "index": 1,
      "name": "pump_1",
      "startup_value": false,
      "commandable": true,
      "allowed_operations": ["latch_on", "latch_off"]
    }
  ]
}
```

## Server Usage

Start with built-in defaults:

```bash
dnp3-sim-server
```

Bind to all network interfaces:

```bash
dnp3-sim-server --host 0.0.0.0 --port 20000
```

Use custom config files:

```bash
dnp3-sim-server \
  --outstation config/outstation.json \
  --points config/points.json \
  --host 0.0.0.0 \
  --port 21000
```

Stop the server with `Ctrl+C`.

## Client Usage

Read all binary output status points:

```bash
dnp3-sim-client read --host 127.0.0.1 --port 20000
```

Read one point:

```bash
dnp3-sim-client read --host 127.0.0.1 --port 20000 --index 0
```

Write a point:

```bash
dnp3-sim-client write --host 127.0.0.1 --port 20000 --index 0 --value on
dnp3-sim-client write --host 127.0.0.1 --port 20000 --index 0 --value off
```

Send FC 5 Direct Operate:

```bash
dnp3-sim-client direct \
  --host 127.0.0.1 \
  --port 20000 \
  --function-code 5 \
  --index 0 \
  --operation latch_on
```

Send FC 6 Direct Operate No Acknowledge:

```bash
dnp3-sim-client direct \
  --host 127.0.0.1 \
  --port 20000 \
  --function-code 6 \
  --index 0 \
  --operation latch_off \
  --timeout 0.2
```

Supported direct operate operations:

```text
latch_on
latch_off
pulse_on
pulse_off
```

Send a cold restart:

```bash
dnp3-sim-client restart --host 127.0.0.1 --port 20000 --type cold
```

Send a warm restart:

```bash
dnp3-sim-client restart --host 127.0.0.1 --port 20000 --type warm
```

Send a raw DNP3 frame as hex:

```bash
dnp3-sim-client send-hex --host 127.0.0.1 --port 20000 "05 64 ..."
```

## Client Output

The client prints JSON with the transmitted request frame and decoded response.

Example shape:

```json
{
  "tx_hex": "05 64 ...",
  "rx_hex": "05 64 ...",
  "link": {
    "control": 68,
    "destination": 1,
    "source": 1024
  },
  "application": {
    "transport_control": 193,
    "app_control": 192,
    "function_code": 129,
    "payload_hex": "00 00 ..."
  }
}
```

Notes:

- `tx_hex` is the DNP3 request frame.
- `rx_hex` is the DNP3 response frame.
- Response function code `129` is `0x81`.
- FC 6 usually returns no normal application response.

## JSON Helper Mode

The `dnp3-sim` command also supports JSON-lines helper requests on the same TCP
port. This mode is intended for quick debugging; binary DNP3 mode is the main
interface.

Start the server first, then run:

```bash
dnp3-sim send --function-code 1
dnp3-sim send --function-code 2 --index 0 --value on
dnp3-sim send --function-code 5 --index 0 --operation latch_on
dnp3-sim send --function-code 6 --index 0 --operation latch_off
dnp3-sim send --function-code 13
dnp3-sim send --function-code 14
dnp3-sim status
```

## Capture Traffic

To inspect traffic in Wireshark, capture TCP port `20000` while the simulator
and client are running:

```bash
sudo tcpdump -i lo -s 0 -w dnp3_simulation.pcap 'tcp port 20000'
```

For local loopback testing, use the Wireshark display filter:

```text
tcp.port == 20000
```

DNP3 frames produced by this package start with:

```text
05 64
```

If the client and server are on different machines, capture on the real network
interface instead of `lo`.

## Development

Install development dependencies:

```bash
python -m pip install -e ".[dev]"
```

Run tests:

```bash
python -m pytest -q
```

Expected result:

```text
13 passed
```

Build release artifacts:

```bash
python -m build
python -m twine check dist/*
```

## Limitations

This package intentionally implements a focused DNP3 subset. It does not
currently implement fragmentation, unsolicited responses, secure authentication,
serial transport, or every DNP3 object group.

## License

This project is licensed under the MIT License.
