dnp3

package module
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 24, 2026 License: GPL-3.0 Imports: 6 Imported by: 0

README

go-dnp3

A native Go implementation of DNP3 (IEEE Std 1815-2012), providing both master and outstation roles.

Status: usable, API not yet stable. A master and an outstation talk to each other end to end over TCP, TLS, UDP, serial or an in-process pipe, with or without link-layer confirmation. Three applications ship with it. What is and is not supported is set out honestly in the device profile, including the gaps.

m := master.New(master.Config{
    LocalAddr: 1, RemoteAddr: 10,
    IntegrityOnStartup: true, DisableUnsolOnStartup: true,
}, handler)

go m.Run(ctx, channel.TCPClient("10.0.0.5:20000", channel.DefaultRetry))

m.AddPeriodicScan(ctx, 5*time.Second, dnp3.Class123)
m.ScanRange(ctx, 30, 1, 0, 15)
m.SyncTime(ctx)

// Controls. SelectAndOperate gives the outstation a chance to refuse
// before anything in the substation moves.
res, err := m.SelectAndOperate(ctx, master.Trip(3, 1000))
res, err = m.DirectOperate(ctx, master.AnalogOutputFloat32(7, 13.75))

Documentation

User guide How to build a master, an outstation, or a decoder with this. Start here.
API reference Every exported type and function, package by package.
Device profile What is supported, in the shape a vendor ships — including the known gaps.
SKILL.md The same ground, condensed for AI coding agents.

Why

There is no comprehensive native Go DNP3 stack. opendnp3, the de-facto reference implementation, is C++11 and has been archived since September 2022. What exists in Go today is limited to banner-grab scanners and partial dissectors — no session layer, no outstation, no event model.

Design

DNP3 is a four-layer stack, and each layer here is a package whose core takes bytes and returns bytes: it performs no I/O, starts no goroutines, and reads no clock. Sessions and channels supply those from the outside.

That constraint is the whole design. A layer that cannot touch a socket can be fuzzed with a byte slice, unit-tested against golden hex, and driven by a virtual clock — which is what a protocol facing untrusted field devices needs.

internal/link       framing, CRC-16/DNP, primary and secondary state machines
internal/transport  FIR/FIN/SEQ segmentation and reassembly
internal/app        application headers, function codes, IIN, qualifiers
internal/stack      link + transport plumbing shared by both roles
objects             group/variation codecs, generated from a spec table
master, outstation  session state machines built on the layers above, including
                    group 70 file transfer on both sides
channel             TCP, TLS, UDP, serial and an in-process pipe
multidrop           one channel shared by several sessions, for multi-drop lines
decoder             structured protocol traces for logging and tooling

Each session owns its protocol state in a single goroutine. The socket is read by a separate goroutine that hands completed fragments across a channel, so no protocol state is ever touched from two places. The request API is safe to call from anywhere: it submits a task and waits, rather than reaching into the session.

Every integration test runs a full master against a full outstation through channel.Pipe — the real link, transport, application and object layers, with no socket and no hardware. That is where the two halves are proven to agree.

The library depends on the standard library plus one package: go.bug.st/serial, reached only from channel/serial.go. gopkg.in/yaml.v3 is in go.mod for the code generator in internal/gen and the configuration files of dnp3-master and dnp3-outstation — none of them importable, so no importable package reaches it, which go list -deps will confirm. The terminal explorer carries Bubble Tea, likewise reached from nothing the library exports.

Conformance

The device profile states what is supported, in the shape a vendor ships with a device — including a known gaps section rather than leaving them to be discovered. Nothing here is a certified conformance claim.

Interoperability

Verified in both directions against opendnp3, built from source in container:

make interop-build   # clone and compile opendnp3 3.1.2
make interop         # our master against opendnp3 outstation
make interop-reverse # opendnp3 master against our outstation

conformance/ drives an outstation with hand-built request fragments and checks what comes back, modelled on the DNP Users Group's Level 2 procedures: unknown function codes, unknown groups, broadcast handling, the restart handshake, event confirmation, select-before-operate. It is not a substitute for certified conformance testing, and nothing in this repository is a conformance claim.

Object codecs

The ~113 group/variation encodings are generated from a single declarative table, objects/spec/dnp3_objects.yaml. Adding a variation is one line; the generator emits its parser, its writer, its registry entry, and the size the framing layer needs to walk a fragment. The output is committed, so consumers never run the generator, and CI fails if it drifts from the spec.

Three things stay hand-written because the table cannot express them: bit-packed objects, whose unit of encoding is the range rather than the object; commands, whose fields map onto purpose-built structs rather than a measurement type; and groups 0 and 70 — device attributes and file transfer — whose objects are variable length and carry their own size, and whose variations name a thing rather than an encoding.

Scope

Targeting IEEE 1815 conformance Level 2, then Level 3, with Level 4 features staged after that. Transports: TCP, TLS, serial and UDP. Secure Authentication v5 is out of scope; use TLS.

Commands

Each has a README of its own with the full flag reference, configuration file format and output shapes; cmd/ indexes them.

Command What it is Status
dnp3-decode An offline frame decoder Working
dnp3-outstation A simulated substation RTU with plant behind it and fault injection Working
dnp3-explorer A terminal DNP3 browser built on Bubble Tea v2 Working
dnp3-master A SCADA client, CSV recorder and control tool Working
dnp3-explorer

A full-screen browser for one outstation, driven by the keyboard or the mouse. Point it at a device, or run -demo for a full outstation in-process over channel.Pipe — the real link, transport, application and object layers with no socket and no hardware:

$ dnp3-explorer -demo
dnp3-explorer  demo (in-process outstation)  ● connected      up 1:36  13.4/s  12:04:29
 1 Overview  2 Points  3 Events  4 Log  5 Help                            20 points
────────────────────────────────────────────────────────────────────────────────────
 POINT ▲          VALUE TREND        QUALITY                AGE TIMESTAMP
 BI 0                ON ▁▁▁▁████████ ONLINE                 0s  12:04:29.375
 BI 5               OFF ▄▄▄▄▄▄▄▄▄▄▄▄ COMM_LOST              0s  12:04:29.375
 CT 0             10422 ▁▂▃▄▄▅▆▆▇███ ONLINE                 0s  12:04:29.375
 AI 0         11019.967 ▁▁▂▂▃▄▄▅▆▆▇█ ONLINE                 0s  12:04:29.375
 AI 4             0.420 ▄▄▄▄▄▄▄▄▄▄▄▄ ONLINE|LOCAL_FORCED    0s  12:04:29.375
 BO 0                ON ▁▁▁▁████████ ONLINE                 0s  12:04:29.375
 OS 0  GO-DNP3 DEMO RTU              ONLINE                 3s  —
────────────────────────────────────────────────────────────────────────────────────
 [i Integrity] [p Poll] [/ Filter] [d Inspect] [c Close] [o Open] [S SBO] [e Export]
 ↑↓ move · enter act · d inspect · / filter · < > r sort · b deadband · ? help

Five screens: an overview of the session and the device's health, the point table, the sequence of events, the activity log, and a reference. Points can be filtered on anything in the row, sorted by value or by quality — worst first, which is how you find the broken points in a device with a thousand good ones — and inspected one at a time, with the flags named, the trend drawn and the group and variation each value arrived under. e exports what is on screen, after the filter and the sort, as CSV.

Controls are the reason to be careful, so the tool is: enter on an output opens a dialog naming exactly what will be sent, select-before-operate by default, and a confirmation before anything moves. -direct and -no-confirm turn that off for the devices and the situations that need it — and while -no-confirm is in effect the toolbar says so, in the one mode where no dialog appears to say it for itself.

The connection itself is editable while it runs. C opens an editor for the address, the two link addresses, the response timeout and the poll interval; applying it tears the session down and brings a new one up in place. That is there because a link address read off a drawing is a guess until something answers, and restarting the tool to try 11 instead of 10 is how ten minutes of commissioning becomes an afternoon. Pointing somewhere new drops the point table with it — those measurements came from a different device.

The mouse works throughout — tabs, rows, column headings, footer buttons, the wheel and the scrollbar — and every click resolves to the key the keyboard would have pressed, so the two can never drift apart. -mouse=false turns it off, and -inline gives back inline rendering instead of the alternate screen.

The DNP3 session runs in its own goroutine and never touches the model; every action is a tea.Cmd that returns a result message, so a poll that takes five seconds costs five seconds of that one command and nothing else. Key handling is a plain HandleKey(string) and the pointer resolves against a layout computed from the terminal size, which is what lets the whole interface — clicks included — be driven from tests without a terminal.

dnp3-master

Polls one outstation or many, records what they report, and issues controls:

$ dnp3-master -host 127.0.0.1:20000 -record ./data -listen :8080
$ curl -s localhost:8080/status | head
[{"name":"127.0.0.1:20000","connected":true,
  "indications":"CLASS_2_EVENTS|CLASS_3_EVENTS",
  "tasks_run":9,"tasks_succeeded":9,"tasks_failed":0,"response_timeouts":0}]

$ dnp3-master -host 127.0.0.1:20000 operate trip 0
[0] CROB{PULSE_ON|TRIP count=1 on=1000ms off=0ms status=SUCCESS}: [0]=SUCCESS

Recording is CSV — values.csv for the current picture, events.csv for the sequence of record, kept separate because a sequence-of-events file with poll data mixed into it is no longer a sequence of events. Files are appended and flushed per row: a recorder that loses the last minute of an outage to a buffer is worse than one that writes a little more slowly.

The plan called for SQLite as well. That would mean a large dependency in what is meant to be an example, so it is left out; anyone who wants a database can load these files into one.

dnp3-outstation

A simulated RTU whose points behave like plant: a breaker stays open once tripped and takes time to travel, an analog ramps rather than jumping, and a control closes the loop — tripping breaker 0 opens breaker 0, which raises a binary input event, which the master receives.

$ dnp3-outstation -points
Simulated plant

  Breakers (binary input / binary output)
    BI   0 / BO   0  Feeder 1 breaker (closed)
    BI   1 / BO   1  Feeder 2 breaker (closed)
    BI   2 / BO   2  Bus tie (open)
    BI   3 / BO   3  Earth switch (racked out) (open)  [interlocked: commands refused]

  Analogs
    AI   0  Bus voltage  sine 10.8..11.2 kV
    AI   1  Feeder 1 current  walk 0..400 A
    AI   2  Feeder 2 current  walk 0..400 A
    AI   3  Transformer temperature  ramp 35..85 degC
    AI   4  Tap position  step 7..9

  Counters
    CT   0  Feeder 1 energy  12/s
    CT   1  Feeder 2 energy  8/s

The fault injections matter as much as the simulation: -inject event-storm=500, -inject restart-after=5m, -inject offline-every=30s. Those are the conditions a master's error handling is usually wrong about and the hardest to arrange with real equipment.

dnp3-decode takes hex from arguments, a file or a pipe and renders the layer tree. It reads Wireshark hex-dump exports directly — the offset column and ASCII gutter are recognised and dropped:

$ go run ./cmd/dnp3-decode -f decoder/testdata/sample.hex
--  link  MSTR→OUTS UNCONFIRMED_USER_DATA  1→10  len=20  frame=27B
      transport  seq=00 FIR|FIN
      application  READ seq=00 FIR FIN
        g60v2  0x06(none,all-objects) all            0 object(s)
        g60v3  0x06(none,all-objects) all            0 object(s)
        g60v4  0x06(none,all-objects) all            0 object(s)
        g60v1  0x06(none,all-objects) all            0 object(s)

--  link  OUTS→MSTR UNCONFIRMED_USER_DATA  10→1  len=30  frame=39B
      transport  seq=00 FIR|FIN
      application  RESPONSE seq=00 FIR FIN iin=CLASS_1_EVENTS|DEVICE_RESTART
        g1v2  0x00(none,start-stop8) [0..3]         4 object(s)  4 octets
          [0] ON  ONLINE
          [1] OFF  ONLINE
          [2] ON  ONLINE
          [3] OFF  ONLINE
        g30v2  0x00(none,start-stop8) [0..1]         2 object(s)  6 octets
          [0] 300  ONLINE
          [1] 400  ONLINE

--  link  MSTR→OUTS UNCONFIRMED_USER_DATA  1→10  len=24  frame=33B
      transport  seq=00 FIR|FIN
      application  DIRECT_OPERATE seq=01 FIR FIN
        g12v1  0x17(index8,count8)    count=1        1 object(s)  12 octets
          [3] PULSE_ON|TRIP count=1 on=1000ms off=0ms → SUCCESS

Pass -s to reassemble fragments that span several frames, and -x for a hex dump of each frame.

Development

make check       # gofmt, vet and race-enabled tests
make fuzz-short  # 30 seconds per fuzz target
make cover       # coverage.html
make help        # everything else

Fuzzing is part of the normal gate rather than an occasional extra. Crashers found by the fuzzer are committed under testdata/fuzz/ and become regression tests.

License

GNU General Public License, version 3 or later. See LICENSE for the full text and NOTICE for provenance and dependencies.

GPLv3 is a strong copyleft licence, and this is a library: a program that links it must be released under the GPL too. That is the intended effect. If you need to link it from something proprietary, it is not the right licence for you and no exception is offered here.

All dependencies are permissive (MIT, Apache-2.0, BSD-3-Clause) and compatible as inbound licences.

This is a clean-room implementation; no source from another DNP3 stack was copied. It is not affiliated with or certified by the DNP Users Group.

Documentation

Overview

Package dnp3 implements the DNP3 protocol (IEEE Std 1815-2012) for both master and outstation roles.

The stack is layered the way the standard is. Each layer below this package exposes a core that takes bytes and returns bytes — it performs no I/O, starts no goroutines, and reads no clock. Sessions and channels supply those concerns from the outside, which is what makes every parser fuzzable and every state machine testable against a virtual clock.

internal/link       framing, CRC-16/DNP, primary and secondary state machines
internal/transport  FIR/FIN/SEQ segmentation and reassembly
internal/app        application headers, function codes, IIN, qualifiers
objects             group/variation codecs
master, outstation  session state machines built on the layers above
channel             TCP, TLS, UDP, serial and in-process pipe transports

This root package holds the value types the whole stack shares: measurements, quality flags, timestamps, commands and their status codes.

Index

Constants

View Source
const (
	// AttrSetStandard is attribute set 0, the one the standard defines. A
	// device may keep private attributes in other sets.
	AttrSetStandard uint8 = 0

	// AttrAll is the variation a master reads to ask for every attribute a
	// device has, rather than naming them one at a time. It appears only in
	// requests.
	AttrAll uint8 = 254

	// AttrList asks which attributes the device implements.
	AttrList uint8 = 255
)

Attribute set and variation numbers with a meaning fixed by the standard.

View Source
const MaxDNP3Time = int64(1)<<48 - 1

MaxDNP3Time is the largest instant a 48-bit DNP3 timestamp can express: 2^48 - 1 milliseconds after the UNIX epoch, which falls in the year 10889.

View Source
const MaxOctetStringLen = 255

MaxOctetStringLen is the largest octet string the encoding allows.

View Source
const Version = "0.1.0-dev"

Version is the library version, following semantic versioning.

Variables

View Source
var (
	// ErrMalformed means a received octet sequence did not conform to the
	// protocol and could not be recovered.
	ErrMalformed = errors.New("dnp3: malformed data")

	// ErrTimeout means a peer did not respond within the configured window.
	ErrTimeout = errors.New("dnp3: timeout")

	// ErrClosed means the session or channel has been shut down.
	ErrClosed = errors.New("dnp3: closed")

	// ErrNotSupported means the peer rejected a function code or object it
	// does not implement.
	ErrNotSupported = errors.New("dnp3: not supported by peer")

	// ErrBadConfig means a configuration value is outside the range the
	// protocol allows.
	ErrBadConfig = errors.New("dnp3: invalid configuration")

	// ErrTaskFailed means a master task exhausted its retries.
	ErrTaskFailed = errors.New("dnp3: task failed")

	// ErrFileTransfer means an outstation rejected a file operation. The
	// wrapped message carries the status code it reported.
	ErrFileTransfer = errors.New("dnp3: file transfer failed")

	// ErrNoConnection means the channel has no established connection.
	ErrNoConnection = errors.New("dnp3: no connection")
)

Errors returned across the stack. Layer packages define their own detailed errors and wrap them in these, so callers can classify a failure without importing internal packages.

Functions

func AnalogFitsIn16

func AnalogFitsIn16(v float64) bool

AnalogFitsIn16 reports whether v can be encoded as a 16-bit analog without loss, which the outstation needs when a master requests a narrow variation.

func AnalogFitsIn32

func AnalogFitsIn32(v float64) bool

AnalogFitsIn32 reports whether v can be encoded as a 32-bit analog without loss.

func AttributeName added in v0.4.0

func AttributeName(variation uint8) (string, bool)

AttributeName returns the standard set's name for a variation, and whether there is one. It is exported so a tool can label an attribute it has only the number of.

func DNP3ToTime

func DNP3ToTime(ms uint64) time.Time

DNP3ToTime converts a 48-bit DNP3 timestamp to a time.Time in UTC. Bits above the low 48 are ignored, matching what a conforming encoder can emit.

func TimeToDNP3

func TimeToDNP3(t time.Time) uint64

TimeToDNP3 converts t to DNP3's wire representation: milliseconds since the UNIX epoch, clamped to the 48 bits the encoding provides. Times before the epoch clamp to zero.

Types

type Analog

type Analog struct {
	Value float64
	Flags Flags
	Time  Timestamp
}

Analog is an analog input. The stack carries every analog variation — 16-bit, 32-bit, single and double precision — as a float64 and narrows only at the encoding boundary.

type AnalogOutput

AnalogOutput is any of the four analog output command variations.

type AnalogOutputFloat32

type AnalogOutputFloat32 struct {
	Value  float32
	Status CommandStatus
}

AnalogOutputFloat32 is a group 41 variation 3 control.

type AnalogOutputFloat64

type AnalogOutputFloat64 struct {
	Value  float64
	Status CommandStatus
}

AnalogOutputFloat64 is a group 41 variation 4 control.

type AnalogOutputInt16

type AnalogOutputInt16 struct {
	Value  int16
	Status CommandStatus
}

AnalogOutputInt16 is a group 41 variation 2 control.

type AnalogOutputInt32

type AnalogOutputInt32 struct {
	Value  int32
	Status CommandStatus
}

AnalogOutputInt32 is a group 41 variation 1 control.

type AnalogOutputStatus

type AnalogOutputStatus struct {
	Value float64
	Flags Flags
	Time  Timestamp
}

AnalogOutputStatus reports the present value of an analog control point.

type Attribute added in v0.4.0

type Attribute struct {
	// Set is the attribute set, and Variation identifies the attribute within
	// it. Together they are the attribute's name on the wire.
	Set       uint8
	Variation uint8
	Type      AttributeType

	Text   string
	Number int64
	Real   float64
	Octets []byte
	Time   time.Time
}

Attribute is one thing a device says about itself.

Exactly one of the value fields carries the value, chosen by Type. They are separate fields rather than an interface because the overwhelmingly common thing to do with an attribute is print it, and the second most common is to read one number out of it.

func (Attribute) Name added in v0.4.0

func (a Attribute) Name() string

Name returns what the attribute is called, or a placeholder naming its number for one this package does not know.

func (Attribute) String added in v0.4.0

func (a Attribute) String() string

func (Attribute) Value added in v0.4.0

func (a Attribute) Value() string

Value renders the attribute's value as text, whatever its type.

type AttributeType added in v0.4.0

type AttributeType uint8

AttributeType is the encoding of an attribute's value, from the standard's attribute data type codes.

const (
	AttrVisibleString AttributeType = 1
	AttrUnsignedInt   AttributeType = 2
	AttrSignedInt     AttributeType = 3
	AttrFloat         AttributeType = 4
	AttrOctetString   AttributeType = 5
	AttrBitString     AttributeType = 6
	AttrTime          AttributeType = 7
)

Attribute data types.

func (AttributeType) String added in v0.4.0

func (t AttributeType) String() string

type Binary

type Binary struct {
	Value bool
	Flags Flags
	Time  Timestamp
}

Binary is a single-bit status input.

type BinaryOutputStatus

type BinaryOutputStatus struct {
	Value bool
	Flags Flags
	Time  Timestamp
}

BinaryOutputStatus reports the present state of a control point.

type Class

type Class uint8

Class is a bit mask over the DNP3 event classes plus class 0, the static data set. Masks are how polls are expressed: an integrity poll is Class0 | Class1 | Class2 | Class3.

const (
	Class0 Class = 1 << iota // static data
	Class1                   // event class 1, conventionally the most urgent
	Class2
	Class3
	// ClassNone assigns a point to no event class, suppressing its events.
	ClassNone Class = 0
	// Class123 is every event class, excluding static data.
	Class123 = Class1 | Class2 | Class3
	// ClassAll is an integrity poll: static data plus every event class.
	ClassAll = Class0 | Class123
)

Class mask bits.

func (Class) Has

func (c Class) Has(mask Class) bool

Has reports whether every class in mask is present.

func (Class) String

func (c Class) String() string

type CommandStatus

type CommandStatus uint8

CommandStatus is the outcome an outstation reports for a control operation.

const (
	CommandSuccess           CommandStatus = 0
	CommandTimeout           CommandStatus = 1
	CommandNoSelect          CommandStatus = 2
	CommandFormatError       CommandStatus = 3
	CommandNotSupported      CommandStatus = 4
	CommandAlreadyActive     CommandStatus = 5
	CommandHardwareError     CommandStatus = 6
	CommandLocal             CommandStatus = 7
	CommandTooManyOps        CommandStatus = 8
	CommandNotAuthorized     CommandStatus = 9
	CommandAutomationInhibit CommandStatus = 10
	CommandProcessingLimited CommandStatus = 11
	CommandOutOfRange        CommandStatus = 12
	CommandDownstreamLocal   CommandStatus = 13
	CommandAlreadyComplete   CommandStatus = 14
	CommandBlocked           CommandStatus = 15
	CommandCanceled          CommandStatus = 16
	CommandBlockedOtherMstr  CommandStatus = 17
	CommandDownstreamFail    CommandStatus = 18
	CommandNonParticipating  CommandStatus = 126
	CommandUndefined         CommandStatus = 127
)

Command status codes defined by the standard.

func (CommandStatus) OK

func (s CommandStatus) OK() bool

OK reports whether the command succeeded.

func (CommandStatus) String

func (s CommandStatus) String() string

type ControlCode

type ControlCode uint8

ControlCode is the operation field of a Control Relay Output Block. It packs an operation type in the low four bits with three modifier flags above.

const (
	ControlNUL      ControlCode = 0x00
	ControlPulseOn  ControlCode = 0x01
	ControlPulseOff ControlCode = 0x02
	ControlLatchOn  ControlCode = 0x03
	ControlLatchOff ControlCode = 0x04
)

Control operation types, occupying the low nibble of a ControlCode.

const (
	// ControlClose sets the close coil field.
	ControlClose ControlCode = 0x80
	// ControlTrip sets the trip coil field.
	ControlTrip ControlCode = 0x40
)

Trip/close modifiers, which pair with an operation type to drive the two coils of a breaker.

func (ControlCode) Clear

func (c ControlCode) Clear() bool

Clear reports whether the clear bit is set, which cancels a queued or running operation on the point.

func (ControlCode) IsClose

func (c ControlCode) IsClose() bool

IsClose reports whether the close coil is selected.

func (ControlCode) IsTrip

func (c ControlCode) IsTrip() bool

IsTrip reports whether the trip coil is selected.

func (ControlCode) OpType

func (c ControlCode) OpType() ControlCode

OpType returns the operation type nibble.

func (ControlCode) String

func (c ControlCode) String() string

type ControlRelayOutputBlock

type ControlRelayOutputBlock struct {
	Code ControlCode
	// Count is how many times to execute the operation. Zero is legal and
	// means "do nothing", which some masters use to probe a point.
	Count uint8
	// OnTime and OffTime are milliseconds, used by the pulse operations.
	OnTime  uint32
	OffTime uint32
	// Status is meaningful only on a response echo.
	Status CommandStatus
}

ControlRelayOutputBlock is a group 12 variation 1 control: the command used to operate breakers, reclosers and other discrete outputs.

func (ControlRelayOutputBlock) String

func (c ControlRelayOutputBlock) String() string

type Counter

type Counter struct {
	Value uint32
	Flags Flags
	Time  Timestamp
}

Counter is a running count. It wraps rather than saturating.

type DoubleBit

type DoubleBit uint8

DoubleBit is the state of a double-bit binary input, which reports a two-contact device — typically a breaker with separate open and closed auxiliary contacts — and can therefore distinguish a device in transit or miswired from one that is definitely open or definitely closed.

const (
	// DoubleBitIntermediate: both contacts read open. The device is moving.
	DoubleBitIntermediate DoubleBit = 0
	// DoubleBitDeterminedOff: the device is open.
	DoubleBitDeterminedOff DoubleBit = 1
	// DoubleBitDeterminedOn: the device is closed.
	DoubleBitDeterminedOn DoubleBit = 2
	// DoubleBitIndeterminate: both contacts read closed, which is impossible.
	DoubleBitIndeterminate DoubleBit = 3
)

Double-bit states. The numeric values are the on-the-wire encoding.

func (DoubleBit) String

func (d DoubleBit) String() string

type DoubleBitBinary

type DoubleBitBinary struct {
	Value DoubleBit
	Flags Flags
	Time  Timestamp
}

DoubleBitBinary is a two-bit status input.

type FileInfo added in v0.3.0

type FileInfo struct {
	// Name is the path the outstation knows the file by. In a directory
	// listing it is the entry's own name, not the full path.
	Name string
	Type FileType
	Size uint32
	// Created is the file's creation time. It is the zero time when the
	// outstation does not keep one, which many embedded devices do not.
	Created     time.Time
	Permissions FilePermissions
}

FileInfo describes one file or directory on an outstation.

func (FileInfo) IsDir added in v0.3.0

func (f FileInfo) IsDir() bool

IsDir reports whether the entry is a directory.

func (FileInfo) String added in v0.3.0

func (f FileInfo) String() string

type FileMode added in v0.3.0

type FileMode uint16

FileMode is what a file is opened for.

const (
	// FileModeNull opens nothing. It is what a command that names a file
	// without transferring it — a delete — carries.
	FileModeNull FileMode = 0
	// FileModeRead opens an existing file for reading.
	FileModeRead FileMode = 1
	// FileModeWrite creates or truncates a file for writing.
	FileModeWrite FileMode = 2
	// FileModeAppend opens a file for writing at its end.
	FileModeAppend FileMode = 3
)

File open modes.

func (FileMode) String added in v0.3.0

func (m FileMode) String() string

type FilePermissions added in v0.3.0

type FilePermissions uint16

FilePermissions are the access bits of a file, laid out as POSIX lays them out: three groups of read, write and execute.

const (
	PermWorldExecute FilePermissions = 0x0001
	PermWorldWrite   FilePermissions = 0x0002
	PermWorldRead    FilePermissions = 0x0004
	PermGroupExecute FilePermissions = 0x0008
	PermGroupWrite   FilePermissions = 0x0010
	PermGroupRead    FilePermissions = 0x0020
	PermOwnerExecute FilePermissions = 0x0040
	PermOwnerWrite   FilePermissions = 0x0080
	PermOwnerRead    FilePermissions = 0x0100
)

Permission bits, from IEEE 1815 table 4-7.

func (FilePermissions) String added in v0.3.0

func (p FilePermissions) String() string

String renders the bits the way ls does, so a directory listing from a device reads like one from a filesystem.

type FileStatus added in v0.3.0

type FileStatus uint8

FileStatus is the outcome an outstation reports for a file operation.

The set is the standard's, including the gap between 9 and 16: the codes are fixed by IEEE 1815 table 4-8 and are not renumbered here, because a capture has to be readable against the specification.

const (
	FileSuccess          FileStatus = 0
	FilePermissionDenied FileStatus = 1
	// FileInvalidMode means the requested mode is not one the outstation
	// allows for that file — writing a read-only configuration, say.
	FileInvalidMode FileStatus = 2
	FileNotFound    FileStatus = 3
	// FileLocked means another master has the file open.
	FileLocked      FileStatus = 4
	FileTooManyOpen FileStatus = 5
	// FileInvalidHandle means the handle is unknown, which is what a master
	// sees after the outstation has timed the transfer out.
	FileInvalidHandle  FileStatus = 6
	FileWriteBlockSize FileStatus = 7
	FileCommLost       FileStatus = 8
	FileCannotAbort    FileStatus = 9
	FileNotOpen        FileStatus = 16
	// FileHandleExpired means the outstation closed the file itself after the
	// master went quiet for longer than its inactivity timeout.
	FileHandleExpired FileStatus = 17
	FileBufferOverrun FileStatus = 18
	// FileFatal means the transfer failed for a reason the outstation cannot
	// describe more precisely; the file is not usable.
	FileFatal FileStatus = 19
	// FileBlockSequence means a block arrived out of order, which invalidates
	// everything written so far.
	FileBlockSequence FileStatus = 20
	FileUndefined     FileStatus = 255
)

File operation status codes.

func (FileStatus) Err added in v0.3.0

func (s FileStatus) Err() error

Err returns nil for FileSuccess and an error wrapping ErrFileTransfer otherwise, so a caller can test the outcome with errors.Is and still print what the outstation actually said.

func (FileStatus) String added in v0.3.0

func (s FileStatus) String() string

type FileType added in v0.3.0

type FileType uint16

FileType says whether a directory entry is a file or another directory.

const (
	FileTypeDirectory FileType = 0
	FileTypeSimple    FileType = 1
)

File types.

func (FileType) String added in v0.3.0

func (t FileType) String() string

type Flags

type Flags uint8

Flags is the quality octet that accompanies most measurements.

The low five bits mean the same thing for every measurement type. The upper three are type-specific: what is CHATTER_FILTER on a binary input is OVER_RANGE on an analog input and ROLLOVER on a counter. Because the interpretation depends on the point type, Flags stores the raw octet and leaves naming to the per-type accessors below.

const (
	// Online means the point is being read from the field. A cleared Online
	// bit is the single most important quality signal in DNP3: the value
	// present alongside it is not trustworthy.
	Online Flags = 0x01
	// Restart means the value has not been updated since the device restarted.
	Restart Flags = 0x02
	// CommLost means communication with the source of the point has failed.
	CommLost Flags = 0x04
	// RemoteForced means the value was forced by a downstream device.
	RemoteForced Flags = 0x08
	// LocalForced means the value was forced by the outstation itself.
	LocalForced Flags = 0x10
)

Flag bits common to every measurement type.

const (
	// ChatterFilter applies to binary and double-bit binary inputs: the point
	// is toggling faster than the outstation's filter allows.
	ChatterFilter Flags = 0x20
	// Rollover applies to counters. Deprecated by the standard in favour of
	// letting the counter wrap, but still emitted by older devices.
	Rollover Flags = 0x20
	// Discontinuity applies to counters: the value cannot be compared against
	// the previous reading.
	Discontinuity Flags = 0x40
	// OverRange applies to analog inputs and outputs: the value exceeds the
	// range the point can represent.
	OverRange Flags = 0x20
	// ReferenceErr applies to analog inputs and outputs: the reference used to
	// digitise the value is not accurate.
	ReferenceErr Flags = 0x40
	// StateBit carries the value itself for binary points inside a flags
	// octet, as in group 1 variation 2.
	StateBit Flags = 0x80
)

Type-specific bits. Each is valid only for the measurement types named.

func (Flags) Clear

func (f Flags) Clear(mask Flags) Flags

Clear returns f with every bit in mask cleared.

func (Flags) Has

func (f Flags) Has(mask Flags) bool

Has reports whether every bit in mask is set.

func (Flags) HasAny

func (f Flags) HasAny(mask Flags) bool

HasAny reports whether any bit in mask is set.

func (Flags) IsGood

func (f Flags) IsGood() bool

IsGood reports whether the value carrying these flags may be trusted: online, not restarting, not comm-lost, and not forced from either end.

func (Flags) Set

func (f Flags) Set(mask Flags) Flags

Set returns f with every bit in mask set.

func (Flags) String

func (f Flags) String() string

String renders the common bits by name. Type-specific bits are shown by position because their meaning depends on the point type; use Flags.StringFor when the type is known.

func (Flags) StringFor

func (f Flags) StringFor(t PointType) string

StringFor renders the flags with the upper bits named according to t.

type FrozenCounter

type FrozenCounter struct {
	Value uint32
	Flags Flags
	Time  Timestamp
}

FrozenCounter is a counter value captured at a freeze command.

type Indexed

type Indexed[T any] struct {
	Index uint32
	Value T
}

Indexed pairs a measurement with the point index it was reported at.

Index is 32 bits because that is what the protocol can carry: an object header may address points with a four-octet range or prefix. It was once 16, and a master narrowing what an outstation reported to fit it delivered point 70000 as point 4464 — a real measurement attributed to a different point, with nothing to say so.

type OctetString

type OctetString []byte

OctetString is a variable-length opaque value, group 110 and 111. The standard caps it at 255 octets.

type PointType

type PointType uint8

PointType identifies a measurement type, which fixes the meaning of the upper three quality bits.

const (
	TypeUnknown PointType = iota
	TypeBinary
	TypeDoubleBitBinary
	TypeCounter
	TypeFrozenCounter
	TypeAnalog
	TypeBinaryOutputStatus
	TypeAnalogOutputStatus
	TypeOctetString
)

Measurement types.

func (PointType) String

func (p PointType) String() string

type RestartMode

type RestartMode uint8

RestartMode selects between the two restart function codes.

const (
	// ColdRestart reinitialises the outstation completely, as though power
	// had been cycled.
	ColdRestart RestartMode = iota
	// WarmRestart reinitialises only the communications process.
	WarmRestart
)

Restart modes.

func (RestartMode) String

func (m RestartMode) String() string

type Timestamp

type Timestamp struct {
	Time    time.Time
	Quality TimestampQuality
}

Timestamp pairs a time with how much that time can be trusted.

The two are kept together deliberately. A DNP3 measurement can carry a perfectly well-formed timestamp from an outstation whose clock has never been set, and a consumer that ignores the quality will happily file that event under 1970 or under whatever the drifted clock says.

func NoTime

func NoTime() Timestamp

NoTime returns the timestamp used by measurements that carry no time.

func Now

func Now(t time.Time) Timestamp

Now returns a synchronized timestamp for t.

func Unsynchronized

func Unsynchronized(t time.Time) Timestamp

Unsynchronized returns a timestamp for t whose source clock was not synced.

func (Timestamp) IsValid

func (t Timestamp) IsValid() bool

IsValid reports whether the timestamp carries a usable time.

func (Timestamp) String

func (t Timestamp) String() string

type TimestampQuality

type TimestampQuality uint8

TimestampQuality describes how much a timestamp can be relied on.

const (
	// TimestampInvalid means the measurement carried no time at all. The
	// Timestamp's Time field is the zero value.
	TimestampInvalid TimestampQuality = iota
	// TimestampUnsynchronized means the outstation reported a time while its
	// own clock was not synchronised to the master.
	TimestampUnsynchronized
	// TimestampSynchronized means the outstation's clock was synchronised when
	// the measurement was taken.
	TimestampSynchronized
)

Timestamp qualities, in increasing order of trust.

func (TimestampQuality) String

func (q TimestampQuality) String() string

Directories

Path Synopsis
Package channel provides the physical layer beneath a DNP3 session: the thing that produces a byte stream and reproduces it after a failure.
Package channel provides the physical layer beneath a DNP3 session: the thing that produces a byte stream and reproduces it after a failure.
cmd
dnp3-decode command
Command dnp3-decode renders DNP3 octets as a decoded protocol tree.
Command dnp3-decode renders DNP3 octets as a decoded protocol tree.
dnp3-explorer command
Command dnp3-explorer is a terminal browser for a DNP3 outstation.
Command dnp3-explorer is a terminal browser for a DNP3 outstation.
dnp3-master command
Command dnp3-master is a SCADA client: it polls outstations, records what they report, and issues controls.
Command dnp3-master is a SCADA client: it polls outstations, records what they report, and issues controls.
dnp3-outstation command
Command dnp3-outstation is a simulated DNP3 outstation: a substation RTU with plant behind it.
Command dnp3-outstation is a simulated DNP3 outstation: a substation RTU with plant behind it.
Package decoder turns DNP3 octets into a structured trace.
Package decoder turns DNP3 octets into a structured trace.
internal
app
Package app implements the DNP3 application layer framing: the fragment header, function codes, internal indications, and the object headers with their qualifier, range and prefix encodings.
Package app implements the DNP3 application layer framing: the fragment header, function codes, internal indications, and the object headers with their qualifier, range and prefix encodings.
gen command
Command gen reads objects/spec/dnp3_objects.yaml and emits the object codecs, the descriptor registry, and the size table the framing layer needs.
Command gen reads objects/spec/dnp3_objects.yaml and emits the object codecs, the descriptor registry, and the size table the framing layer needs.
link
Package link implements the DNP3 data link layer: frame encoding and decoding, the CRC, and the primary and secondary station state machines.
Package link implements the DNP3 data link layer: frame encoding and decoding, the CRC, and the primary and secondary station state machines.
stack
Package stack couples the link and transport layers to a byte stream.
Package stack couples the link and transport layers to a byte stream.
transport
Package transport implements the DNP3 transport function: the single-octet layer that cuts application fragments into link-sized segments and puts them back together.
Package transport implements the DNP3 transport function: the single-octet layer that cuts application fragments into link-sized segments and puts them back together.
Package master implements the DNP3 master role: the station that polls outstations, receives their events, and issues commands.
Package master implements the DNP3 master role: the station that polls outstations, receives their events, and issues commands.
Package multidrop shares one channel between several DNP3 sessions.
Package multidrop shares one channel between several DNP3 sessions.
Package objects implements the DNP3 object group and variation codecs.
Package objects implements the DNP3 object group and variation codecs.
Package outstation implements the DNP3 outstation role: the device that holds measurements, answers a master's polls, and executes its commands.
Package outstation implements the DNP3 outstation role: the device that holds measurements, answers a master's polls, and executes its commands.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL