Conformance run with dnp3c¶
dnp3c is a DNP3 conformance test harness from Step Function I/O. It acts as a master and runs the outstation test procedures of the DNP Users Group's DNP3 IED Certification Procedure against a device. Continuous integration runs it against this library's outstation, and this page says how each procedure ends.
Every procedure that runs passes. None passes with a warning, none is excluded, and no
option is turned off to avoid a failure. The report the tool writes is kept in
compliance/.
It is a second carrying-out of the certification procedures, by a tool this project did not write. The first is the unit suite's own, which is this project reading the procedures and checking its reading. Where the two agree, the agreement means something. Where they did not, the difference was settled from the documents, and this page says how.
This is not a certification, and it is not a statement by Step Function I/O about this library. It is what their tool reported when this project ran it.
What is run¶
| Tool | dnp3c, version 0.2.0, as published on Maven Central |
| Procedures | Revision 3.1 rev 1 (13 September 2022), at Subset Level 2 |
| Device | A Subset Level 2 configuration of this library's outstation, served over TCP on a loopback address of its own by interop/conformance_outstation.py |
| Steps a master cannot perform | Done by a plugin through the device's control channel: cycling power, changing inputs and flags, setting up the network, and checking each response against what the device holds |
| Where | The dnp3c-conformance job of the interop workflow, on every pull request that reaches the outstation: the library outside the master, the device script, the harness and its record, the workflow or the dependencies. The interop gate waits for it, so such a pull request does not merge while it fails |
The device is built from the library's defaults. A few things a device built on the library decides for itself are decided as the procedures expect:
- It carries out a cold restart request, and can be set not to, without a restart.
- It carries out a control sent to a broadcast address (IEEE 1815-2012 4.7), and takes broadcasts sent as UDP datagrams beside its TCP connection (13.2.3.2).
- It asks for the time again ten seconds after a master writes it, and stops trusting its clock twenty seconds after the write, as a clock that drifts does (D60).
Before each procedure the counters are frozen once, as an operator would before testing, so that a frozen counter holds a value. Flags are set abnormal through the point's source: the test bench marks an input's or an output's source as having lost communication, and a frozen counter carries its counter's flags from the freeze.
How each section ends¶
The tables on this page are written from interop/dnp3c/expected.json, the record the
job compares every run with. A procedure that starts to fail, starts to pass, stops
running, or passes with a new warning fails the job until the record and this page are
brought up to date.
| Section | Covers | Passed | Skipped | Failed |
|---|---|---|---|---|
| 6.1 | Reset Link and Passive Confirm support | 6.1.2 | ||
| 6.3 | Request Link Status | 6.3.2 | ||
| 6.4 | Test Retries | 6.4.2 | ||
| 6.5 | DIR and FCV Bits | 6.5.2 | ||
| 6.6 | Data Link Rejects Invalid Frames | 6.6.2a to 6.6.2c, 6.6.2e, 6.6.2f | 6.6.2d, 6.6.3a to 6.6.3d | |
| 6.7 | Self-Address Support | 6.7.2 | ||
| 7 | Transport Layer | 7.2 | ||
| 8.1 | Binary Output Status | 8.1.2 | ||
| 8.2 | Binary Outputs | 8.2.1.2.1 to 8.2.1.2.15, 8.2.2.2.1 to 8.2.2.2.3, 8.2.3.2.1 to 8.2.3.2.3, 8.2.4, 8.2.5.2.1, 8.2.5.2.2, 8.2.6 | ||
| 8.3 | Analog Output Status | 8.3.2 | ||
| 8.4 | Analog Outputs | 8.4.1.2.1 to 8.4.1.2.13, 8.4.2.2.1 to 8.4.2.2.3, 8.4.3.2.1 to 8.4.3.2.3, 8.4.4 | ||
| 8.5 | Class Data | 8.5.1, 8.5.2.2.1 to 8.5.2.2.4, 8.5.3.2.1 to 8.5.3.2.4, 8.5.4.2.1 to 8.5.4.2.4, 8.5.5.2.1, 8.5.5.2.2, 8.5.6 | ||
| 8.6 | Indications | 8.6.1 to 8.6.3, 8.6.5a, 8.6.5b, 8.6.6.2.1 | 8.6.4a, 8.6.4b, 8.6.6.2.2 to 8.6.6.2.4 | |
| 8.7 | Time | 8.7.1.1.2, 8.7.1.2.2, 8.7.2 | ||
| 8.8 | Cold Restart | 8.8.2 | ||
| 8.9 | Application Layer Fragmentation | 8.9.1, 8.9.2 | ||
| 8.10 | Multi-Drop Support | 8.10.2 | ||
| 8.11 | Unsolicited Responses | 8.11.2.5, 8.11.2a to 8.11.2e | ||
| 8.12 | Collision Avoidance | 8.12.2 | ||
| 8.13 | Binary Inputs Specific Object Requests | 8.13.2c, 8.13.2d | 8.13.2a, 8.13.2b | |
| 8.14 | Binary Input Change Specific Requests | 8.14.2c to 8.14.2p | 8.14.2a, 8.14.2b | |
| 8.15 | Common Time of Occurrence | 8.15.2, 8.15.3 | ||
| 8.16 | Binary Counters Specific Object Requests | 8.16.1.2.3, 8.16.1.2.4, 8.16.2.2.3 to 8.16.2.2.6 | 8.16.1.2.1, 8.16.1.2.2, 8.16.2.2.1, 8.16.2.2.2 | |
| 8.17 | Binary Counters, Event | 8.17.2b | 8.17.2a, 8.17.2c to 8.17.2f | |
| 8.18 | Analog Input | 8.18.2c | 8.18.2a, 8.18.2b | |
| 8.19 | Analog Change Event | 8.19.2.4, 8.19.2b, 8.19.2c | 8.19.2a | |
| 8.20 | Multiple Read Requests | 8.20.2b | 8.20.2a | |
| 8.21 | Double-bit Inputs | 8.21.1, 8.21b | 8.21a, 8.21c, 8.21d | |
| 8.22 | Double-bit Input Change | 8.22.1, 8.22b | 8.22a, 8.22c to 8.22p | |
| 8.23 | Specific Point Ranges | 8.23.2.1.1 to 8.23.2.1.3, 8.23.2.2.1 to 8.23.2.2.3, 8.23.2.3.1 to 8.23.2.3.3, 8.23.2.4.1 to 8.23.2.4.3, 8.23.2.5.1 to 8.23.2.5.3, 8.23.2.6.1 to 8.23.2.6.3 | ||
| 8.24 | Specific variations for Static Inputs | 8.24.2a to 8.24.2g | ||
| 8.25 | Specific Variations for Events | 8.25.2a to 8.25.2e | ||
| 8.26 | Assign Class | 8.26.2 | 8.26.3 | |
| 9.5 | Network: All Communication Methods | 9.5.1.1.2, 9.5.1.2.2, 9.5.1.5.2 | 9.5.1.3.2, 9.5.1.4.2, 9.5.1.6.2, 9.5.2.1.2, 9.5.2.2.2, 9.5.2.3.2, 9.5.2.4.2, 9.5.2.5.2, 9.5.3.1.2, 9.5.3.2.2, 9.5.3.3.2, 9.5.3.4.2, 9.5.3.5.2, 9.5.4 | |
| 10.3 | Output Control Events: Binary | 10.3.2 to 10.3.4 | ||
| 10.4 | Output Control Events: Analog | 10.4.2 to 10.4.4 |
Procedures that fail¶
None.
Procedures that are excluded¶
None.
Procedures that pass with a warning¶
dnp3c warns on a pass where the device should probably change something, for instance where it complies with IEEE 1815-2012 but not with IEEE 1815-202x.
None.
What the run settled¶
Each of these ended differently in an earlier run, and was settled from the documents.
- A read while an unsolicited response is unconfirmed (8.11.2a). The outstation holds the read until the confirmation arrives or its timeout expires, as 3.1 rev 1's 8.11.1 and IEEE 1815-2012 rule 16 have it (D71). dnp3c used to wait only the response timeout. It now waits the unsolicited confirm timeout too.
- Counter reads in other variations (8.24.2d). The outstation now answers a 16-bit or flagless counter read at 32 bits with flags, as 8.24.2 allows a device that keeps 32-bit values, where it refused the read (D100).
- Frozen counters a device lacks (8.23.2.4.1). An outstation whose counters do not freeze refuses a read of group 21 with IIN2.1, where it answered with nothing (D100).
- A disable request with unsolicited responses off (8.11.2). The outstation now refuses it with IIN2.0 (D21).
- A broadcast sent as confirmed user data (8.6.5). The outstation now acts on it (D50).
- Events with time before the time is written (8.14.2j to 8.14.2l), and the error indication from a device without double-bit inputs (8.21, 8.22). The outstation's answers were already the ones 3.1 rev 1 gives, and dnp3c now accepts them.
Procedures that are skipped¶
dnp3c skips a procedure when an option says the device does not have the feature it tests. Each option that is off, and the reason:
| Option turned off | Why | Procedures skipped for it |
|---|---|---|
network.tcp.multicast |
The outstation joins no multicast group. | 9.5.1.6.2 |
network.tcp.specificAddress |
The outstation accepts a connection from any master and keeps no list of masters. | 9.5.1.3.2 |
network.tcp.wildcard |
The outstation keeps no list of masters, so no range of them either. | 9.5.1.4.2 |
network.udp |
The outstation answers requests over TCP only. It takes broadcasts as UDP datagrams beside TCP, and nothing else over UDP. | 9.5.2.1.2, 9.5.2.2.2, 9.5.2.3.2, 9.5.2.4.2, 9.5.2.5.2 |
network.udp.unsolicited |
The outstation sends unsolicited responses over TCP only. | 9.5.3.1.2, 9.5.3.2.2, 9.5.3.3.2, 9.5.3.4.2, 9.5.3.5.2, 9.5.4 |
serial.enabled |
The outstation is reached over TCP. This library has no serial link. | 6.6.2d, 8.10.2, 8.12.2 |
supports.analogInputEventsQueuedPerPoint |
An analog input keeps one event, its newest. | 8.6.6.2.2 |
supports.analogOutputChangeEvents |
Analog output events are not offered. | 10.4.2, 10.4.3 |
supports.analogOutputCommandEvents |
Analog output events are not offered. | 10.4.2, 10.4.4 |
supports.assignNoClass |
Assign class is not implemented. A point's class is fixed when the outstation is built. | none |
supports.binaryOutputChangeEvents |
Binary output events are not offered. | 10.3.2, 10.3.3 |
supports.binaryOutputCommandEvents |
Binary output events are not offered. | 10.3.2, 10.3.4 |
supports.counterChangeEvents |
A counter reports no change events. | 8.6.6.2.3, 8.17.2c to 8.17.2f |
supports.counterEventsQueuedPerPoint |
A counter reports no change events, so none are queued. | none |
supports.dataLinkConfirmRequests |
The outstation never requests data link confirmation. | 6.4.2, 6.6.3a to 6.6.3d |
supports.dataLinkRetries |
The outstation never requests data link confirmation, so it has nothing to send again. | none |
supports.deviceAttributes |
Device attributes are not served. | none |
supports.disable.doubleBitInput |
The outstation has no double-bit binary inputs to disable. | none |
supports.doubleBitInput |
The outstation has no double-bit binary inputs. | 8.6.6.2.4, 8.21c, 8.21d, 8.22c to 8.22p |
supports.doubleBitInputChangeEvents |
The outstation has no double-bit binary inputs, so none report events. | none |
supports.frozenCounterChangeEvents |
Held to Subset Level 2, a freeze buffers no event. | none |
supports.globalRemoteSupervisoryControl |
Outputs have no local state, so no switch puts them all in it. | 8.6.4a |
supports.individualRemoteSupervisoryControl |
Outputs have no local state. | 8.6.4b |
supports.multipleAddresses |
The outstation answers to one link address. | 6.6.2d |
supports.selfAddress |
The outstation does not answer the self-address. | 6.7.2 |
It also skips a procedure written for a device without a feature, when the device has it:
| Procedure | Skipped because |
|---|---|
| 8.13.2b | supports.binaryInput != false |
| 8.14.2b | it needs not (supports.binaryInput == true and supports.binaryInputChangeEvents == true) |
| 8.16.1.2.2 | supports.counter != false |
| 8.16.2.2.2 | supports.frozenCounter != false |
| 8.18.2b | supports.analogInput != false |
And it skips the procedures written for another subset level:
| Procedure | Skipped because |
|---|---|
| 8.13.2a, 8.14.2a, 8.16.1.2.1, 8.16.2.2.1, 8.17.2a, 8.18.2a, 8.19.2a, 8.20.2a, 8.21a, 8.22a | it applies to subset level(s) 1, and the device is configured for level 2 |
| 8.26.3 | it applies to subset level(s) 3, and the device is configured for level 2 |
Procedures dnp3c does not implement¶
None.
Seeing a run¶
compliance/ keeps the
report of the run on record: report.html, and each procedure's log.html with every
step, every frame sent and received decoded at each layer, and every check. A test holds
its outcomes to the record above.
The job also keeps a run's whole output as the dnp3c-conformance-results artifact of
the workflow run, whether the run passed or not, with each procedure's dump.pcap, its
traffic, which opens in Wireshark, and outstation.log, the device's own log.
To run it yourself, see
interop/dnp3c/README.md.
It needs Java 17 or newer, Maven, and a Python with this library installed.
The tool's license¶
dnp3c is licensed under the
GNU General Public License v3.0 only.
The plugin that drives it is a separate program in interop/dnp3c/ that links the
tool's library and is used only in this repository's tests. The report in compliance/
is the tool's output and carries its text, so it is licensed the same way. Nothing of the
tool, the plugin or the report is in the library, its wheel or its source distribution.