Repository navigation
Releases: otfabric/go-iec104
Release list
go-iec104 v0.0.3
Date: 2026-10-10
Previous release: v0.0.2
Summary
File transfer in control direction and the file directory, a comparison
with wendy512/iec104, and a folder of examples to start from. A thorough
overhaul of the order in which the two protocol machines, the redundancy
group and the event queue change state and tell their applications: 25
fixes, found by a new suite of state machine tests, a randomized chaos test
and two independent reviews of the code. No breaking API change.
Upgrade notes
Close(client, group, session and server) now waits for a handler call in progress to return. A handler that never returns blocks it; see "Changed".- A request that is waiting when data transfer stops now fails with
iec104.ErrNotStartedinstead of its timeout error; see "Fixed". - A request method called from inside the client's
Handleror state handler now fails at once with the newiec104.ErrInHandler. It could never be answered there and used to run into its timeout; see "Changed".
Changes
Added
- File upload —
Client.PutFile(andGroup.PutFile) delivers a file to a station: file ready, then the sections and segments the station calls for, each with its checksum, until the station acknowledges the file. On the station,server.FileServer.Sink(aFileSink) decides whether a file is accepted and stores what arrived intact; a length or checksum that does not match, or a failed store, is acknowledged negatively. - File directory —
Client.ListFiles(andGroup.ListFiles) calls the directory and returns its entries;server.FileServer.Directoryanswers the call, asF_DR_TA_1sequences. - Names for the secure authentication types —
asdu.S_CH_NA_1..asdu.S_UC_NA_1andasdu.S_IT_TC_1(IEC TS 60870-5-7),TypeID.IsSecurity, and the causesasdu.CauseAuthentication,CauseSessionKeyandCauseUserRoleUpdateKey. These types print by name and are still carried asASDU.Raw: the procedures of IEC 62351-5 are not implemented (see SECURITY.md). - Examples — nine programs under
examples/:quickstart,commands,files,events,redundancy,tlsandobservabilityare self-contained and run withgo run;serverandclientare the two separate programs that were there.make examplesruns the self-contained ones, and so does CI.
Changed
server.FileTypesnow also listsF_FR_NA_1,F_SR_NA_1,F_SG_NA_1andF_LS_NA_1, which a station receives when a file is delivered to it. AFileServerwithout aSinkrefuses them with "unknown information object address"; before, aMuxanswered them with "unknown type identification".Closewaits for the callbacks, onClient,Group,SessionandServer. It returns when theHandler, the state handler and the switch handler have been called for the last time (see "Fixed"); several callers at once all wait for that. A handler that blocks forever therefore blocksClosecalled from another goroutine. From inside a handlerClosereturns at once, as before, the handler is not called again, and the close completes when that call has returned.Client.Closehands over what was received. Called from outside a callback it first gives theHandlereverything the connection had already received and acknowledged to the station.- Callbacks may call back. A client state handler may call
StartDT,StopDT,Send,ConnectandClose; a group's state and switch handlers may callSwitchoverandClose; a server handler may callSession.CloseandServer.Close, also from the first "stopped" notification. Before, several of these deadlocked. - New error
iec104.ErrInHandlerfor a client request method (Command,Interrogate,Read,GetFile, ...) called from the client'sHandleror state handler. The answer is delivered by the goroutine the callback occupies: from theHandlersuch a call always ran into its timeout, from the state handler it did so depending on timing. It now fails at once, every time. It is not retried. Group.Switchoverreports when nothing was switched. It returnsErrNotConnectedwhen the standby could not be started and data transfer is back on the connection it was on; before, it returned nil.- A
Groupfollows its connections, not theHandler. It notices a lost or stopped connection when the connection changes, also while the application'sHandleris busy on it. It no longer holds its lock while the application is called: a slow state handler delays the notifications of its own connection and nothing else. - A
GroupwaitsReconnect.MinDelay, doubling up toMaxDelay, before it dials an address again that could not be reached. server.NewFileServeraccepts a nil source, for a station that only takes files or only has a directory.- The comparison matrix in the README has a column for wendy512/iec104.
Fixed
Link and state order:
-
A station confirmed STOPDT and STARTDT before it had changed its own state. For a moment after the confirmation was on the wire,
Session.Startedstill reported the old state andSession.Sendfollowed it: a controlling station that had its STOPDT confirmed could find the station still "started". The state now changes before the confirmation is sent. Found by the end-to-end tests on a CI runner. -
A request that was waiting when data transfer stopped kept waiting until its timeout. After STOPDT (the client's own, or a group's switchover) the station cannot answer any more. Such requests now fail at once with
iec104.ErrNotStarted, after everything the station sent before it confirmed the stop has been delivered, so an answer that did arrive is not lost. A request sent after the restart is not affected by the stop before it. -
Sending on a connection that was just ending could report the wrong error. For a moment during shutdown a
Send, and with it any request, was toldErrNotStarted, orErrConnectionLostfor a connection the application itself was closing. It now gets the reason the connection ended:ErrClosedfor a localClose. -
A deactivation confirmation could pass for an activation confirmation. The late answer to a
Deactivatethe caller had given up on was taken as the positive confirmation of a following command on the same object. Confirmations are now matched by cause. -
A directory listing could break a file transfer on the same connection.
ListFilesrunning whileGetFileorPutFilewas under way failed that transfer with a protocol violation when the directory listed the file being transferred first: the directory was taken for a part of the transfer. Found by the end-to-end tests in the release pipeline. -
A directory listing could be returned as the value of a read.
Readof an address took a directory thatListFileshad called for at the same time, when the directory listed a file at that address: both carry the cause "request". Interrogations and reads no longer take anything that belongs to file transfer. Found by the cross-talk tests below.
Close:
- Callbacks could still arrive after
Closehad returned.Client.Close,Session.CloseandServer.Closereturned as soon as the connection was down, while aHandleror state handler call could still be in progress or about to start. They now return when the last callback has returned and none follows. - After
Closefrom inside a handler, the handler was called again for what was still queued, on client and server. Closefrom a state handler could deadlock:Client.Closefrom the client's state handler, andSession.CloseorServer.Closefrom the first "stopped" notification of a session.- A second
Closereturned early. Called while anotherClosewas still waiting for a handler,Client.CloseandServer.Closereturned at once. Server.Closedid not wait for sessions that were already ending: one whose connection the peer had dropped and whose state handler was still running, or one whoseHandlerhad closed its own session.- A client lost monitoring data it had acknowledged.
Client.Closediscarded what the connection had received but theHandlerhad not yet seen; the station had been told it arrived and did not send it again. Found by the chaos test. - A client could be told about a connection after it had closed it, and "disconnected" twice, when the station dropped the connection at the moment the application called
Close. - A client could report "stopped" for a connection that was already lost, before "disconnected". A connection that has ended is now only reported by its loss.
ConnectracingClosecould leave a closed client reporting a state other than "disconnected", and read the client's state without its lock (a data race, found by the chaos test).
Event queue (Server.Enqueue):
- Events could be sent twice on the same connection after a STOPDT and STARTDT, when the session's
Handlerwas slow: the queue learned of the stop only when theHandlerhad returned and took the events of the next started period for unacknowledged ones. The queue now follows the connection itself. - Events could wait in the queue of a started connection when the controlling station sent STARTDT while its STOPDT was still waiting for an acknowledgement; they moved again with the next event.
- A switchover could be held up to t1 by the old connection, when its window was full and it no longer acknowledged.
- The application's logger is no longer called with the queue's lock held.
Redundancy group (client.Group):
- Two connections could end up started. A STARTDT the group had given up on could still be confirmed later, and nothing stopped that connection. The group now stops such a stray, or drops the connection when it cannot.
Group.CloseandGroup.Switchoverfrom a state handler deadlocked, and a state handler that was slow held up the whole group,Closeincluded...
go-iec104 v0.0.2
Date: 2026-10-10
Previous release: v0.0.1
Summary
Fixes a client bug that could hide a refused command, and closes the gaps
with lib60870-C and j60870: file transfer (typed ASDUs and the download
procedure in both roles), server-side redundancy groups and a server-side
event queue with acknowledged delivery. Qualified against three reference
stacks instead of two. No breaking API change.
Upgrade if you use v0.0.1: see "Fixed".
Changes
Added
- File transfer types (
asdu) —F_FR_NA_1..F_SC_NB_1(120..127) now decode intoFileReady,SectionReady,FileCall,FileLastSegment,FileAck,FileSegment,FileDirectoryEntryandFileQueryLog, with the qualifier constants andTypeID.IsFileTransfer. 67 type identifications are modelled. - File download —
Client.GetFile(andGroup.GetFile) runs the file transfer procedure in monitor direction: select, call, sections, segments, with the length and checksum of every section and of the file checked and acknowledged.server.FileServer, registered on aMuxforserver.FileTypes, serves the files of aFileSource;server.NewFilesplits content into sections andFileServer.OnDonereports the end of each transfer. - Server-side redundancy groups —
server.WithRedundancyGroupsassigns connections to groups by client address;Session.Group. The connection of a group that started data transfer last is its active one. - Event queue —
Server.Enqueuequeues an event per redundancy group, delivers in order to the group's active connection, keeps events while no controlling station is connected and repeats what was not acknowledged when a connection stops or is lost.server.WithEventQueuesets the size (default 1024, oldest dropped when full);Server.PendingandServer.Droppedexpose it.
Changed
- File transfer ASDUs are no longer raw. Code that read
ASDU.Rawfor types 120..127 now finds typedASDU.Objectsinstead.ASDU.Rawremains for the private range. - A server now turns away a client that no redundancy group allows. With the default configuration (one group for everyone) nothing changes.
Fixed
- A command or interrogation could take the activation termination of an earlier request for its own answer.
Command,Deactivate,ClockSync,TestCommandandResetProcessreturned success, without waiting for the confirmation, when the termination of a previous command to the same object arrived after the new one was sent: a refusal by the station went unnoticed.InterrogateandCounterInterrogatecould likewise return early, with no data, on the termination of the previous run. A termination now only ends the request whose confirmation preceded it. Present in v0.0.1; found by the new end-to-end tests, where both sides answer within microseconds.
Verification
-
End-to-end tests of the library against itself (package
e2e, part ofgo test ./...), 33 tests that execute about 85% of the library:- the client against a go-iec104 station serving the interop fixture, over TCP, TLS and with
k = w = 1: interrogation, counters and read value by value; all command types, select-before-operate and refusals; file download; STARTDT/STOPDT; the idle test in both directions; reconnect; concurrent sessions; the event queue; redundancy groups on the client and on the server; - a wire matrix of all 67 type identifications in every variant, through both codecs and both protocol machines, also with the IEC 101 field sizes and a non-UTC time zone, and a 40,000-frame run across the sequence number wrap;
- state callbacks, logs and metrics of both sides checked against each other; retries; admission (session limit, accept filter, redundancy groups by address); event queue overflow; broadcast; timeouts, cancellation,
ErrBusyand shutdown under a pending request; handler panics; mutual TLS; unsolicited data amid requests; STOPDT during an event stream; a silent link detected by t1 on both sides.
- the client against a go-iec104 station serving the interop fixture, over TCP, TLS and with
-
Byte-level vectors and fuzzing for the file transfer types.
-
Qualified against otfabric/iec104-interop v0.2.0, pinned by digest: lib60870-C v2.4.1, j60870 1.7.2 and, new, wendy512/iec104 v1.0.4 on go-iecp5 v1.2.6 as a third independent implementation. All three pass in both directions.
-
File download against the reference stacks:
GetFilefetches the files of a lib60870-C station with the content its fixture defines, and the lib60870-C and j60870 clients download from a go-iec104 station and see, ASDU by ASDU, what they see from the lib60870-C file server. -
The interop suite reads the capabilities of each reference image and skips what an image does not declare: commands with time tag and file transfer for wendy512/iec104, the file server role for j60870.
-
The event queue is tested against all three reference clients: events queued before a connection arrive in order and their acknowledgements empty the queue.
Still open against lib60870-C
- File transfer in control direction (upload), the file directory and deletion: typed in the codec, no procedure.
- IEC 60870-5-101 serial link layer: out of scope for this module (see README).
go-iec104 v0.0.1
Date: 2026-10-09
Previous release: none
Summary
First release: an IEC 60870-5-104 controlling station (client) and
controlled station (server) in pure Go on a shared protocol machine, with
an ASDU codec that is independent of the transport. No dependencies outside
the standard library; Go 1.23 or later.
The library is qualified against two independent implementations, MZ
Automation lib60870-C v2.4.1 and OpenMUC j60870 1.7.2, as client and as
server.
The API is not frozen: until v1.0.0 a minor release may change it.
Changes
Added
client—Dial/Connect,StartDT/StopDT/TestLink,Interrogate,CounterInterrogate,Read,ClockSync,Command,Deactivate,TestCommand,ResetProcessand rawSend. Every received ASDU is delivered in order to aHandler. TLS (IEC 62351-3), customDialer, connection state handler.- Resilience —
context.Contexton every call; automatic reconnect with exponential backoff (WithReconnect); a start handler that runs on every fresh connection (WithStartHandler); automatic retries of the requests that only read (WithRetry), never of commands. - Redundancy groups (
client.Group) — several connections to one station with exactly one started, automatic failover and manualSwitchover. This is the protocol's counterpart of a connection pool. - Observability — silent by default. Structured logging through
FieldLogger/ContextLogger(NewSlogLoggerreturns both), with a formatted fallback for printf-style loggers. Metrics hooks for connections and frames (Metrics), request outcomes with duration and retries (RequestMetrics) and handler duration (HandlerMetrics). server—Serverwith any number ofSessions;Muxrouting by type identification with the refusals of the standard (unknown type, cause, common address);Confirm/Negative/Terminate/Reject;Broadcastto started sessions; session limit, accept filter, TLS with a bounded handshake.asdu— model and codec for 59 type identifications: process information in monitor direction (1..21, 30..40), process commands (45..51, 58..64), system information (70, 100..107) and parameters (110..113). CP24Time2a and CP56Time2a, SQ = 0 and SQ = 1, configurable field sizes (Params) so that the package serves IEC 60870-5-101 as well. File transfer and private types travel asASDU.Raw.apci— APDU framing (Frame,Parse,ReadFrame,AppendBinary), I/S/U control fields, 15-bit sequence arithmetic andParams(k,w,t0..t3) with validation.- Protocol machine (
internal/link) — send and receive sequence numbers, thek/wwindows,t1/t2/t3supervision, STARTDT/STOPDT/TESTFR in both roles including the "unconfirmed stopped" state, receive flow control that withholds acknowledgements before it stops reading. - Root package
iec104— sentinel errors (ErrClosed,ErrNotConnected,ErrConnectFailed,ErrNotStarted,ErrConnectionLost,ErrTimeout,ErrProtocol,ErrBusy,ErrInvalidOption),NegativeError,State, theLoggerandMetricsinterfaces with their extensions,log/slogandlogadapters. - Examples —
examples/serverandexamples/client, and runnable examples in the package documentation. - Docs — README, API.md, ARCHITECTURE.md, INTEROPERABILITY.md, ERRORS.md, OBSERVABILITY.md, SECURITY.md, CONTRIBUTING.md.
Verification
-
Unit and integration tests with the race detector: codec vectors from the standard, round trips over every type identification, a scripted peer for every window, timer and protocol violation, client and server against each other including TLS and reconnect. Statement coverage is above 90% in every package.
-
Fuzzing of
apci.Parse/ReadFrameandasdu.Decode(parse, re-encode, fixed point). -
Interop suite (
interop,make interop, the Interop workflow on every push) against the images of otfabric/iec104-interop v0.1.0, pinned by digest:Reference go-iec104 client → reference server reference client → go-iec104 server lib60870-C v2.4.1 pass pass j60870 1.7.2 pass pass In the server direction each of 30 scenarios must have the outcome the standard prescribes, and the reference client's complete result must equal what it gets from the server of its own stack.
Known limitations
- No file transfer state machine and no object model for types 120..127.
- Redundancy groups are managed on the client side only; a server does not group its sessions. No buffering of events for stopped connections.
- No connection pool, deliberately: connections of this protocol are sessions, not interchangeable request channels. Use a redundancy group.
- No IEC 62351-5 application-layer authentication.
- A client
Handlercannot call the request methods (Command,Interrogate, ...) from insideHandleASDU; use another goroutine. - Verified against lib60870-C and j60870 only; not against certified test equipment or field devices. The interop suite covers the types of the reference fixture; the other type identifications are covered by this repository's codec tests.