dnp3

package module
v0.3.1 Latest Latest
Warning

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

Go to latest
Published: Nov 7, 2026 License: Apache-2.0 Imports: 35 Imported by: 0

README

dnp3

DNP3 (IEEE Std 1815-2012) master and outstation in pure Go, with Secure Authentication version 5.

Go Reference test Go

The module has its own data link, transport and application layers, the object library, file transfer, data sets and device attributes, over TCP, TLS, UDP (including a fixed reply port) and serial lines, with several outstations on one link. Secure Authentication v5 works on both sides, with update key changes and g120v8 user certificates. It is built on the standard library and goburrow/serial, without cgo. It was written for SigmaM2M, a oneM2M platform, where it serves the DNP3 master and outstation integrations.

Features

Feature Status
Data link layer: block CRCs, resynchronisation, FCB/FCV, link reset, test, confirmed and unconfirmed user data, link status, self-address yes
Transport function: segments of 1 to 249 octets, repeats dropped yes
Application layer: every function code and qualifier of Table 4-6, sequence numbers, confirmations, multi-fragment and unsolicited responses yes
Objects: binary, double-bit, binary output, counters, frozen counters, analogs, frozen analogs, analog outputs, their events (absolute or relative time), command events, deadbands, BCD, octet strings, virtual terminal, device storage, security statistics yes; device profile objects (g0 sets other than 0) are carried as raw data
Outstation database: per-point classes, variations and deadbands; event modes (detect, force, suppress, event only); flags-only updates; event buffers bounded per class and per point type, losing the new or the oldest event on overflow yes (PointDB.UpdateWith, UpdateFlags, EventBuffer.SetTypeCapacity, SetDropOldest)
Gateway (data concentrator): downstream outstations' points republished on our outstation under other indexes, events passed with their time, controls passed down with their status, COMM_LOST on loss yes (gateway; tested with opendnp3 on both sides)
Device attributes (group 0, set 0) yes; other sets no
Time synchronisation (LAN and non-LAN) yes; the outstation never sets the host clock
Freeze, freeze-and-clear, freeze at time, assign class, restarts, application control, activate configuration yes
Sequential file transfer (both sides, with file authentication) yes; outstation answers are always immediate
Data sets (g85 to g88) yes
Pattern control blocks binary outputs (g12v3) only, as the standard defines
Channels: TCP, TLS, UDP (including a fixed reply port), serial lines; several outstations on one link (multi-drop) yes
Secure Authentication v5: session keys, challenge/reply, aggressive mode, HMAC-SHA-1/SHA-256 and AES-GMAC, the 18 statistics and their limits yes
Update key change: symmetric methods 3 to 5, asymmetric 67 to 71 with g120v8 user certificates yes
Update key authority a reference service (authority): user register, per-master tokens, the status change sequence number per outstation, certification and key encryption, g120v8 certificates (DSA), audit log; the master–authority protocol is not standardised, so ours (JSON over HTTP) talks only to this package
Secure Authentication v6 no
Broadcast outstation: per function code, critical requests only in verified aggressive mode under SA; master: never sends
Device profile document (XML) data points list and configuration items, written from a PointDB and read back into one (profile); not validated against the DNP Users Group schema, which is for members
Time-and-interval points (g50v4): class 0, reads, master writes yes
Deadbands on analogs, analog outputs, counters and frozen counters yes
A polling session on the master (RunSession): start-up sequence, periodic integrity polls and class scans, polls on IIN1.1 to 1.3, LAN or non-LAN time synchronisation when asked, start-up again after a restart yes; requests of the application's own (controls, files) take turns with it
A measurement model on the master (mapping points to names and units) no: the application's

Security services come in through iotsec: a TLS client or server profile, a dialer, an auditor for security events and an IEC 62351-8 role authority. Each is optional; without them the module uses the standard library and refuses only what needs the missing service (user certificates need a role authority).

Choosing a DNP3 library

Compared from each project's source and documentation at the versions named, in October 2026. "Tested" means run against this module in the interop tests below. Check the projects themselves before relying on a row.

this module opendnp3 3.1.2 stepfunc dnp3 1.7.0 Go alternatives
Language Go, no cgo C++11 (Java and .NET bindings) Rust (C, C++, Java, .NET bindings) none found with a master or an outstation
Licence Apache-2.0 Apache-2.0 non-commercial and non-production only; commercial licence for sale
Maintained yes no release since 2022 yes
Master and outstation both (tested against opendnp3) both both
TCP, TLS, UDP, serial all four, each tested against opendnp3 both ways all four (TLS through OpenSSL) all four (TLS through rustls)
Secure Authentication v5 yes, both sides (tested against opendnp3 2.1.0-RC5's experimental SAv5) no (an experimental version, in tags 2.1.0-RC1 to RC5, was moved out in RC6) no
File transfer both sides no (group 70 is only named) master: read and write files, directories
Device attributes (group 0) yes no yes
Data sets (groups 85 to 88) yes no no
Virtual terminal (groups 112, 113) yes: codec, outstation ports no (objects named only) no
Time-and-interval points (g50v4) yes (tested against opendnp3) yes not checked
Master scheduling (periodic scans, start-up sequence, automatic time sync) yes (RunSession; tested against opendnp3) yes yes
Event database with per-point configuration yes (PointDB: event modes, per-type buffer sizes, drop-oldest) yes yes
Gateway / data concentrator yes (gateway, tested with opendnp3 below and above) no (an application builds it from master and outstation) no (same)
Device profile XML write and read (profile) no no
Update key authority reference service (authority) no no
Simulated outstation for tests yes (dnp3-sim), with two vendor presets demo programs example programs

Pick this module for a Go program, for Secure Authentication v5, or for file transfer, data sets and virtual terminal ports on both sides. Pick stepfunc dnp3 when you can buy its commercial licence and want bindings for several languages. Pick opendnp3 for a C++ or .NET code base that already uses it and does not need Secure Authentication; it is no longer released. A Go master or outstation other than this one was not found (the Go DNP3 code that turns up in searches is scanner plugins that send one link-status request).

Install

go get github.com/cB-Sigma/dnp3@latest

Requires Go 1.24.0 or later.

Quick start

import "github.com/cB-Sigma/dnp3"

m, err := dnp3.Dial(ctx, dnp3.MasterConfig{URL: "tcp://10.0.0.9:20000", MasterAddress: 1, OutstationAddress: 10})
frags, err := m.Request(ctx, dnp3.FCRead, dnp3.ClassRequest(1, 2, 3, 0)) // integrity poll
for _, f := range frags {
	blocks, _ := dnp3.ParseObjects(f.Objects, true)
	// blocks[i].Points: group, variation, index, value, flags, time
}
latch, _ := dnp3.EncodeBlock(12, 1, dnp3.Q8Index8, []dnp3.Point{{Index: 0, CROB: dnp3.CROB{Code: dnp3.OpLatchOn, Count: 1}}})
err = m.SelectOperate(ctx, latch, 5*time.Second, nil)

A polling session (start-up sequence, scans, time synchronisation) in place of hand-made requests:

go m.RunSession(ctx, dnp3.SessionConfig{Unsolicited: []int{1, 2, 3}, TimeSync: dnp3.TimeSyncLAN,
	Integrity: time.Hour, Scans: []dnp3.ClassScan{{Classes: []int{1}, Period: 5 * time.Second}},
	OnData: func(f dnp3.Fragment) { /* ParseObjects(f.Objects, true) */ }})

A gateway: an outstation below republished at other indexes on our outstation, its breaker control passed down:

db := dnp3.NewPointDB()
g := gateway.New(db, nil)
src := &gateway.Source{Name: "feeder", Points: []gateway.Map{{Type: "analog", From: 0, To: 200, Class: 2}},
	Controls: []gateway.Control{{Group: 12, From: 0, To: 50}}}
_ = g.Add(src)
src.Master, _ = dnp3.Dial(ctx, dnp3.MasterConfig{URL: "tcp://10.0.0.9:20000", MasterAddress: 1, OutstationAddress: 10, OnUnsolicited: src.Data})
go src.Master.RunSession(ctx, dnp3.SessionConfig{Unsolicited: []int{1, 2, 3}, OnData: src.Data})
a := dnp3.NewAssociation(dnp3.OutstationConfig{Address: 20, Master: 100, DB: db, Handler: g.Handler(), Unsolicited: true})

With Secure Authentication, and TLS from crypto/tls (or an IEC 62351-3 profile implementing iotsec.TLSClient):

auth, _ := dnp3.NewAuth(&dnp3.AuthConfig{UpdateKey: updateKey, AggressiveMode: true})
m, err := dnp3.Dial(ctx, dnp3.MasterConfig{URL: "tcp://rtu.example:19999", MasterAddress: 1, OutstationAddress: 10,
	Auth: auth, TLS: iotsec.StdTLS{Config: tlsConfig}})
err = m.ChangeSessionKeys(ctx)

The runnable examples are ExampleNewAssociation (an outstation polled by the master) and ExampleEncodeBlock; dnp3-sim has a master against the simulated outstation.

Documentation

API reference: https://pkg.go.dev/github.com/cB-Sigma/dnp3

Package What it does
dnp3 (module root) Frames, segments and fragments; object encoding (EncodeBlock, ParseObjects); the master (Dial, OpenChannel, Master.Request, SelectOperate, ChangeSessionKeys, ChangeUpdateKey); the outstation (PointDB, NewAssociation, OutstationNet); Secure Authentication v5 (NewAuth, NewOutstationAuth); file transfer, data sets, device attributes
gateway Gateway, Source, Map, Control: downstream outstations onto one outstation database
profile device profile documents (XML): New, Write, Read, Document.Define
authority the update key authority as a service (Server) and the master's client (Client, a dnp3.Authority)
interop (nested module, tests only) this module's master and outstation against opendnp3 3.1.2 and, for Secure Authentication v5, opendnp3 2.1.0-RC5, in Docker

Testing

go vet ./...
go test -race ./...

Tests carry // Covers: SIG-... comments: these are requirement IDs of the SigmaM2M project's compliance report and have no meaning outside it. Code comments that cite F- numbers refer to SigmaM2M's register of readings where the standard or a vendor profile leaves a choice open; PROVENANCE.md explains them. The tests that drive the simulated outstation are in dnp3-sim, so this module does not depend on its simulator.

interop/ is a nested module with its own go.mod. It builds two reference images in Docker and runs this module against them: opendnp3 3.1.2 with OpenSSL (TLS) and socat (interop/peer), and opendnp3 2.1.0-RC5, the last opendnp3 tag that carried its experimental Secure Authentication v5 module, on Debian 9 with OpenSSL 1.0.2 (interop/peer-sa). The peer programs are ours; opendnp3 is built from its own sources inside the containers. The tests are skipped unless SIGMA_DNP3_INTEROP=1 is set and Docker is running:

cd interop && SIGMA_DNP3_INTEROP=1 go test -race -v ./...
Interop tested against

Run on 2026-10-11 on macOS with Docker Desktop; every item below passed. The v0.3.0 rows were added and run on 2026-10-12 against the same builds.

opendnp3 3.1.2 around this module's gateway (v0.3.0):

Area What was checked
Gateway opendnp3's outstation below, polled by our master (RunSession, unsolicited on); opendnp3's master above, against our outstation over the gateway's database: its start-up and integrity poll read the mapped points at the gateway's indexes; a binary change below arrives above as an unsolicited g2v2 event; CROB and g41v3 select-before-operate from opendnp3's master reach opendnp3's outstation at the source's indexes (SELECT, then SELECT and OPERATE) and are reported SUCCESS above; a lost source is read above as events with COMM_LOST and without ONLINE
Database opendnp3's master reads a forced event of an unchanged value and a flags-only change (flags 4) of our outstation as unsolicited events

opendnp3 3.1.2 (Apache-2.0), this module's master against opendnp3's outstation:

Area What was checked
Channels TCP; TLS 1.2 and TLS 1.3 with mutual certificates (crypto/tls against OpenSSL), and the connection refused when our certificate comes from a CA the outstation does not trust; UDP from a fixed local port; a serial line: this module opens a real pty whose bytes reach opendnp3's serial channel on its own pty through socat
Static data integrity poll with g1v2, g3v2, g10v2, g20v1, g21v5, g30v5, g40v3, g50v4, g110 (every type, decoded and checked); an integrity poll split by opendnp3 into fragments of at most 249 octets, each confirmed
Events g2v2, g2v3 and g4v3 after a common time of occurrence (g51), g4v2, g11v2, g22v1, g22v5, g23v5, g32v5, g32v7, g42v5, g42v7, g111; times within 5 s of the change; class polls return only their class; events cleared by our confirmation
Unsolicited enabled and confirmed; with our first confirmation dropped by a proxy, opendnp3 repeats the response with the same sequence number, we confirm the repeat and the event is cleared
Functions clear restart; non-LAN and LAN time synchronisation (to the millisecond, IIN1.4 cleared); immediate freeze, freeze and clear, freeze without response; cold and warm restart (g52v2 1500 ms, g52v1 3 s); assign class; WRITE of g50v4
Controls CROB select-before-operate, direct operate and pulse (count, on and off times); analog outputs g41v1, g41v2, g41v3 and g41v4; a CROB and an analog output in one select-before-operate
Link layer confirmed user data (one link reset, FCB alternating, every frame acknowledged); REQUEST_LINK_STATUS both ways (our keep-alive answered, opendnp3's answered by us)
Polling session RunSession start-up (disable unsolicited, clear restart, integrity, LAN or non-LAN time synchronisation, enable unsolicited), unsolicited events, a periodic class 3 scan

opendnp3 3.1.2, opendnp3's master against this module's outstation:

Area What was checked
Channels TCP; TLS 1.2 and 1.3 (OpenSSL client, our crypto/tls server requiring client certificates); UDP; a serial line on a real pty here
Start-up opendnp3's sequence in non-LAN and LAN time-sync modes: disable unsolicited, our null unsolicited response confirmed, clear restart, integrity poll, time synchronisation, enable unsolicited
Data static g1v2, g3v2, g10v2, g20v1, g21v1, g30v5, g40v3, g50v4, g110; unsolicited events g2v2, g4v2, g11v2, g22v5, g42v7, g111 with their times to the millisecond; frozen counters after IMMED_FREEZE, FREEZE_CLEAR and IMMED_FREEZE_NR, read back as g21
Functions WRITE of g50v4 and a range read of it; cold and warm restart; integrity and class polls
Controls CROB select-before-operate, direct operate and pulse; analog outputs g41v1 to g41v4; a CROB and an analog output in one request; each reported SUCCESS by opendnp3 and seen by our handler with the values sent
Link layer and loss opendnp3's REQUEST_LINK_STATUS keep-alive answered; with opendnp3's confirmation of our unsolicited response dropped, we repeat it with the same sequence number and the event is cleared once confirmed

opendnp3 2.1.0-RC5 (Apache-2.0; its SAv5 module was marked experimental), Secure Authentication v5:

Direction What was checked
Our master, opendnp3's outstation session key status and session key change; challenges of critical requests answered with HMAC-SHA-256/16, HMAC-SHA-256/8, HMAC-SHA-1/10 and HMAC-SHA-1/8; AES-128 and AES-256 key wrap; direct operate and select-before-operate carried out after the challenge; every request challenged (the outstation's "authenticate everything" setting); a wrong update key refused with error 1
opendnp3's master, our outstation opendnp3's session key change, start-up and integrity poll; a CROB direct operate and an analog output challenged by us, answered by opendnp3 and carried out for user 1; MAC algorithms HMAC-SHA-256/16, HMAC-SHA-1/10, HMAC-SHA-256/8; AES-128 and AES-256 key wrap
Aggressive mode opendnp3's outstation does not take aggressive mode: it challenges the request and then refuses its g120v3 object with IIN2.2. Our master falls back to challenges at once and the operation is carried out exactly once (tested). Aggressive mode itself is checked only against this module's own outstation

Defects of the reference peers met on the way (not ours; recorded so nobody chases them): opendnp3 3.1.2's master reads g52v1 (seconds) as milliseconds when reporting a restart time; its UDP channel loses its bound local port when the remote is given as a host name (asio's range connect reopens the socket), so the peer programs resolve names first; its master reports g111 octet string events with the event flag clear.

Not tested against an independent implementation:

  • File transfer (group 70): opendnp3 only names the objects; stepfunc dnp3 (master only) was not run because of its licence.
  • Data sets (groups 85 to 88), device attributes (group 0), virtual terminal ports: neither opendnp3 version implements them.
  • Secure Authentication update key change (symmetric and asymmetric), g120v8 user certificates and aggressive mode: opendnp3 2.1.0-RC5 has a symmetric update key change on its master, not run here; only this module's own master and outstation exercise them.
  • The device profile documents (no open reader of the schema was found) and the authority service (its protocol is ours alone).
  • Real serial hardware (ptys and socat stand in), multi-drop serial lines with several outstations, IPv6.
  • stepfunc dnp3 was not run: its licence allows only non-commercial, non-production use.
  • In process: the simulated outstation and the vendor presets of dnp3-sim (same author, so not independent).

Defects of this module found by these tests and fixed in v0.2.0 are in CHANGELOG.md.

  • dnp3-sim: a simulated outstation with fault switches and two vendor presets, as a Go package and a command.
  • iotsec: the security seams.

Status

v0.x: the API may change before v1.

Contributing

See CONTRIBUTING.md.

Security

See SECURITY.md to report a vulnerability privately.

Licence

Apache-2.0 (see LICENSE and NOTICE). Copyright 2026 Abhinav Gautam.

Documentation

Overview

Package dnp3 is DNP3 (IEEE Std 1815-2012) in pure Go: a master and an outstation over TCP (optionally TLS), UDP or a serial line, with their own data link, transport and application layers, the object library, and Secure Authentication version 5 on both sides.

The layers:

  • Data link (link.go, clause 9): frames with block CRCs, a parser that resynchronises after noise, primary and secondary stations with FCB/FCV state, confirmed and unconfirmed user data, link status.
  • Transport (transport.go, clause 8): segmenting and reassembly.
  • Application (app.go, objects.go, clauses 4 to 6 and 11, Annex A): fragments, function codes, qualifiers, internal indications, and object encoding and decoding (static, event, command, frozen, octet string, file, data set, device attribute and security objects).
  • Master (master.go): Dial a MasterConfig, then Request, SelectOperate, ChangeSessionKeys and ChangeUpdateKey; unsolicited responses are confirmed and handed to OnUnsolicited.
  • Outstation (outstation.go, outdb.go, outfile.go, outnet.go): a PointDB with per-class event buffers (EventStore makes them durable), an Association per master that answers polls, controls, time synchronisation, freeze, file transfer, data sets and unsolicited reporting through a Handler, and an OutstationNet that routes frames from streams (TCP, TLS, serial) and UDP sockets.
  • Secure Authentication v5 (auth.go, ostauth.go, updatekey.go, gmac.go, usercert.go, clause 7): session keys, challenges and replies, aggressive mode, the security statistics and their limits, update key change (symmetric and asymmetric), and g120v8 user certificates read through an IEC 62351-8 role authority.

Security services come in through the small interfaces of github.com/cB-Sigma/iotsec: a TLS client or server profile (MasterConfig.TLS; the outstation's listener is the caller's), a Dialer, an Auditor for security events (SetAuditor) and a RoleAuthority for user certificates (OutstationAuthConfig.Roles). Each is optional.

A simulated outstation for tests, with a command that serves it, is the separate module github.com/cB-Sigma/dnp3-sim.

Index

Examples

Constants

View Source
const (
	ActivateOK         = 0
	ActivateBadObject  = 1
	ActivateBadConfig  = 2
	ActivateOtherError = 3
	ActivateNotChecked = 4
)

Activate configuration status codes.

View Source
const (
	AppFIR = 0x80
	AppFIN = 0x40
	AppCON = 0x20
	AppUNS = 0x10
)

Application control bits; the low four bits are the sequence number.

View Source
const (
	FCConfirm            = 0
	FCRead               = 1
	FCWrite              = 2
	FCSelect             = 3
	FCOperate            = 4
	FCDirectOperate      = 5
	FCDirectOperateNR    = 6
	FCImmedFreeze        = 7
	FCImmedFreezeNR      = 8
	FCFreezeClear        = 9
	FCFreezeClearNR      = 10
	FCFreezeAtTime       = 11
	FCFreezeAtTimeNR     = 12
	FCColdRestart        = 13
	FCWarmRestart        = 14
	FCInitializeData     = 15 // obsolete
	FCInitializeAppl     = 16
	FCStartAppl          = 17
	FCStopAppl           = 18
	FCSaveConfig         = 19 // deprecated
	FCEnableUnsolicited  = 20
	FCDisableUnsolicited = 21
	FCAssignClass        = 22
	FCDelayMeasure       = 23
	FCRecordCurrentTime  = 24
	FCOpenFile           = 25
	FCCloseFile          = 26
	FCDeleteFile         = 27
	FCGetFileInfo        = 28
	FCAuthenticateFile   = 29
	FCAbortFile          = 30
	FCActivateConfig     = 31
	FCAuthRequest        = 32
	FCAuthRequestNoAck   = 33
	FCResponse           = 129
	FCUnsolicited        = 130
	FCAuthResponse       = 131
)

Application function codes (4.2.2.5, Table 4-2). Requests use 0 to 128 (34 to 128 reserved), responses 129 to 255 (132 to 255 reserved).

View Source
const (
	Q8StartStop  = 0x00
	Q16StartStop = 0x01
	Q32StartStop = 0x02
	QVAddr8      = 0x03 // virtual address ranges (group 102)
	QVAddr16     = 0x04
	QVAddr32     = 0x05
	QAll         = 0x06
	Q8Count      = 0x07
	Q16Count     = 0x08
	Q32Count     = 0x09
	Q8Index8     = 0x17 // 1-octet count, 1-octet index prefix
	Q8Index16    = 0x18 // 2-octet count, 1-octet index prefix
	Q8Index32    = 0x19
	Q16Index8    = 0x27
	Q16Index16   = 0x28 // 2-octet count, 2-octet index prefix
	Q16Index32   = 0x29
	Q32Index8    = 0x37
	Q32Index16   = 0x38
	Q32Index32   = 0x39
	QSize8       = 0x4B // 1-octet count, 1-octet object size prefix
	QFreeFormat  = 0x5B // 1-octet count, 2-octet object size prefix
	QSize32      = 0x6B
)

Object header qualifiers (4.2.2.7.3): bit 7 reserved (0), bits 6-4 the object prefix code, bits 3-0 the range specifier code. Every code of Table 4-6 is parsed.

View Source
const (
	AttrVSTR = 1 // visible string
	AttrUINT = 2 // unsigned integer, 1–4 octets
	AttrINT  = 3 // signed integer, 1–4 octets
	AttrFLT  = 4 // IEEE-754 float, 4 or 8 octets
	AttrOSTR = 5 // octet string
	AttrBSTR = 6 // bit string
	// AttrList and AttrExtList are lists of UINT8-BSTR8 pairs (the list of
	// attribute variations, g0v255); an extended list is 256 octets plus
	// its length octet long (5.5.4.3, Table 5-16).
	AttrList    = 254
	AttrExtList = 255
)

Attribute data type codes decoded here.

View Source
const (
	AuthChallenge         = 1
	AuthReply             = 2
	AuthAggressive        = 3
	AuthKeyStatusReq      = 4
	AuthKeyStatus         = 5
	AuthSessionKeyChg     = 6
	AuthErrorObj          = 7
	AuthUserCertificate   = 8
	AuthMACObj            = 9
	AuthUserStatusChange  = 10
	AuthUpdateKeyChgReq   = 11
	AuthUpdateKeyChgReply = 12
	AuthUpdateKeyChg      = 13
	AuthUpdateKeyChgSig   = 14
	AuthUpdateKeyChgConf  = 15

	AuthReasonCritical = 1
	KeyWrapAES128      = 1
	KeyWrapAES256      = 2
	KeyStatusOK        = 1
	KeyStatusNotInit   = 2
	KeyStatusCommFail  = 3
	KeyStatusAuthFail  = 4
	// UserUnknown and UserDefault are the reserved user numbers (Table 7-11).
	UserUnknown = 0
	UserDefault = 1
)

Group 120 variations.

View Source
const (
	AuthErrAuthFailed         = 1
	AuthErrUnexpectedResponse = 2 // SAv2 only
	AuthErrNoResponse         = 3 // SAv2 only
	AuthErrAggressiveNotSupp  = 4
	AuthErrMACNotSupp         = 5
	AuthErrKeyWrapNotSupp     = 6
	AuthErrAuthorization      = 7
	AuthErrUpdateMethod       = 8
	AuthErrInvalidSignature   = 9
	AuthErrInvalidCertData    = 10
	AuthErrUnknownUser        = 11
	AuthErrMaxKeyStatus       = 12
)

Authentication error codes (g120v7, A.45.7).

View Source
const (
	StatUnexpected = iota
	StatAuthorizationFailures
	StatAuthFailures
	StatReplyTimeouts
	StatRekeysAuthFailure
	StatMessagesSent
	StatMessagesReceived
	StatCriticalSent
	StatCriticalReceived
	StatDiscarded
	StatErrorsSent
	StatErrorsReceived
	StatSuccessfulAuth
	StatSessionKeyChanges
	StatFailedSessionKeyChanges
	StatUpdateKeyChanges
	StatFailedUpdateKeyChanges
	StatRekeysRestart
	NumSecurityStatistics // 18 in this version (7.5.2.2)
)

Security statistics (Table 7-6): the point indexes of g121/g122; the master keeps the same counters for its side of the association.

View Source
const (
	MACSHA1Trunc4    = 1
	MACSHA1Trunc10   = 2
	MACSHA256Trunc8  = 3
	MACSHA256Trunc16 = 4
	MACSHA1Trunc8    = 5
	MACAESGMAC       = 6
)

MAC algorithm codes (g120 MAL) this implementation computes. Code 1 (HMAC-SHA-1 truncated to 4 octets) was SAv2 only and code 6 (AES-GMAC) is optional; neither is computed, so a challenge naming one fails.

View Source
const (
	DescID   = 1
	DescUUID = 2
	DescNSPC = 3
	DescNAME = 4
	DescDAEL = 5
	DescPTYP = 6
	DescCTLV = 7
	DescCTLS = 8
)

Descriptor element codes (Table 5-7).

View Source
const (
	DSNone = 0
	DSVSTR = 1
	DSUINT = 2
	DSINT  = 3
	DSFLT  = 4
	DSOSTR = 5
	DSBSTR = 6
	DSTIME = 7
	DSUNCD = 8
)

Data set data type codes (Table 5-6).

View Source
const (
	DSReadable = 0x01
	DSWritable = 0x02
	DSStatic   = 0x04
	DSEvent    = 0x08
	DSByMaster = 0x10
)

Characteristics of a data set (g86v2): RD, WR, ST, EV, DF.

View Source
const (
	SecEventBroadcastRefused     = "dnp3_broadcast_refused"
	SecEventMaxAuthFailures      = "dnp3_max_authentication_failures"
	SecEventMaxReplyTimeouts     = "dnp3_max_reply_timeouts"
	SecEventMaxAuthRekeys        = "dnp3_max_authentication_rekeys"
	SecEventMaxRekeysDueRestarts = "dnp3_max_rekeys_due_to_restarts"
	SecEventMaxErrorMessages     = "dnp3_max_error_messages_sent"
)

Security events of Secure Authentication that go to the application's security log (through SetAuditor) as well as the slog log: refused broadcasts, and the statistic limits of Table 7-6 when they act (7.5.2.2; IEEE 1815-2012 7.7.1 asks that dropping a connection be logged). No key material is ever included.

View Source
const (
	FileModeNone   = 0 // delete, get info
	FileModeRead   = 1
	FileModeWrite  = 2
	FileModeAppend = 3
)

File operational modes of a file command (g70v3).

View Source
const (
	CtrlDIR = 0x80 // set on frames sent by a master
	CtrlPRM = 0x40 // set on primary (initiating) frames
	CtrlFCB = 0x20
	CtrlFCV = 0x10 // DFC on secondary frames
)

Link control octet bits.

View Source
const (
	LinkResetLinkStates     = 0
	LinkTestLinkStates      = 2
	LinkConfirmedUserData   = 3
	LinkUnconfirmedUserData = 4
	LinkRequestLinkStatus   = 9

	LinkAck          = 0
	LinkNack         = 1
	LinkStatus       = 11
	LinkNotSupported = 15
)

Link function codes. Primary codes are used with CtrlPRM, secondary codes without it.

View Source
const (
	AddrMaxIndividual      = 0xFFEF
	AddrSelf               = 0xFFFC  // self-address: optional, only as a destination
	AddrBroadcastNoConfirm = 0xFFFD  // application confirmation not required to clear IIN1.0
	AddrBroadcastMandatory = 0xFFFE  // application confirmation mandatory to clear IIN1.0
	AddrBroadcastOptional  = 0xFFFF  // application confirmation optional to clear IIN1.0
	CtrlDFC                = CtrlFCV // data flow control, secondary frames

)

Special addresses (9.2.5.2, Table 9-3). Individual addresses are 0x0000 to 0xFFEF; 0xFFF0 to 0xFFFB are reserved and frames sent to them are discarded.

View Source
const (
	OpNul      = 0
	OpPulseOn  = 1
	OpPulseOff = 2
	OpLatchOn  = 3
	OpLatchOff = 4

	TCCClose = 0x40
	TCCTrip  = 0x80
)

CROB operation types (low nibble of the control code) and trip/close codes (top two bits).

View Source
const (
	PermRead  = 0x124
	PermWrite = 0x092
	PermExec  = 0x049
)

Permission bits of every class (owner, group, world: 5.3.2 keeps them equal).

View Source
const (
	UnitsNoRepeat     uint8 = 0
	UnitsMilliseconds uint8 = 1
	UnitsSeconds      uint8 = 2
	UnitsMinutes      uint8 = 3
	UnitsHours        uint8 = 4
	UnitsDays         uint8 = 5
	UnitsWeeks        uint8 = 6
	// Months on the same day of the month, the same weekday counted from
	// the start of the month, and the same weekday counted from its end.
	UnitsMonthsDay        uint8 = 7
	UnitsMonthsWeekday    uint8 = 8
	UnitsMonthsWeekdayEnd uint8 = 9
	UnitsSeasons          uint8 = 10
)

Units of a time-and-interval point (the codes of g50v4).

View Source
const (
	TransportFIN = 0x80
	TransportFIR = 0x40
)

Transport header bits.

View Source
const (
	KeyChangeAES128SHA1    = 3
	KeyChangeAES256SHA256  = 4 // the mandatory default
	KeyChangeAES256GMAC    = 5
	KeyChangeRSA1024SHA1   = 67
	KeyChangeRSA2048SHA256 = 68
	KeyChangeRSA3072SHA256 = 69
	KeyChangeRSA2048GMAC   = 70
	KeyChangeRSA3072GMAC   = 71
)

Update key change methods (Table 7-16). 0 to 2 and 64 to 66 are obsolete or unused.

View Source
const (
	UserAdd    = 1
	UserDelete = 2
	UserChange = 3
)

User status change operations (A.45.10).

View Source
const (
	RoleViewer     = 0
	RoleOperator   = 1
	RoleEngineer   = 2
	RoleInstaller  = 3
	RoleSecAdm     = 4
	RoleSecAud     = 5
	RoleRBACMnt    = 6
	RoleSingleUser = 32768
)

User roles (Table 7-12).

View Source
const (
	CertID        = 1
	CertAttribute = 2
)

Certificate types of g120v8.

View Source
const AttrAll = 254

AttrAll is the group 0 variation that reads every attribute.

View Source
const MaxLinkData = 250

MaxLinkData is the most user data one link frame carries.

View Source
const MaxSegment = MaxLinkData - 1

MaxSegment is the most application octets one transport segment carries.

Variables

View Source
var (
	ErrFileNotFound = errors.New("file not found")
	ErrFileDenied   = errors.New("file access denied")
)

ErrFileNotFound is returned by a FileStore for a name it does not hold, ErrFileDenied for one the requestor may not use.

View Source
var (
	OIDIECUserRoles       = asn1.ObjectIdentifier{1, 0, 62351, 8, 1}
	OIDIECUserRolesLegacy = asn1.ObjectIdentifier{1, 2, 840, 10070, 8, 1}
)

OIDIECUserRoles is the IEC 62351-8:2020 access token OID (10.3), used when issuing; OIDIECUserRolesLegacy (IEC/TS 62351-8:2011) is accepted too (10.5.1.2).

View Source
var ErrAuthLimit = errors.New("secure authentication: max authentication rekeys exceeded; closing the connection")

ErrAuthLimit ends a session: the authentication failures passed Max Authentication Failures while rekeys due to them passed Max Authentication Rekeys (Table 7-8: close the TCP connection and log).

View Source
var ErrLinkCRC = errors.New("link frame CRC mismatch")

ErrLinkCRC is reported through LinkParser.Errors; it is exported for callers that decode single frames with DecodeFrame.

View Source
var ErrNoRoleAuthority = errors.New("no IEC 62351-8 role authority is configured to read user certificates")

ErrNoRoleAuthority refuses a g120v8 user certificate when no IEC 62351-8 role authority is configured to read its roles.

View Source
var ErrTimeout = errors.New("no response")

ErrTimeout is wrapped by errors for requests the outstation did not answer in time.

View Source
var OpenSerial = func(dev string, baud int, parity string) (io.ReadWriteCloser, error) {
	p, err := serial.Open(&serial.Config{Address: dev, BaudRate: baud, DataBits: 8, StopBits: 1, Parity: parity, Timeout: 200 * time.Millisecond})
	if err != nil {
		return nil, err
	}
	return &lockedPort{p: p}, nil
}

OpenSerial opens a serial line (8 data bits, 1 stop bit; parity "N", "E" or "O"). The master and the outstation's serial channel use it; tests, or an application that reaches its ports another way, replace it.

Functions

func ActivateStatusName

func ActivateStatusName(c uint8) string

ActivateStatusName names an activate configuration status code.

func AppendAttribute

func AppendAttribute(out []byte, variation uint8, set uint8, value any) []byte

AppendAttribute appends one group 0 object (header with the attribute set as a one-octet start-stop range, then type, length and value). A string value is sent as VSTR, a float64 with a fraction or outside the 32-bit range as FLT (8 octets), other numbers as UINT or INT in 4 octets, and []byte as OSTR.

func AppendAuthObject

func AppendAuthObject(out []byte, variation uint8, data []byte) []byte

AppendAuthObject appends one group 120 object with the free-format qualifier (count 1, two-octet size) or, for g120v4, a count of one.

func AppendElements

func AppendElements(out []byte, els []DescElement) []byte

AppendElements encodes descriptor elements.

func AppendHeader

func AppendHeader(out []byte, group, variation, qual uint8, a, b uint32) []byte

AppendHeader appends an object header. For start-stop and virtual address qualifiers a and b are the start and stop; for count, index- and size-prefix qualifiers a is the count; QAll takes neither.

func AppendKeyStatus

func AppendKeyStatus(out []byte, k KeyStatus) []byte

AppendKeyStatus encodes g120v5 object data.

func AppendTime48

func AppendTime48(out []byte, t time.Time) []byte

AppendTime48 appends t as a time48.

func AssignClass

func AssignClass(sets []ClassAssignment) []byte

AssignClass builds the objects of an ASSIGN_CLASS request: per set a group 60 header with qualifier 0x06 (variation 1 for class 0, 2 to 4 for classes 1 to 3) followed by its point headers.

func AttributeName

func AttributeName(variation uint8) string

AttributeName is the key a group 0 variation is reported under.

func AuthErrorName

func AuthErrorName(c uint8) string

AuthErrorName names an authentication error code.

func CRC

func CRC(b []byte) uint16

CRC is the DNP3 link-layer CRC of b (initial value 0, complemented result). It is sent least significant octet first.

func CRCTableEntry

func CRCTableEntry(i byte) uint16

CRCTableEntry is entry i of the CRC look-up table (Annex E).

func CTLSStatus

func CTLSStatus(v []byte) uint8

CTLSStatus is the status code of a CTLS element (as in g12v1).

func CertifyStatus

func CertifyStatus(method uint8, key []byte, master uint16, u UserStatus) ([]byte, error)

CertifyStatus is the symmetric certification data of a user status (Table A-10): the method's MAC of the status information with the authority certification key; for AES-GMAC the IV is the master's address, user number 0 (not yet assigned), the SCS and CSQ 0 (Table 7-21 row 10, F-593).

func ChallengeLen

func ChallengeLen(method uint8) int

ChallengeLen is the challenge data size of a method (Table 7-17).

func ClassRequest

func ClassRequest(classes ...int) []byte

ClassRequest is the object part of a READ (or unsolicited enable or disable) of the given classes, 0 to 3, with qualifier "all".

func ConfirmationMAC

func ConfirmationMAC(method uint8, newKey []byte, receiver string, senderChal, receiverChal []byte, ksq uint32, usr uint16) ([]byte, error)

ConfirmationMAC is the g120v15 MAC (Table A-13): the receiver's name, the sender's then the receiver's challenge data, the KSQ and the user number, keyed with the new update key.

func ConfirmationMACIV

func ConfirmationMACIV(method uint8, newKey, iv []byte, receiver string, senderChal, receiverChal []byte, ksq uint32, usr uint16) ([]byte, error)

ConfirmationMACIV is ConfirmationMAC with the AES-GMAC IV of the GMAC methods (Table 7-21: sender, user, last KSQ, last CSQ).

func CriticalMandatory

func CriticalMandatory(fc byte) bool

CriticalMandatory lists the request function codes Table 7-7 marks mandatory critical; the master sends these in aggressive mode.

func DSASign

func DSASign(method uint8, key *dsa.PrivateKey, data []byte) ([]byte, error)

DSASign signs data with the method's hash (SHA-1 for 67, SHA-256 otherwise), DER encoded.

func DSAVerify

func DSAVerify(method uint8, key *dsa.PublicKey, data, sig []byte) bool

DSAVerify checks a DSASign signature.

func DataSetType

func DataSetType(name string) (uint8, bool)

DataSetType maps a data type name (vstr, uint, ...) to its code.

func DataSetValue

func DataSetValue(typ uint8, v []byte) (any, error)

DataSetValue decodes one element value by its data type.

func EncodeBlock

func EncodeBlock(group, variation, qual uint8, pts []Point, cto ...time.Time) ([]byte, error)

EncodeBlock encodes an object header and its objects. Start-stop qualifiers need consecutive indices; relative-time objects (g2v3, g4v3) take their offset from the preceding CTO given as cto.

Example

Encoding an object block and reading it back.

package main

import (
	"fmt"

	"github.com/cB-Sigma/dnp3"
)

func main() {
	b, _ := dnp3.EncodeBlock(30, 2, dnp3.Q8StartStop, []dnp3.Point{{Index: 3, Value: 1234.0, Flags: 0x01}})
	fmt.Printf("% X\n", b)
	blocks, _ := dnp3.ParseObjects(b, true)
	fmt.Println(blocks[0].Points[0].Value)
}
Output:
1E 02 00 03 03 01 D2 04
1234

func EncodeDataSetValue

func EncodeDataSetValue(e DescElement, v any) ([]byte, error)

EncodeDataSetValue encodes a value for an element (maximum length and type from the descriptor): strings for VSTR/UNCD, hex strings for OSTR and BSTR, numbers for UINT/INT/FLT, time.Time or RFC 3339 for TIME.

func EncodePointRefs

func EncodePointRefs(id uint32, refs []PointRef) []byte

EncodePointRefs is a g86v3 object's data.

func Family

func Family(group uint8) string

Family is the point type a group reports: binary, double, binary-output, counter, frozen-counter, analog or analog-output; empty for groups that are not data points.

func FilePath

func FilePath(name string) (string, bool)

FilePath turns a request's file name into a store path: "\" and "/" both separate segments; "." and ".." are refused.

func FileStatusName

func FileStatusName(s uint8) string

FileStatusName names a file status code.

func FuncName

func FuncName(fc byte) string

FuncName is a readable name for a function code.

func GMAC

func GMAC(key, iv []byte, parts ...[]byte) ([]byte, error)

GMAC is the 12-octet AES-GMAC tag of the concatenated parts.

func GMACIV

func GMACIV(sender, usr uint16, ksq, csq uint32) []byte

GMACIV builds the 12-octet IV.

func GMACMethod

func GMACMethod(method uint8) bool

GMACMethod reports the methods authenticating with AES-GMAC (5, 70, 71).

func IsBroadcast

func IsBroadcast(a uint16) bool

IsBroadcast reports the three broadcast addresses (9.2.5.2.1).

func IsReservedAddress

func IsReservedAddress(a uint16) bool

IsReservedAddress reports 0xFFF0 to 0xFFFB, which no frame may be passed up for (9.2.5.2.3).

func IssueDSAUserCertificate

func IssueDSAUserCertificate(method uint8, key *dsa.PrivateKey, issuer string, user string, spki []byte, roles []UserRoleInfo, notBefore, notAfter time.Time) ([]byte, error)

IssueDSAUserCertificate issues an ID certificate for user (public key SubjectPublicKeyInfo spki) with the given roles, signed with the authority's DSA key (SHA-1 for method 67, SHA-256 otherwise).

func Known

func Known(group, variation uint8) bool

Known reports whether a group/variation can be decoded.

func MAC

func MAC(mal uint8, key []byte, parts ...[]byte) ([]byte, error)

MAC computes the truncated HMAC of algorithm mal over the parts.

func MACByName

func MACByName(name string) uint8

MACByName is the MAC algorithm code of a name: hmac-sha256-16, hmac-sha256-8, hmac-sha1-10, hmac-sha1-8 or aes-gmac; 0 for any other.

func MACIV

func MACIV(mal uint8, key, iv []byte, parts ...[]byte) ([]byte, error)

MACIV computes the MAC of algorithm mal; iv is used by AES-GMAC only.

func MACLen

func MACLen(mal uint8) int

MACLen is the MAC length of an algorithm (0 when not computed here).

func MarshalDSAPublicKey

func MarshalDSAPublicKey(pub *dsa.PublicKey) ([]byte, error)

MarshalDSAPublicKey encodes a DSA public key as an X.509 SubjectPublicKeyInfo (RFC 3279 2.3.2), the form a g120v10 carries (A.45.10); the standard library parses but does not write it.

func MethodHash

func MethodHash(method uint8) (func() hash.Hash, int, error)

MethodHash is the hash of a method (its HMAC, or the digest its DSA signatures use) and the update key length.

func NoResponse

func NoResponse(fc byte) bool

NoResponse reports the request function codes the outstation never answers (the _NR variants and AUTH_REQUEST_NO_ACK), plus CONFIRM.

func ObjectSize

func ObjectSize(group, variation uint8) int

ObjectSize is the octets per object of a fixed-size, non-packed variation (0 for packed, free-format and unknown ones).

func PackedBits

func PackedBits(group, variation uint8) int

PackedBits is the bits per point of a packed variation (0 otherwise).

func ParseAuthObject

func ParseAuthObject(b []byte) (uint8, []byte, error)

ParseAuthObject returns the variation and object data of the first group 120 object in a fragment's objects.

func ParseSessionKeyData

func ParseSessionKeyData(b []byte) (ctrl, mon, keyStatus []byte, err error)

ParseSessionKeyData splits unwrapped g120v6 key data; keyStatus is the key status data the master claims to answer (padding included).

func PointGroup

func PointGroup(typ string) ([2]uint8, bool)

Point types an outstation serves: the static group and the event group. PointGroup is the static and event group of a point type ("binary", "analog", ...; the keys of the outstation database's point types).

func PointTypeOf

func PointTypeOf(group uint8) string

PointTypeOf is the point type of a static or event group.

func Segment

func Segment(apdu []byte, seq *uint8) [][]byte

Segment splits an application fragment into transport segments of 1 to 249 application octets (8.2.2). seq is the sender's running sequence number and is advanced per segment.

func SelfSignedDSAAuthority

func SelfSignedDSAAuthority(key *dsa.PrivateKey, name string, notBefore, notAfter time.Time) ([]byte, error)

SelfSignedDSAAuthority is an authority certificate for a DSA key (for configuring the outstation's trust in a DSA authority).

func SerialParams

func SerialParams(u *url.URL) (dev string, baud int, parity string, err error)

SerialParams reads a serial URL (serial:///dev/ttyS1?baud=9600&parity=N): the device, the baud rate and the parity letter.

func SessionKeyData

func SessionKeyData(ctrl, mon, keyStatus []byte) []byte

SessionKeyData is the plaintext wrapped in a g120v6: the key length, the control and monitoring direction session keys and the key status object data being answered, zero-padded to a multiple of 8 octets.

func SetAuditor

func SetAuditor(r iotsec.Auditor)

SetAuditor sets where Secure Authentication security events are recorded for the whole process (an application's security log); nil stops recording.

func SignatureData

func SignatureData(outstation string, masterChal, ostnChal []byte, ksq uint32, usr uint16, encrypted []byte) []byte

SignatureData is what the user's g120v14 signature covers (Table A-12).

func SplitAggressive

func SplitAggressive(objects []byte) (req []byte, inner []byte, mac []byte, macAt int, ok bool)

SplitAggressive finds an aggressive-mode fragment's parts: the g120v3 object data, the objects between, the MAC and the length of the fragment objects before the MAC value. ok is false when the objects do not start with g120v3 and end with g120v9.

func StatisticName

func StatisticName(i uint32) string

StatisticName names a security statistic by its index (modulo 18, as further associations report in blocks of 18).

func StatusName

func StatusName(s uint8) string

StatusName is the readable name of a command status code.

func Symmetric

func Symmetric(method uint8) bool

Symmetric reports a symmetric key change method (below 64).

func Time48

func Time48(b []byte) time.Time

Time48 decodes six octets of milliseconds since the epoch.

func UnwrapKey

func UnwrapKey(kek, wrapped []byte) ([]byte, error)

UnwrapKey reverses WrapKey and checks the integrity value.

func UpdateKeyData

func UpdateKeyData(name string, key, ostnChallenge []byte, pad bool) []byte

UpdateKeyData is the plaintext of the encrypted update key data (Table A-11), zero-padded to a multiple of 8 octets when pad is set.

func ValidQualifier

func ValidQualifier(q uint8) bool

ValidQualifier reports the codes of Table 4-6.

func WrapKey

func WrapKey(kek, key []byte) ([]byte, error)

WrapKey is RFC 3394 AES key wrap of key (a multiple of 8 octets, at least 16) with kek.

Types

type ActivateStatus

type ActivateStatus struct {
	Delay    time.Duration
	Elements []StatusElement
}

ActivateStatus is a g91v1.

func ParseActivateStatus

func ParseActivateStatus(b []byte) (ActivateStatus, error)

ParseActivateStatus decodes a g91v1.

func (ActivateStatus) Encode

func (a ActivateStatus) Encode() []byte

Encode is the object data.

type Association

type Association struct {
	// contains filtered or unexported fields
}

Association is the outstation's side of one master–outstation association.

func NewAssociation

func NewAssociation(cfg OutstationConfig) *Association

NewAssociation makes an association (Run starts it).

Example

An outstation: a point database, one association per master, served over TCP; a change becomes a class 1 event.

package main

import (
	"context"
	"fmt"
	"net"
	"time"

	"github.com/cB-Sigma/dnp3"
)

// exampleHandler is an outstation application that accepts every control
// and supports nothing else.
type exampleHandler struct{}

func (exampleHandler) Control(context.Context, dnp3.ControlRequest) uint8        { return 0 }
func (exampleHandler) DataSetControl(context.Context, dnp3.DataSetControl) uint8 { return 4 }
func (exampleHandler) Restart(context.Context, bool) time.Duration               { return 0 }
func (exampleHandler) Activate(context.Context, [][]byte) dnp3.ActivateStatus {
	return dnp3.ActivateStatus{}
}
func (exampleHandler) Application(context.Context, byte, [][]byte) dnp3.IIN {
	return dnp3.IINNoFuncCodeSupport
}
func (exampleHandler) TimeSet(time.Time)                            {}
func (exampleHandler) DataSetWritten(context.Context, dnp3.DataSet) {}

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	db := dnp3.NewPointDB()
	_ = db.Define(dnp3.DBPoint{Type: "binary", Index: 0, Class0: true, Class: 1})
	_, _ = db.Update(ctx, "binary", 0, 0, nil, 0x01, time.Now())

	a := dnp3.NewAssociation(dnp3.OutstationConfig{Address: 10, Master: 1, DB: db, Handler: exampleHandler{}})
	n := &dnp3.OutstationNet{}
	n.Add(a)
	go a.Run(ctx)
	ln, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		panic(err)
	}
	defer ln.Close()
	go func() {
		for {
			c, err := ln.Accept()
			if err != nil {
				return
			}
			go n.ServeStream(ctx, c)
		}
	}()

	m, err := dnp3.Dial(ctx, dnp3.MasterConfig{URL: "tcp://" + ln.Addr().String(), MasterAddress: 1, OutstationAddress: 10, Timeout: 2 * time.Second})
	if err != nil {
		panic(err)
	}
	defer m.Close()
	_, _ = db.Update(ctx, "binary", 0, 1, nil, 0x01, time.Now())
	frags, err := m.Request(ctx, dnp3.FCRead, dnp3.ClassRequest(1))
	if err != nil {
		panic(err)
	}
	blocks, _ := dnp3.ParseObjects(frags[0].Objects, true)
	p := blocks[0].Points[0]
	fmt.Printf("event g%dv%d index %d: %v\n", p.Group, p.Var, p.Index, p.Value)
}
Output:
event g2v2 index 0: true

func (*Association) Accepts

func (a *Association) Accepts(dest uint16) bool

Accepts reports whether the association takes a frame to dest.

func (*Association) Attach

func (a *Association) Attach(l OutLink)

Attach makes l the link the association sends on (a new connection).

func (*Association) Deliver

func (a *Association) Deliver(f Frame)

Deliver takes a primary frame from the association's master: the secondary station rules, the transport function, then the request.

func (*Association) Detach

func (a *Association) Detach(l OutLink)

Detach forgets l when it is the current link.

func (*Association) Idle

func (a *Association) Idle() time.Duration

Idle is how long nothing was received.

func (*Association) KeepAlive

func (a *Association) KeepAlive()

KeepAlive sends REQUEST_LINK_STATUS on the link (13.2.3.3.1).

func (*Association) Linked

func (a *Association) Linked() bool

Linked reports whether a link is attached.

func (*Association) Master

func (a *Association) Master() uint16

Master is the association's master address.

func (*Association) Run

func (a *Association) Run(ctx context.Context)

Run serves the association until ctx ends.

func (*Association) Status

func (a *Association) Status() map[string]any

Status is a summary for the status container.

func (*Association) String

func (a *Association) String() string

String names the association for logs.

type AsymmetricAuthority

type AsymmetricAuthority struct {
	KeyChangeMethod uint8
	Key             *dsa.PrivateKey
}

AsymmetricAuthority certifies user status changes with its DSA private key (methods 67 to 69).

func (AsymmetricAuthority) Certify

func (a AsymmetricAuthority) Certify(u UserStatus) ([]byte, error)

Certify signs the status information.

func (AsymmetricAuthority) EncryptUpdateKey

func (AsymmetricAuthority) EncryptUpdateKey(string, []byte, []byte) ([]byte, error)

EncryptUpdateKey is not used with asymmetric methods.

func (AsymmetricAuthority) Method

func (a AsymmetricAuthority) Method() uint8

Method is the key change method.

type Auth

type Auth struct {
	User       uint16
	UpdateKey  []byte // AES-128 or AES-256 key
	MAL        uint8  // MAC algorithm for the master's own challenges
	Aggressive bool
	// ChallengeResponses challenges responses with objects (see AuthConfig).
	ChallengeResponses bool
	// KeyChangeCount is the authentication message count after which the
	// session keys are changed (0: never by count).
	KeyChangeCount int

	// Subject and Tenant name the association in security events.
	Subject, Tenant string
	// contains filtered or unexported fields
}

Auth is a master's Secure Authentication state for one association.

func NewAuth

func NewAuth(c *AuthConfig) (*Auth, error)

NewAuth builds a master's Secure Authentication state (nil for a nil configuration).

func (*Auth) KeyChangeDue

func (a *Auth) KeyChangeDue() bool

KeyChangeDue reports that the authentication message count since the last session key change reached KeyChangeCount.

func (*Auth) KeysAge

func (a *Auth) KeysAge() time.Duration

KeysAge is how long ago the session keys were set (0 when they are not).

func (*Auth) RestartRekey

func (a *Auth) RestartRekey() bool

RestartRekey reports whether the master is to change the session keys because the outstation restarted (Table 7-8, master only): not once Rekeys Due to Restarts passed Max Rekeys Due to Restarts, until the next key change for the interval.

func (*Auth) ScheduledKeyChange

func (a *Auth) ScheduledKeyChange()

ScheduledKeyChange notes a session key change for the interval or the message count: rekeys due to authentication failures and restarts resume and Max Authentication Rekeys is reset (Table 7-8).

func (*Auth) SetUpdateKey

func (a *Auth) SetUpdateKey(key []byte, user uint16)

SetUpdateKey replaces the update key and user number, for an application that stored a changed update key and restores it.

func (*Auth) Statistics

func (a *Auth) Statistics() map[string]uint32

Statistics is a copy of the master's security statistics (Table 7-6).

func (*Auth) UpdateKeyOf

func (a *Auth) UpdateKeyOf() ([]byte, uint16)

UpdateKeyOf returns the user's current update key and user number (after a change-update-key, the new ones).

type AuthConfig

type AuthConfig struct {
	// User is the user number (Table 7-11); 0 is taken as the default
	// user 1.
	User uint16
	// UpdateKey is the user's update key: 16 octets (AES-128 key wrap) or
	// 32 (AES-256 key wrap).
	UpdateKey []byte
	// MAC is the MAC algorithm (one of the MAC constants) of the master's
	// own challenges; 0 is HMAC-SHA-256 truncated to 16 octets. MACByName
	// reads the usual names.
	MAC uint8
	// AggressiveMode sends the critical requests of Table 7-7 with their
	// authentication (g120v3 ... g120v9) once a challenge has been
	// answered since the last session key change (7.5.3.3).
	AggressiveMode bool
	// ChallengeResponses has the master challenge every solicited and
	// unsolicited response that carries objects before using it
	// (7.5.2.3.2 c).
	ChallengeResponses bool
	// KeyChangeCount changes the session keys after this many
	// authentication messages in either direction (7.6.1.4.4); 0: never
	// by count.
	KeyChangeCount int
	// The statistic thresholds that act as moving limits (7.5.2.2, Table
	// 7-15); 0 takes the defaults of Table 7-6: 5, 3, 3 and 3.
	MaxAuthenticationFailures int
	MaxReplyTimeouts          int
	MaxAuthenticationRekeys   int
	MaxRekeysDueToRestarts    int
}

AuthConfig enables Secure Authentication v5 on a master association.

type AuthError

type AuthError struct {
	Code uint8
	Msg  string
}

AuthError is a g120v7 error from the outstation, or a failed key change.

func (*AuthError) Error

func (e *AuthError) Error() string

Error names the code and the detail.

type Authority

type Authority interface {
	Method() uint8
	// Certify returns the certification data of a user status change.
	Certify(u UserStatus) ([]byte, error)
	// EncryptUpdateKey returns the encrypted update key data (symmetric
	// methods only).
	EncryptUpdateKey(name string, key, ostnChallenge []byte) ([]byte, error)
}

Authority is the key distribution authority as the master sees it (7.5.4.5): it certifies user status changes and, for the symmetric methods, encrypts new update keys for the outstation. The standard keeps it apart from the master and leaves the master–authority protocol open.

type Block

type Block struct {
	Header Header
	Points []Point
}

Block is an object header and its decoded objects.

func ParseObjects

func ParseObjects(b []byte, withData bool) ([]Block, error)

ParseObjects decodes the object headers and objects of a fragment. With withData false (READ requests and unsolicited enable/disable) headers carry no objects. An unknown group/variation stops parsing, since its size is unknown; the blocks decoded so far are returned with the error.

func ParseRequest

func ParseRequest(fc byte, objs []byte) ([]Block, error)

ParseRequest splits a request's objects into headers (a READ's headers carry no data; size-prefixed objects are kept whole), as the outstation reads them.

type CROB

type CROB struct {
	Code    uint8
	Count   uint8
	OnTime  uint32 // ms
	OffTime uint32 // ms
}

CROB is a control relay output block (g12v1).

type CertifiedUser

type CertifiedUser struct {
	Name      string
	PublicKey any // ID certificates
	Roles     []UserRoleInfo
	NotBefore time.Time
	NotAfter  time.Time
	// Token is the IEC 62351-8 access token as the role authority read it
	// (its signature checked by VerifyUserCertificate).
	Token iotsec.AccessToken
}

CertifiedUser is what a verified user certificate says.

func VerifyUserCertificate

func VerifyUserCertificate(u UserCertificate, authority *x509.Certificate, ra iotsec.RoleAuthority) (CertifiedUser, error)

VerifyUserCertificate checks a g120v8 certificate against the authority certificate and returns what it certifies; ra reads the IEC 62351-8 access token (and checks the signature of attribute certificates that are not DSA-signed).

func (CertifiedUser) Role

func (c CertifiedUser) Role(ra iotsec.RoleAuthority, aors []string) (UserRoleInfo, bool)

Role is the first role of the first entry whose area of responsibility applies to an outstation with the areas aors (remote access), as ra matches areas (IEC 62351-8 10.4.4.9), or false.

type Challenge

type Challenge struct {
	CSQ    uint32
	User   uint16
	MAL    uint8
	Reason uint8
	Data   []byte
}

Challenge is a decoded g120v1.

func ParseChallenge

func ParseChallenge(d []byte) (Challenge, error)

ParseChallenge decodes g120v1 object data (at least 4 octets of challenge data, A.45.1).

func (Challenge) Encode

func (c Challenge) Encode() []byte

Encode is the g120v1 object data.

type Channel

type Channel struct {
	// contains filtered or unexported fields
}

Channel is one shared connection.

func OpenChannel

func OpenChannel(ctx context.Context, cfg MasterConfig) (*Channel, error)

OpenChannel opens the connection of cfg (URL, TLS, Timeout) for several masters.

func (*Channel) Attach

func (c *Channel) Attach(cfg MasterConfig) (*Master, error)

Attach adds an association for cfg.OutstationAddress (its own addresses, timeouts, authentication; the URL and TLS are the channel's).

func (*Channel) Close

func (c *Channel) Close() error

Close ends the connection and every association on it.

func (*Channel) Done

func (c *Channel) Done() <-chan struct{}

Done is closed when the connection ends.

func (*Channel) Err

func (c *Channel) Err() error

Err is why the connection ended.

func (*Channel) Members

func (c *Channel) Members() int

Members counts the attached associations.

type ClassAssignment

type ClassAssignment struct {
	Class   int
	Headers [][]byte
}

ClassAssignment is one assignment set of an ASSIGN_CLASS request (4.4.14): the class (0 disables events, 1 to 3) and the object headers of the points whose events it takes.

type ClassScan added in v0.2.0

type ClassScan struct {
	Classes []int
	Period  time.Duration
}

ClassScan polls event classes (1 to 3; 0 for static data) every Period.

type ControlRequest

type ControlRequest struct {
	Func   byte
	Group  uint8
	Var    uint8
	Index  uint32
	CROB   CROB
	Value  float64
	Select bool   // a SELECT: check only, do not operate
	User   uint16 // the authenticated user (Secure Authentication), 0 without
}

ControlRequest is one control object of a SELECT, OPERATE or DIRECT_OPERATE(_NR): a CROB (g12v1) or an analog output block (g41).

type DBPoint

type DBPoint struct {
	Type      string
	Index     uint32
	Value     float64
	Raw       []byte // octet strings
	Flags     uint8
	Time      time.Time
	Class0    bool    // reported in class 0 polls
	Class     int     // event class 1 to 3, 0: no events
	StaticVar uint8   // 0: the type's default
	EventVar  uint8   // 0: the type's default
	Deadband  float64 // analogs, counters: an event needs a change larger than this
	Frozen    float64 // counters: the value at the last freeze
	// Units is the interval's units code of a time-and-interval point
	// (whose Time is its start and Value its interval count).
	Units uint8
	// contains filtered or unexported fields
}

DBPoint is one point of the outstation database.

type DataSet

type DataSet struct {
	ID     uint32
	Time   time.Time
	Values [][]byte
}

DataSet is a g87v1 (present value) or g88v1 (event) object: identifier, evaluation or event time and the raw element values.

func ParseDataSet

func ParseDataSet(b []byte) (DataSet, error)

ParseDataSet decodes a g87v1 or g88v1.

func (DataSet) Encode

func (d DataSet) Encode() []byte

Encode is the object data.

type DataSetControl

type DataSetControl struct {
	Func   byte
	Select bool
	ID     uint32
	Names  []string
	Values []any
	User   uint16
}

DataSetControl is a control group of a data set in a control request (5.4.11): the values of the CTLV elements following one CTLS.

type DataSetDef

type DataSetDef struct {
	Desc     Descriptor
	Chars    uint8 // DSReadable, DSWritable, DSStatic, DSEvent, DSByMaster
	Refs     []PointRef
	Class    int  // event class of its events (0: none)
	Class0   bool // present value included in class 0
	Values   [][]byte
	Time     time.Time
	Received bool // a value was written by the master
}

DataSetDef is one data set of the outstation.

type DescElement

type DescElement struct {
	Code      uint8
	Type      uint8
	MaxLen    uint8
	Ancillary []byte
}

DescElement is one descriptor element.

func IDElement

func IDElement(id uint32) DescElement

IDElement is the mandatory first element carrying the identifier (a 2-octet UINT up to 65535, as in EX 5-5).

func ParseElements

func ParseElements(b []byte) ([]DescElement, error)

ParseElements decodes descriptor elements.

func (DescElement) Name

func (e DescElement) Name() string

Name is the element's name (the ancillary VSTR of DAEL, CTLV, CTLS and NAME; the instance name after the UUID of a PTYP).

type Descriptor

type Descriptor struct {
	ID        uint32
	UUID      [16]byte // prototypes only
	Namespace string   // prototypes only
	Name      string
	Elements  []DescElement // after the ID (and UUID, NSPC, NAME)
}

Descriptor is a data set descriptor (g86v1) or prototype (g85v1).

func ParseDescriptor

func ParseDescriptor(b []byte, proto bool) (Descriptor, error)

ParseDescriptor decodes a g86v1 (proto false) or g85v1 (proto true) and checks the element order rules (5.4.6.2, 5.4.7.2).

func (Descriptor) EncodeDescriptor

func (d Descriptor) EncodeDescriptor() []byte

EncodeDescriptor is a g86v1 object's data.

func (Descriptor) EncodePrototype

func (d Descriptor) EncodePrototype() []byte

EncodePrototype is a g85v1 object's data.

func (Descriptor) Flatten

func (d Descriptor) Flatten(protos map[[16]byte]Descriptor) ([]DescElement, error)

Flatten resolves the data elements of a descriptor, substituting each prototype reference with the prototype's elements (5.4.6.2.5).

type Event

type Event struct {
	ID    int64     `json:"id"`
	Class int       `json:"class"`
	Group uint8     `json:"g"`
	Var   uint8     `json:"v"`
	Index uint32    `json:"i"`
	Value float64   `json:"value"`
	Raw   []byte    `json:"raw,omitempty"`
	Flags uint8     `json:"flags"`
	Time  time.Time `json:"t"`
	Assoc uint16    `json:"assoc,omitempty"`
}

Event is one buffered event.

type EventBuffer

type EventBuffer struct {
	// contains filtered or unexported fields
}

EventBuffer is an association's events per class, bounded (4.5.12).

func NewEventBuffer

func NewEventBuffer(capacity map[int]int, store EventStore) *EventBuffer

NewEventBuffer makes a buffer with per-class capacities (0: 100 each).

func (*EventBuffer) Add

func (b *EventBuffer) Add(ctx context.Context, e Event)

Add buffers an event; a full class loses the event and sets overflow.

func (*EventBuffer) Capacity

func (b *EventBuffer) Capacity(class int) int

Capacity is a class's buffer size.

func (*EventBuffer) Classes

func (b *EventBuffer) Classes(except map[int64]bool) map[int]bool

Classes reports which classes have events not in except.

func (*EventBuffer) Counts

func (b *EventBuffer) Counts() map[int]int

Len is the number of buffered events. Counts are the buffered events per class.

func (*EventBuffer) Len

func (b *EventBuffer) Len() int

Len counts the buffered events of every class.

func (*EventBuffer) Load

func (b *EventBuffer) Load(ctx context.Context) error

Load restores the durable events.

func (*EventBuffer) Overflow

func (b *EventBuffer) Overflow() bool

Overflow reports the overflow condition: set by a lost event, cleared once every class has room again.

func (*EventBuffer) Pending

func (b *EventBuffer) Pending(classes map[int]bool, limit int) []Event

Pending returns the buffered events of the classes, oldest first (at most limit per class when limit > 0).

func (*EventBuffer) Remove

func (b *EventBuffer) Remove(ctx context.Context, ids []int64)

Remove drops confirmed events.

func (*EventBuffer) SetDropOldest added in v0.3.0

func (b *EventBuffer) SetDropOldest(on bool)

SetDropOldest chooses what a full buffer loses: with on, its oldest event of the same class (or point type) makes room for the new one; by default the new event is lost. Either way the overflow is reported (IIN2.3).

func (*EventBuffer) SetTypeCapacity added in v0.3.0

func (b *EventBuffer) SetTypeCapacity(typ string, n int)

SetTypeCapacity bounds the buffered events of one point type ("binary", "analog", ...) besides the per-class capacity, as outstations that size their buffers per object type do; n <= 0 removes the bound.

type EventMode added in v0.3.0

type EventMode int

EventMode decides whether an update creates an event.

const (
	// EventDetect creates an event when the value or flags changed beyond
	// the point's deadband (Update's behaviour).
	EventDetect EventMode = iota
	// EventForce creates an event whether or not anything changed (an
	// event passed on from another device, a momentary change already
	// over).
	EventForce
	// EventSuppress updates the static value without an event.
	EventSuppress
	// EventOnly creates an event and leaves the static value as it was
	// (a historical event reported late).
	EventOnly
)

Event modes of UpdateWith.

type EventStore

type EventStore interface {
	Add(ctx context.Context, payload []byte) (int64, error)
	Delete(ctx context.Context, ids ...int64) error
	ListPayloads(ctx context.Context) ([]int64, [][]byte, error)
}

EventStore keeps an association's events durably (an application backs it with its own queue or database); nil keeps them in memory only.

type FileAuth

type FileAuth struct {
	User, Password string
	Key            uint32
}

FileAuth is g70v2: a request for (and the answer with) a file authentication key.

func ParseFileAuth

func ParseFileAuth(b []byte) (FileAuth, error)

ParseFileAuth decodes g70v2 object data.

func (FileAuth) Encode

func (a FileAuth) Encode() []byte

Encode is the object data.

type FileClient

type FileClient struct {
	M        *Master
	MaxBlock uint16        // largest block accepted; default 1024
	Wait     time.Duration // how long to poll for a delayed answer; default 10 s
	Other    func(Fragment)
	User     string // with Password: AUTHENTICATE_FILE before open and delete
	Password string
	// contains filtered or unexported fields
}

FileClient runs the master's side of sequential file transfer (5.3) on an association. Objects the outstation returns later as events are collected by polling classes 1 to 3; Other receives every fragment of those polls so their other data is not lost.

func (*FileClient) Delete

func (c *FileClient) Delete(ctx context.Context, name string) error

Delete removes a file.

func (*FileClient) Info

func (c *FileClient) Info(ctx context.Context, name string) (FileDescriptor, error)

Info returns a file's descriptor (GET_FILE_INFO).

func (*FileClient) List

func (c *FileClient) List(ctx context.Context, dir string) ([]FileDescriptor, error)

List reads a directory file into its entries.

func (*FileClient) ReadFile

func (c *FileClient) ReadFile(ctx context.Context, name string) ([]byte, error)

ReadFile opens, reads block by block until the last and closes a file.

func (*FileClient) WriteFile

func (c *FileClient) WriteFile(ctx context.Context, name string, data []byte) error

WriteFile opens a file for writing, sends it block by block (the last marked) and closes it.

type FileCommand

type FileCommand struct {
	Created     time.Time
	Permissions uint16
	AuthKey     uint32
	Size        uint32
	Mode        uint16
	MaxBlock    uint16
	RequestID   uint16
	Name        string
}

FileCommand is g70v3: open or delete a file.

func ParseFileCommand

func ParseFileCommand(b []byte) (FileCommand, error)

ParseFileCommand decodes g70v3 object data.

func (FileCommand) Encode

func (c FileCommand) Encode() []byte

Encode is the object data (the name follows the fixed fields).

type FileDescriptor

type FileDescriptor struct {
	Type        uint16
	Size        uint32
	Created     time.Time
	Permissions uint16
	RequestID   uint16
	Name        string
}

FileDescriptor is g70v7: file information (GET_FILE_INFO, directory entries). Type 0 is a directory, 1 a simple file.

func ParseDirectory

func ParseDirectory(b []byte) ([]FileDescriptor, error)

ParseDirectory splits the content of a directory file into its g70v7 entries (5.3: each entry is a descriptor preceded by nothing; the descriptors follow each other).

func ParseFileDescriptor

func ParseFileDescriptor(b []byte) (FileDescriptor, error)

ParseFileDescriptor decodes g70v7 object data.

func (FileDescriptor) Encode

func (d FileDescriptor) Encode() []byte

Encode is the object data.

type FileInfo

type FileInfo struct {
	Name        string // base name
	Dir         bool
	Size        uint32
	Created     time.Time
	Permissions uint16
}

FileInfo describes a file or directory. Size is the length of a file, or the number of entries of a directory (5.3.5).

type FileRights

type FileRights struct{ Read, Write bool }

FileRights are the rights of a request without a valid key.

type FileStatus

type FileStatus struct {
	Handle    uint32
	Size      uint32
	MaxBlock  uint16
	RequestID uint16
	Status    uint8
	Text      string
}

FileStatus is g70v4: the answer to open, close, delete and abort, and the request of close and abort.

func ParseFileStatus

func ParseFileStatus(b []byte) (FileStatus, error)

ParseFileStatus decodes g70v4 object data.

func (FileStatus) Encode

func (s FileStatus) Encode() []byte

Encode is the object data.

type FileStore

type FileStore interface {
	Stat(ctx context.Context, path string) (FileInfo, error)
	List(ctx context.Context, dir string) ([]FileInfo, error)
	Read(ctx context.Context, path string) ([]byte, error)
	Write(ctx context.Context, path string, data []byte, created time.Time, perms uint16) error
	Delete(ctx context.Context, path string) error
}

FileStore holds an outstation's files. Paths are the file name of the request without separators at either end, segments joined by "/" ("" is the root directory).

type FileTransport

type FileTransport struct {
	Handle uint32
	Block  uint32
	Last   bool
	Data   []byte
}

FileTransport is g70v5: a block of file data. Block numbers start at 0; Last marks the final block (bit 31 of the block field).

func ParseFileTransport

func ParseFileTransport(b []byte) (FileTransport, error)

ParseFileTransport decodes g70v5 object data.

func (FileTransport) Encode

func (t FileTransport) Encode() []byte

Encode is the object data.

type FileTransportStatus

type FileTransportStatus struct {
	Handle uint32
	Block  uint32
	Last   bool
	Status uint8
	Text   string
}

FileTransportStatus is g70v6: the status of a file data transfer.

func ParseFileTransportStatus

func ParseFileTransportStatus(b []byte) (FileTransportStatus, error)

ParseFileTransportStatus decodes g70v6 object data.

func (FileTransportStatus) Encode

func (t FileTransportStatus) Encode() []byte

Encode is the object data.

type FileUser

type FileUser struct {
	Password Secret
	Write    bool
}

FileUser is a user of AUTHENTICATE_FILE: files are readable, and writable and deletable with Write.

type Fragment

type Fragment struct {
	Control byte
	Func    byte
	IIN     IIN // responses only
	Objects []byte
}

Fragment is one application fragment (request or response).

func ParseFragment

func ParseFragment(b []byte) (Fragment, error)

ParseFragment decodes an application fragment's header.

func (Fragment) Encode

func (f Fragment) Encode() []byte

Encode serialises the fragment.

func (Fragment) IsResponse

func (f Fragment) IsResponse() bool

IsResponse reports a solicited or unsolicited response.

func (Fragment) Seq

func (f Fragment) Seq() uint8

Seq is the fragment's sequence number.

type Frame

type Frame struct {
	Ctrl byte
	Dest uint16
	Src  uint16
	Data []byte
}

Frame is one link-layer frame.

func DecodeFrame

func DecodeFrame(b []byte) (Frame, error)

DecodeFrame decodes exactly one encoded frame.

func (Frame) Encode

func (f Frame) Encode() ([]byte, error)

Encode serialises the frame with its header and block CRCs.

func (Frame) FCB

func (f Frame) FCB() bool

FCB reports the frame count bit and FCV the frame count valid bit of a primary frame.

func (Frame) FCV

func (f Frame) FCV() bool

FCV reports the frame count valid bit (DFC on secondary frames).

func (Frame) FromMaster

func (f Frame) FromMaster() bool

FromMaster reports the DIR bit.

func (Frame) Func

func (f Frame) Func() byte

Func is the frame's link function code.

func (Frame) Primary

func (f Frame) Primary() bool

Primary reports a primary (initiating) frame.

type Handler

type Handler interface {
	// Control returns the control status (0 success; codes of Table 11-7).
	Control(ctx context.Context, c ControlRequest) uint8
	// DataSetControl issues one control group of a data set.
	DataSetControl(ctx context.Context, c DataSetControl) uint8
	// Restart returns the time the outstation will be busy (4.4.9).
	Restart(ctx context.Context, cold bool) time.Duration
	// Activate checks and activates the configurations named by the
	// request objects (g70v8 file specifications, g110 octet strings).
	Activate(ctx context.Context, names [][]byte) ActivateStatus
	// Application initialises, starts or stops applications (g90 names;
	// nil for all); a non-zero IIN (2.0, 2.1, 2.4) reports a refusal.
	Application(ctx context.Context, fc byte, names [][]byte) IIN
	// TimeSet reports a time synchronisation: the master's time now.
	TimeSet(masterNow time.Time)
	// DataSetWritten reports a data set written by the master.
	DataSetWritten(ctx context.Context, ds DataSet)
}

Handler performs what the outstation cannot do from its database.

type Header struct {
	Group, Var, Qual uint8
	Start, Stop      uint32 // start-stop and virtual address qualifiers
	Count            uint32 // number of objects that follow
}

Header is one object header.

func (Header) String

func (h Header) String() string

String is the object type as gNvM.

type IIN

type IIN uint16

IIN is the internal indications of a response: IIN1 in the low octet, IIN2 in the high octet.

const (
	IINBroadcast           IIN = 1 << 0
	IINClass1Events        IIN = 1 << 1
	IINClass2Events        IIN = 1 << 2
	IINClass3Events        IIN = 1 << 3
	IINNeedTime            IIN = 1 << 4
	IINLocalControl        IIN = 1 << 5
	IINDeviceTrouble       IIN = 1 << 6
	IINDeviceRestart       IIN = 1 << 7
	IINNoFuncCodeSupport   IIN = 1 << 8
	IINObjectUnknown       IIN = 1 << 9
	IINParameterError      IIN = 1 << 10
	IINEventBufferOverflow IIN = 1 << 11
	IINAlreadyExecuting    IIN = 1 << 12
	IINConfigCorrupt       IIN = 1 << 13
	IINReserved2           IIN = 1 << 14 // IIN2.6, always 0
	IINReserved1           IIN = 1 << 15 // IIN2.7, always 0

	IINClassEvents = IINClass1Events | IINClass2Events | IINClass3Events
)

Internal indication bits.

func (IIN) Names

func (i IIN) Names() []string

Names lists the set indications.

func (IIN) String

func (i IIN) String() string

String lists the indications set, by name.

type KeyStatus

type KeyStatus struct {
	KSQ       uint32
	User      uint16
	KeyWrap   uint8
	Status    uint8
	MAL       uint8
	Challenge []byte
	MAC       []byte
}

KeyStatus is a decoded g120v5.

func ParseKeyStatus

func ParseKeyStatus(d []byte) (KeyStatus, error)

ParseKeyStatus decodes g120v5 object data.

func (KeyStatus) Unauthenticated

func (k KeyStatus) Unauthenticated(d []byte) []byte

Unauthenticated is the key status object data without its MAC: what the key wrap data carries (A.45.6 Table A-5).

type LinkAction

type LinkAction struct {
	Respond bool
	Func    byte
	Deliver bool
}

LinkAction is what a secondary station does with a received primary frame: the secondary function code to answer with (when Respond), and whether the user data goes to the transport function (Deliver).

type LinkParser

type LinkParser struct {

	// Errors counts discarded octets runs (garbage, bad CRC, bad length).
	Errors int
	// contains filtered or unexported fields
}

LinkParser extracts frames from a byte stream. It tolerates reads split anywhere and resynchronises on the start octets after garbage or a CRC error.

func (*LinkParser) Feed

func (p *LinkParser) Feed(b []byte) []Frame

Feed adds received octets and returns every complete frame.

type Master

type Master struct {
	// contains filtered or unexported fields
}

Master is a connected association: one outstanding request at a time, with unsolicited responses confirmed and handed to OnUnsolicited.

func Dial

func Dial(ctx context.Context, cfg MasterConfig) (*Master, error)

Dial connects to an outstation.

func (*Master) Auth

func (m *Master) Auth() *Auth

Auth is the association's Secure Authentication state (nil without).

func (*Master) ChangeSessionKeys

func (m *Master) ChangeSessionKeys(ctx context.Context) error

ChangeSessionKeys sets new session keys: key status request, then a session key change answering the key status, then a check of the MAC in the outstation's new key status (A.45.5 Table A-4: over the whole session key change fragment, with the new monitoring direction key).

func (*Master) ChangeUpdateKey

func (m *Master) ChangeUpdateKey(ctx context.Context, c UpdateKeyChange) error

ChangeUpdateKey runs the user status change and update key change and, when they succeed, makes the new update key and user number current and changes the session keys with them. Delete stops after the status change.

func (*Master) Close

func (m *Master) Close() error

Close ends the association.

func (*Master) Done

func (m *Master) Done() <-chan struct{}

Done is closed when the connection ends.

func (*Master) Err

func (m *Master) Err() error

Err is why the connection ended.

func (*Master) IIN

func (m *Master) IIN() IIN

IIN is the internal indications of the latest response.

func (*Master) LearnedAddress

func (m *Master) LearnedAddress() (uint16, bool)

LearnedAddress is the outstation's own address, learnt from its answer to the self-address (false before one arrived).

func (*Master) Quiet

func (m *Master) Quiet(d time.Duration)

Quiet keeps requests back for d (the delay a restarting outstation announced, 4.4.9).

func (*Master) Request

func (m *Master) Request(ctx context.Context, fc byte, objects []byte) ([]Fragment, error)

Request sends one request and returns every fragment of its response, confirming fragments that ask for it. A response whose IIN2 reports the request as unsupported or malformed returns a *RejectedError with the fragments.

func (*Master) RunSession added in v0.2.0

func (m *Master) RunSession(ctx context.Context, cfg SessionConfig) error

RunSession polls the outstation as cfg says until ctx is done (it returns ctx's error) or the connection ends (it returns why).

func (*Master) SelectOperate

func (m *Master) SelectOperate(ctx context.Context, objects []byte, within time.Duration, check func(fc byte, frags []Fragment) error) error

SelectOperate sends SELECT and then OPERATE with the same objects as consecutive requests (no other request in between). check validates each response (nil accepts any); the operate is not sent when the select fails or took longer than within.

func (*Master) SyncTime added in v0.2.0

func (m *Master) SyncTime(ctx context.Context, lan bool) error

SyncTime sets the outstation's clock by the non-LAN procedure (DELAY_MEASURE, then a WRITE of g50v1 corrected by half the round trip less the outstation's processing delay) or the LAN one (RECORD_CURRENT_TIME, then a WRITE of g50v3 with the time it was sent).

type MasterConfig

type MasterConfig struct {
	URL               string // tcp://host:port or udp://host:port
	MasterAddress     uint16
	OutstationAddress uint16
	Timeout           time.Duration // response timeout per fragment
	Log               *slog.Logger
	// OnUnsolicited is called from the receive loop with every unsolicited
	// response (already confirmed when it asked for confirmation).
	OnUnsolicited func(Fragment)
	// Auth enables Secure Authentication: challenges of critical requests
	// are answered and ChangeSessionKeys sets the session keys.
	Auth *Auth
	// KeepAlive, on tcp URLs, sends REQUEST_LINK_STATUS when nothing was
	// received for this long and ends the connection when nothing answers
	// within Timeout (13.2.3.3.1). 0 disables it.
	KeepAlive time.Duration
	// LinkConfirm sends requests as CONFIRMED_USER_DATA, resetting the
	// outstation's link first and retrying each frame LinkRetries times
	// (9.2.6.1, 9.2.6.3); application confirmations stay unconfirmed.
	LinkConfirm bool
	LinkRetries int
	// MaxRequest is the largest request fragment sent (4.3 rule 4); 0 is
	// 2048. A larger request fails before it is sent.
	MaxRequest int
	// TLS, for tcp URLs, runs the connection under a TLS profile, for
	// example the IEC 62351-3 profile with mutual certificates (IEEE
	// 1815-2012 7.4 points to it). A profile that renews keys on the
	// connection may end a connection whose peer does not take part; the
	// caller reconnects. When the connection implements
	// iotsec.KeepaliveConn the master registers REQUEST_LINK_STATUS as
	// its keepalive.
	TLS iotsec.TLSClient
	// Dialer opens tcp connections (nil: a net.Dialer). UDP uses an
	// unconnected socket of its own (see udppeer.go) and serial lines no
	// dialer.
	Dialer iotsec.Dialer
	// LocalPort, for udp URLs, is the local port to send from and listen
	// on (0: any); see udpPeer.
	LocalPort int
}

MasterConfig configures one master–outstation association.

type OutLink interface {
	WriteFrame(f Frame) error
	Close() error
}

OutLink sends frames towards a master.

type OutstationAuth

type OutstationAuth struct {
	// contains filtered or unexported fields
}

OutstationAuth is the Secure Authentication state of one association.

func NewOutstationAuth

func NewOutstationAuth(c OutstationAuthConfig) (*OutstationAuth, error)

NewOutstationAuth checks the settings.

func (*OutstationAuth) RecentSecurityEvents

func (a *OutstationAuth) RecentSecurityEvents() ([]SecurityEventEntry, uint64)

RecentSecurityEvents returns the kept security events, newest first, and the number of broadcasts refused since start.

func (*OutstationAuth) SAState

func (a *OutstationAuth) SAState() (string, time.Time)

SAState describes the association's Secure Authentication exchange: "wait-for-reply" while a challenge of ours is unanswered, "wait-for-key-change" while an update key change is in progress, else "idle"; and the time the session keys last changed (zero if never).

func (*OutstationAuth) SetRSAKey

func (a *OutstationAuth) SetRSAKey(k *rsa.PrivateKey)

Users is a copy of the users (for status and persistence). SetRSAKey replaces the outstation's key transport key (a renewed certificate's key, IEC 62351-9 7.6).

func (*OutstationAuth) Statistics

func (a *OutstationAuth) Statistics() [NumSecurityStatistics]uint32

Statistics is a copy of the counters (Table 7-6).

func (*OutstationAuth) Summary

func (a *OutstationAuth) Summary() map[string]any

Summary describes the association's Secure Authentication for the status: the MAC algorithm, aggressive mode and each user's number, name, role and session key status (never a key).

func (*OutstationAuth) Users

func (a *OutstationAuth) Users() []OutstationUser

Users lists the outstation's users (update keys included; never log them).

type OutstationAuthConfig

type OutstationAuthConfig struct {
	Users []OutstationUser // user 1 (default) is required
	// MAL is the MAC algorithm of our challenges (default 4).
	MAL uint8
	// Critical is the request function codes challenged (default: the
	// mandatory ones of Table 7-7).
	Critical map[byte]bool
	// Aggressive accepts aggressive-mode requests (7.6.1.4.7; default on).
	Aggressive *bool
	// ChallengeConfirms asks for aggressive-mode confirmations of
	// responses with events (7.5.2.3.2 d).
	ChallengeConfirms bool
	// Expected session key change interval and count (7.6.1.4.5); the
	// keys become invalid after either (defaults 30 min and 2000).
	KeyInterval time.Duration
	KeyCount    int
	// MaxKeyStatus is the key status requests allowed per user within the
	// key interval before the operator is alerted (7.6.1.4.6; default 5).
	MaxKeyStatus int
	// MaxErrors is how many error messages are sent before they stop
	// until the next key change (Table 7-6; default 2).
	MaxErrors int
	// MaxAuthFailures and MaxReplyTimeouts are the reporting thresholds
	// of those statistics that act as moving limits (7.5.2.2, Table 7-15;
	// defaults 5 and 3 as in Table 7-6). When authentication failures pass
	// theirs the outstation drops a TCP connection (Table 7-8, 7.7.1);
	// when reply timeouts pass theirs the key status of every user
	// becomes COMM_FAIL (Table 7-8). ReplyTimeout is how long a challenge
	// waits for its reply (1.12.3; default 5 s).
	MaxAuthFailures  int
	MaxReplyTimeouts int
	ReplyTimeout     time.Duration
	// Subject and Tenant name the association in security events.
	Subject, Tenant string
	// Update key change (7.5.5.5, 7.5.5.6): the outstation's name, the
	// authority certification key (symmetric methods), the authority's
	// certificate (asymmetric methods and g120v8), our RSA key (key
	// transport), the areas of responsibility, the methods permitted.
	Name          string
	AuthorityKey  []byte
	AuthorityCert *x509.Certificate
	RSAKey        *rsa.PrivateKey
	AORs          []string
	Methods       map[uint8]bool
	// Roles reads and evaluates the IEC 62351-8 access token of g120v8
	// user certificates (role definition, revision, role IDs, areas of
	// responsibility, status change sequence number). When it is nil,
	// NewRoles builds one for this outstation (its name, AORs and users'
	// sequence numbers); when both are nil, user certificates are refused
	// (ErrNoRoleAuthority).
	Roles    iotsec.RoleAuthority
	NewRoles iotsec.NewRoleAuthority
	// UserCertCheck, when set, checks a g120v8 ID certificate that
	// verified against the authority (validity, revocation by CRL and
	// OCSP through IEC 62351-9); an error refuses it.
	UserCertCheck func(c *x509.Certificate) error
	// UserChanged persists a user after an update key change or delete
	// (Update nil: deleted).
	UserChanged func(OutstationUser)
	Log         *slog.Logger
}

OutstationAuthConfig configures Secure Authentication as outstation.

type OutstationConfig

type OutstationConfig struct {
	Address     uint16 // ours
	Master      uint16 // the master's
	SelfAddress bool   // accept frames to 0xFFFC (9.2.5.2.2)
	Log         *slog.Logger
	DB          *PointDB
	Events      *EventBuffer
	Handler     Handler
	// MaxFragment is the largest response fragment (default 2048).
	MaxFragment int
	// ConfirmTimeout waits for an application confirmation (default 5 s).
	ConfirmTimeout time.Duration
	// SelectTimeout is how long a select stays valid (default 5 s).
	SelectTimeout time.Duration
	// MaxControls is the most control objects in one request (default 16).
	MaxControls int
	// Unsolicited: the unsolicited response mode (4.6.2.1 b). UnsolRetries
	// is the retries per series (-1: indefinitely); UnsolHold delays a
	// response after the first new event, UnsolCount sends at once when
	// this many events are pending (4.6.2.2).
	Unsolicited  bool
	UnsolRetries int
	UnsolHold    time.Duration
	UnsolCount   int
	// UnsolOffline is the pause after a series that ended without a
	// confirmation (its retries used up) before a new series carries the
	// events still buffered (default: ConfirmTimeout).
	UnsolOffline time.Duration
	// NeedTime sets IIN1.4 at start and again this long after each
	// synchronisation; 0 never asks for time.
	NeedTime time.Duration
	// Attributes are the device attributes of set 0 by variation, and
	// Writable the variations the master may write.
	Attributes map[uint8]any
	Writable   map[uint8]bool
	// Auth enables Secure Authentication as outstation.
	Auth *OutstationAuth
	// Broadcast takes requests sent to the broadcast addresses (4.7;
	// never answered). BroadcastFuncs, when set, are the request function
	// codes carried out by broadcast (the device profile's 1.13 items);
	// others are discarded. Under Secure Authentication a critical
	// request by broadcast is carried out only in a verified Aggressive
	// Mode (OutstationAuth.gateBroadcast).
	Broadcast      bool
	BroadcastFuncs map[byte]bool
	// Files enables sequential file transfer (outfile.go); FileStore, when
	// set, is this association's store in place of Files.Store.
	Files     *OutstationFiles
	FileStore FileStore
	// Filter restricts the requests served to a declared device profile
	// (a simulator preset): a non-zero IIN refuses the request with it.
	Filter func(req Fragment) IIN
}

OutstationConfig configures one association.

type OutstationFiles

type OutstationFiles struct {
	Store    FileStore
	MaxBlock int           // largest block (default 1024)
	MaxOpen  int           // files open at once (default 4; rule 10)
	MaxSize  int           // largest file written (default 16 MiB)
	Idle     time.Duration // inactivity before a handle closes (default 5 min, at most 1 h; rule 12)
	Class    int           // class of spontaneous file events (default 3)
	KeyLife  time.Duration // an authentication key's life (default 1 min)
	ReadOnly bool          // refuse every write and delete
	Users    map[string]FileUser
	// Guest are the rights with key 0 (5.3.1); nil: read and write when no
	// users are configured, nothing otherwise.
	Guest *FileRights
	// Changed reports a file written ("written") or deleted ("deleted").
	Changed func(ctx context.Context, path, op string, size int)
	// contains filtered or unexported fields
}

OutstationFiles configures file transfer; one value may serve several associations (handles and locks are then shared: 5.3.8 rule 9).

type OutstationNet

type OutstationNet struct {
	Log       *slog.Logger
	KeepAlive time.Duration // default 30 s
	Timeout   time.Duration // link status answer; default 5 s
	// contains filtered or unexported fields
}

OutstationNet routes frames to associations.

func (*OutstationNet) Add

func (n *OutstationNet) Add(a *Association)

Add registers an association.

func (*OutstationNet) ServeStream

func (n *OutstationNet) ServeStream(ctx context.Context, rw io.ReadWriteCloser)

ServeStream serves one connection until it ends or ctx is done.

func (*OutstationNet) ServeUDP

func (n *OutstationNet) ServeUDP(ctx context.Context, pc net.PacketConn, allowed func(net.Addr, uint16) bool)

ServeUDP serves datagrams until ctx is done; allowed filters peers.

type OutstationUser

type OutstationUser struct {
	Number    uint16
	Name      string
	UpdateKey []byte
	Role      uint16
	Expires   time.Time // zero: never
}

OutstationUser is one user of the outstation.

type Point

type Point struct {
	Group, Var uint8
	Index      uint32
	// Value is a bool (binary, binary output, g80), a float64 (double-bit
	// state 0–3, counters, analogs, delays) or a time.Time (g50, g51).
	Value    any
	Flags    uint8
	HasFlags bool
	Time     time.Time // event time, when the object carries one
	Status   uint8     // command status (g12, g13, g41, g43)
	CROB     CROB      // g12v1, g12v2
	// Interval and Units are the interval of g50v2 (ms) and g50v4 (count
	// and units code); Assoc is the association ID of g121 and g122; Ref
	// is the group and variation a g81 fill status refers to.
	Interval uint32
	Units    uint8
	Assoc    uint16
	Ref      [2]uint8
	// Raw is the object data of variable-size objects: those with an
	// object size prefix (g70, g91, g120, ...) and those whose variation
	// is their length (g110 to g113).
	Raw []byte
}

Point is one decoded object.

func (Point) Quality

func (p Point) Quality() string

Quality turns a point's flags into "good" or a comma-separated list of flag names. The online bit reads as "offline" when clear.

type PointDB

type PointDB struct {
	// contains filtered or unexported fields
}

PointDB is the outstation's database, shared by its associations.

func NewPointDB

func NewPointDB() *PointDB

NewPointDB makes an empty database.

func (*PointDB) AddEvent

func (db *PointDB) AddEvent(ctx context.Context, e Event)

AddEvent buffers an event that is not a point change (a data set event, an authentication error) in every association.

func (*PointDB) Assign

func (db *PointDB) Assign(typ string, indexes []uint32, class int) (missing bool)

Assign changes the event class of the points of a type (all when indexes is nil); class 0 stops their events (4.4.14).

func (*PointDB) Attach

func (db *PointDB) Attach(b *EventBuffer)

Attach registers an association's event buffer.

func (*PointDB) DataSet

func (db *PointDB) DataSet(id uint32) (DataSetDef, bool)

DataSet returns a copy of a data set definition.

func (*PointDB) DataSetIDs

func (db *PointDB) DataSetIDs() []uint32

DataSetIDs lists the data set identifiers in order.

func (*PointDB) Define

func (db *PointDB) Define(p DBPoint) error

Define adds a point (its value unknown until the first Update).

func (*PointDB) DefineDataSet

func (db *PointDB) DefineDataSet(d DataSetDef) error

DefineDataSet adds an outstation-defined data set (identifiers are contiguous from 0, 5.4.2.8.1).

func (*PointDB) DefinePrototype

func (db *PointDB) DefinePrototype(p Descriptor) error

DefinePrototype adds a data set prototype.

func (*PointDB) Detach

func (db *PointDB) Detach(b *EventBuffer)

Detach removes an association's event buffer.

func (*PointDB) Flat

func (db *PointDB) Flat(id uint32) ([]DescElement, error)

Flat is a data set's data elements with prototypes resolved.

func (*PointDB) Freeze

func (db *PointDB) Freeze(ctx context.Context, indexes []uint32, clear bool, at time.Time) []uint32

Freeze copies counters to frozen counters (clearing them with clear) and returns the frozen points changed. Clearing is a change of the counter: an event when it was not 0 already.

func (*PointDB) Point

func (db *PointDB) Point(typ string, index uint32) (DBPoint, bool)

Point returns a copy of a point.

func (*PointDB) Points

func (db *PointDB) Points(typ string) []DBPoint

Points returns copies of the points of a type in index order.

func (*PointDB) Prototype

func (db *PointDB) Prototype(id uint32) (Descriptor, bool)

Prototype returns a copy of a prototype.

func (*PointDB) PrototypeIDs

func (db *PointDB) PrototypeIDs() []uint32

PrototypeIDs lists the prototype identifiers in order.

func (*PointDB) SetDataSet

func (db *PointDB) SetDataSet(ctx context.Context, id uint32, vals [][]byte, t time.Time, event bool) error

SetDataSet sets a data set's present value (and, with event, buffers a g88 event when the data set has an event class).

func (*PointDB) SetDeadband

func (db *PointDB) SetDeadband(index uint32, v float64) bool

SetDeadband sets an analog point's deadband (g34 writes).

func (*PointDB) SetTimeAndInterval added in v0.2.0

func (db *PointDB) SetTimeAndInterval(index uint32, ti TimeAndInterval) error

SetTimeAndInterval sets a time-and-interval point (Index is ignored).

func (*PointDB) TimeAndInterval added in v0.2.0

func (db *PointDB) TimeAndInterval(index uint32) (TimeAndInterval, bool)

TimeAndInterval reads a time-and-interval point.

func (*PointDB) Types

func (db *PointDB) Types() []string

Types lists the point types defined.

func (*PointDB) Update

func (db *PointDB) Update(ctx context.Context, typ string, index uint32, v float64, raw []byte, flags uint8, t time.Time) (bool, error)

Update sets a point's value, flags and time and creates an event when the point has an event class and the value (or flags) changed beyond its deadband. It reports whether an event was created.

func (*PointDB) UpdateFlags added in v0.3.0

func (db *PointDB) UpdateFlags(ctx context.Context, typ string, index uint32, flags uint8, t time.Time) (bool, error)

UpdateFlags changes only a point's flags (and time), keeping its value; an event follows when the flags changed (communication lost, say).

func (*PointDB) UpdateWith added in v0.3.0

func (db *PointDB) UpdateWith(ctx context.Context, typ string, index uint32, v float64, raw []byte, flags uint8, t time.Time, mode EventMode) (bool, error)

UpdateWith is Update with an event mode.

func (*PointDB) VirtualTerminalOutput added in v0.2.0

func (db *PointDB) VirtualTerminalOutput(ctx context.Context, port uint32, data []byte) error

VirtualTerminalOutput queues data from a port as a virtual terminal event (1 to 255 octets).

func (*PointDB) WriteDataSet

func (db *PointDB) WriteDataSet(ds DataSet) error

WriteDataSet stores a data set written by the master: it must be writable and match its complete structure (5.4.2.7).

func (*PointDB) WriteDescriptor

func (db *PointDB) WriteDescriptor(d Descriptor) error

WriteDescriptor stores a master-defined data set descriptor (5.4.2.6.2); an outstation-defined one cannot be replaced.

func (*PointDB) WritePrototype

func (db *PointDB) WritePrototype(p Descriptor) error

WritePrototype stores a master-defined prototype.

type PointRef

type PointRef struct {
	Group uint8
	Index uint32
}

PointRef is one element of a g86v3 point index attribute object.

func ParsePointRefs

func ParsePointRefs(b []byte) (uint32, []PointRef, error)

ParsePointRefs decodes a g86v3.

type Primary

type Primary struct {
	Reset bool
	NFCB  bool
}

Primary is the primary-station state for confirmed user data towards one secondary peer (9.2.6.1, Table 9-4): whether its link states were reset and the next frame count bit.

func (*Primary) Acked

func (p *Primary) Acked()

Acked records an ACK of the last confirmed frame: the next one toggles the frame count bit.

func (*Primary) Control

func (p *Primary) Control(dir byte) byte

Control is the control octet of the next confirmed user data frame (dir is CtrlDIR for a master, 0 for an outstation).

func (*Primary) ResetDone

func (p *Primary) ResetDone()

ResetDone records an ACK of RESET_LINK_STATES: the next confirmed frame carries FCB 1.

type Reassembler

type Reassembler struct {
	// contains filtered or unexported fields
}

Reassembler rebuilds application fragments from transport segments.

func (*Reassembler) Add

func (r *Reassembler) Add(seg []byte) ([]byte, error)

Add takes one segment. It returns the fragment when the segment carried FIN, nil while more are expected, and an error for a segment that does not continue the current fragment (the partial fragment is discarded).

type RejectedError

type RejectedError struct {
	Func byte
	IIN  IIN
}

RejectedError is a request the outstation answered with IIN2 no-func-code-support, object-unknown or parameter-error.

func (*RejectedError) Error

func (e *RejectedError) Error() string

Error names the internal indications of the rejection.

type Secondary

type Secondary struct {
	// contains filtered or unexported fields
}

Secondary is the secondary-station half of the data link layer for one primary peer (9.2.6, 9.2.8 Table 9-5): link reset state, the expected frame count bit and the last ACK or NACK sent, so a repeated TEST_LINK_STATES gets the same answer.

func (*Secondary) Handle

func (s *Secondary) Handle(f Frame) LinkAction

Handle applies the secondary station rules to a primary frame addressed to this station (individually or by broadcast). Frames whose FCV does not match their function code are discarded (9.2.9). Nothing is ever answered to a broadcast destination, and confirmed user data sent to a broadcast address is discarded (9.2.4.1.3.6, 9.2.5.2.1).

type Secret

type Secret string

Secret is a configuration value that never appears in logs or formatted output.

func (Secret) GoString

func (Secret) GoString() string

GoString hides the value from %#v.

func (Secret) LogValue

func (Secret) LogValue() slog.Value

LogValue hides the value from slog.

func (Secret) String

func (Secret) String() string

String hides the value.

type SecurityEventEntry

type SecurityEventEntry struct {
	At     time.Time
	Event  string
	Detail map[string]string
}

SecurityEventEntry is one recent Secure Authentication security event of an association: its time, name and the detail written to the security log (never key material).

type SessionConfig added in v0.2.0

type SessionConfig struct {
	// Unsolicited lists the event classes to enable for unsolicited
	// reporting at the end of the start-up; reporting is first disabled
	// for every class. Empty: unsolicited reporting is disabled and stays
	// so.
	Unsolicited []int
	// Integrity repeats the integrity poll (classes 1, 2, 3, 0); it also
	// runs at start-up and after an event buffer overflow (IIN2.3).
	// 0: no periodic integrity poll.
	Integrity time.Duration
	// Scans are periodic class scans.
	Scans []ClassScan
	// EventPoll polls the classes the outstation reports events for
	// (IIN1.1 to 1.3).
	EventPoll bool
	// TimeSync sets the outstation's clock when it asks.
	TimeSync TimeSyncMode
	// AssignClass, when set, are the objects of an ASSIGN_CLASS request
	// sent at start-up before the integrity poll.
	AssignClass []byte
	// RetryDelay is the pause after a failed task (default 5 s).
	RetryDelay time.Duration
	// OnData receives the response fragments of the session's polls
	// (unsolicited responses go to MasterConfig.OnUnsolicited).
	OnData func(Fragment)
	// OnTask reports each task run ("disable unsolicited", "clear
	// restart", "assign class", "integrity", "time sync", "enable
	// unsolicited", "class N scan", "event poll") and its error.
	OnTask func(name string, err error)
}

SessionConfig configures RunSession.

type StatusElement

type StatusElement struct {
	Code uint8
	Text string
}

StatusElement is one status element of a g91v1.

type SymmetricAuthority

type SymmetricAuthority struct {
	KeyChangeMethod uint8
	Key             []byte // AES-128 for method 3, AES-256 for methods 4 and 5
	MasterAddress   uint16
}

SymmetricAuthority is an authority sharing the authority certification key with the outstation (methods 3, 4 and 5). MasterAddress is the sender address of the AES-GMAC IV (method 5).

func (SymmetricAuthority) Certify

func (s SymmetricAuthority) Certify(u UserStatus) ([]byte, error)

Certify is the HMAC of the status information with the authority key.

func (SymmetricAuthority) EncryptUpdateKey

func (s SymmetricAuthority) EncryptUpdateKey(name string, key, ostnChallenge []byte) ([]byte, error)

EncryptUpdateKey wraps the update key data with the authority key.

func (SymmetricAuthority) Method

func (s SymmetricAuthority) Method() uint8

Method is the key change method.

type TimeAndInterval added in v0.2.0

type TimeAndInterval struct {
	Index    uint32
	Time     time.Time
	Interval uint32
	Units    uint8
}

TimeAndInterval is the value of a time-and-interval point.

type TimeAndIntervalHandler added in v0.2.0

type TimeAndIntervalHandler interface {
	TimeAndIntervalWritten(ctx context.Context, ti TimeAndInterval)
}

TimeAndIntervalHandler is implemented by a Handler that wants to know when the master wrote a time-and-interval point (the database already holds the new value).

type TimeSyncMode added in v0.2.0

type TimeSyncMode int

TimeSyncMode selects how a session sets the outstation's clock when the outstation asks for it (IIN1.4).

const (
	TimeSyncNone   TimeSyncMode = iota
	TimeSyncNonLAN              // DELAY_MEASURE, then WRITE g50v1
	TimeSyncLAN                 // RECORD_CURRENT_TIME, then WRITE g50v3
)

type UpdateKeyChange

type UpdateKeyChange struct {
	Authority  Authority
	Operation  uint8 // UserAdd (default), UserChange or UserDelete
	SCS        uint32
	UserName   string
	Role       uint16
	Expiry     uint16 // days
	Outstation string // the outstation's name, as configured at the authority
	NewKey     []byte // generated when empty
	// Asymmetric methods: the user's key pair (the public key goes to the
	// outstation in the status change) and the outstation's RSA key.
	UserKey       *dsa.PrivateKey
	UserPublicKey []byte // SubjectPublicKeyInfo octets
	OutstationKey *rsa.PublicKey
	// Certificate, for the asymmetric methods, is an IEC/TS 62351-8
	// certificate from the authority sent as a g120v8 instead of the
	// g120v10 (CertificateType 1 ID or 2 attribute; default 1).
	Certificate     []byte
	CertificateType uint8
}

UpdateKeyChange describes one user status and update key change.

type UpdateKeyReply

type UpdateKeyReply struct {
	KSQ  uint32
	User uint16
	Data []byte // outstation challenge data (v12) or encrypted update key data (v13)
}

UpdateKeyReply is g120v12 (and, with Data the encrypted key, the head of g120v13).

func ParseUpdateKeyReply

func ParseUpdateKeyReply(d []byte) (UpdateKeyReply, error)

ParseUpdateKeyReply decodes g120v12 or g120v13 object data.

func (UpdateKeyReply) Encode

func (r UpdateKeyReply) Encode() []byte

Encode is the g120v12 or g120v13 object data.

type UpdateKeyRequest

type UpdateKeyRequest struct {
	Method    uint8
	Name      string
	Challenge []byte
}

UpdateKeyRequest is g120v11.

func ParseUpdateKeyRequest

func ParseUpdateKeyRequest(d []byte) (UpdateKeyRequest, error)

ParseUpdateKeyRequest decodes g120v11 object data.

func (UpdateKeyRequest) Encode

func (r UpdateKeyRequest) Encode() []byte

Encode is the g120v11 object data.

type UserCertificate

type UserCertificate struct {
	Method uint8
	Type   uint8
	Cert   []byte
}

UserCertificate is a g120v8.

func ParseUserCertificate

func ParseUserCertificate(b []byte) (UserCertificate, error)

ParseUserCertificate decodes a g120v8.

func (UserCertificate) Encode

func (u UserCertificate) Encode() []byte

Encode is the object data.

type UserRoleInfo

type UserRoleInfo struct {
	Roles          []int
	AOR            string `asn1:"utf8"`
	Revision       int
	RoleDefinition string          `asn1:"optional,utf8"`
	Operation      asn1.Enumerated `asn1:"optional"`
	SCS            int64           `asn1:"optional"`
}

UserRoleInfo is one IECUserRoles entry.

type UserStatus

type UserStatus struct {
	Method    uint8
	Operation uint8
	SCS       uint32 // status change sequence number
	Role      uint16
	Expiry    uint16 // days after reception
	Name      string
	PublicKey []byte // asymmetric methods: the user's SubjectPublicKeyInfo
	CertData  []byte
}

UserStatus is the user status information of a g120v10.

func ParseUserStatus

func ParseUserStatus(d []byte) (UserStatus, error)

ParseUserStatus decodes g120v10 object data.

func (UserStatus) Encode

func (u UserStatus) Encode() []byte

Encode is the g120v10 object data.

func (UserStatus) StatusInfo

func (u UserStatus) StatusInfo() []byte

StatusInfo is the data the authority certifies (Table A-10, in order).

type VirtualTerminalHandler added in v0.2.0

type VirtualTerminalHandler interface {
	VirtualTerminalWritten(ctx context.Context, port uint32, data []byte)
}

VirtualTerminalHandler is implemented by a Handler that serves virtual terminal ports.

Directories

Path Synopsis
Package authority is a reference implementation of the update key authority of DNP3 Secure Authentication (IEEE 1815-2012 7.5.4): the party that decides which users exist at which outstation with which role, certifies the user status changes a master forwards, encrypts new update keys for the symmetric methods (3 to 5), and signs status changes and issues g120v8 user certificates with its DSA key for the asymmetric methods (67 to 69).
Package authority is a reference implementation of the update key authority of DNP3 Secure Authentication (IEEE 1815-2012 7.5.4): the party that decides which users exist at which outstation with which role, certifies the user status changes a master forwards, encrypts new update keys for the symmetric methods (3 to 5), and signs status changes and issues g120v8 user certificates with its DSA key for the asymmetric methods (67 to 69).
Package gateway is a DNP3 data concentrator: points a master reads from downstream outstations are republished, under indexes of the gateway's choosing, in the PointDB of the gateway's own outstation, and controls the gateway's outstation receives for mapped outputs are passed down to the outstation that owns them, with its status passed back.
Package gateway is a DNP3 data concentrator: points a master reads from downstream outstations are republished, under indexes of the gateway's choosing, in the PointDB of the gateway's own outstation, and controls the gateway's outstation receives for mapped outputs are passed down to the outstation that owns them, with its status passed back.
Package profile writes and reads a DNP3 device profile document in XML (IEEE 1815-2012 clause 14 and the DNP Users Group's XML device profile): the device's identity and communication settings and, from an outstation's PointDB, its data points list (index, name, default static and event variations, event class, class 0 membership, deadband, scaling and units per point).
Package profile writes and reads a DNP3 device profile document in XML (IEEE 1815-2012 clause 14 and the DNP Users Group's XML device profile): the device's identity and communication settings and, from an outstation's PointDB, its data points list (index, name, default static and event variations, event class, class 0 membership, deadband, scaling and units per point).

Jump to

Keyboard shortcuts

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