Repository navigation
Releases
go-mms v1.5.0
Date: 2026-11-08
Previous release: v1.4.0
Added
DialOptions.AllowedServices: an allow-list of confirmed services for a client. A request for a service that is not listed is refused before it reaches the transport: nothing is sent, the association stays usable, and the call returns an error matching the new sentinelErrServiceNotAllowed(theMetricsrequest event carries the same error). Nil (the default) allows every service, as before; an empty non-nil slice refuses every confirmed request. Initiate, Conclude and Abort are association control and are never refused. It makes a client read-only in the library rather than by convention in the caller; go-iec61850 passes it throughDialOptions.MMS. Requested by otfabric/iec61850-explorer, which must not send a state-changing service in its default mode.
Tests
- A client allowed only Read reads a variable, is refused Write and Identify without a byte reaching the transport or a write reaching the server, keeps the association, and reports both refusals as request events; an empty list refuses Read and still concludes; a nil list writes as before.
go-mms v1.4.0
Date: 2026-11-04
Previous release: v1.3.2
Changed
-
A client now sends the ISO addressing IEC 61850 IEDs expect. It used to omit every selector and the TPDU size unless configured, and an IED that requires them (an ABB REB500, otfabric/go-iec61850#28) refused the COTP CR with a Disconnect Request, reason 3 (address unknown). Defaults, the values libiec61850 sends:
- COTP CR: calling and called T-selector
00 01, TPDU size 1024 (0xC0 = 0x0A). - Session: calling and called S-selector
00 01. Presentation: calling and called P-selector00 00 00 01. - ACSE: calling AP-title
1.1.1.999, called AP-title1.1.1.999.1, AE-qualifier 12 each.
A nil option takes the default; a non-nil empty selector or AP-title omits the parameter, as before (
iso.WithCalledTSelector([]byte{}),ISOOptions{LocalAPTitle: mms.APTitle{}}, ...).iso.WithMaxTPDULength(65531)omits 0xC0 again. IEC 61850-8-1 mandates none of these values (§6.2.3.2.2 limits a T-selector to 4 octets; Table 132 lists them as SCL address parameters). - COTP CR: calling and called T-selector
-
A client refuses a T-selector longer than 4 octets (IEC 61850-8-1 §6.2.3.2.2) before dialing.
-
A client accepts a CC whose responding T-selector differs from the called one (X.224 §13.3.4), for example an IED answering
0001with00000001(go-cotp v1.3.0). -
An
iso.Listenerreturns the CR's T-selectors in the CC, as libiec61850 servers do. It still accepts any called selector unlessWithCalledTSelectoris set; the strict comparison is byte for byte, so0001and00000001differ.
Added
iso.WithMaxTPDULength,iso.DefaultMaxTPDULength(1024),iso.MaxTSelectorLength(4).
Tests
- The CR a default client sends, byte for byte; selectors of 1, 2 and 4 octets, omitted selectors, TPDU sizes 1024, 2048 and omitted; a 5-octet selector refused before dialing; a differing responding selector accepted; a Listener echoing absent, 1-, 2- and 4-octet selectors; a strict Listener accepting a default client and refusing an omitted, 4-octet or different called selector with reason 3;
DialOptions.Transportselectors used byDial. - Upper-layer defaults, configured and omitted values, and the default calling AP-title and AE-qualifier decoded by a go-mms server.
Dependencies
- go-cotp v1.3.0.
go-mms v1.3.2
Date: 2026-11-04
Previous release: v1.3.1
Fixed
- Registering a named variable, named variable list or domain re-sorted the whole name list each time, so building a server model of n variables cost O(n² log n): a 30 000-variable domain took 1.9 s, and a large IEC 61850 IED model several seconds (tens of seconds under the race detector). Names are now inserted at their sorted position (30 000 variables: 0.08 s). GetNameList order is unchanged.
go-mms v1.3.1
Date: 2026-11-04
Previous release: v1.3.0
Fixed
- v1.3.0 refused to encode the zero
time.Timeas a UtcTime, butTypeSpec.DefaultValueused it for every UtcTime, also inside structures and arrays. A server reading an unwritten variable that held a UtcTime (for example an IEC 61850Modwith itst) answered with object-access-unsupported. The zerotime.Timenow encodes as the epoch, the all-zero UtcTime of an unset timestamp, andDefaultValuereturns the epoch. Instants before 1970 or after 2106-02-07 are still rejected. DefaultValueof an ObjectIdentifier was an empty OID, which cannot be encoded; it is now0.0, the shortest valid one.
go-mms v1.3.0
Date: 2026-11-04
Previous release: v1.2.4
Breaking
- Requires Go 1.26 (was 1.23). Built on go-cotp v1.2.0 and go-tpkt v1.1.0. CI tests Go 1.26 and 1.27.
Fixed
- An association whose session CONNECT or ACCEPT carried more than about 230 bytes of user data (for example an AARQ with a long ACSE password) was sent with a one-octet length indicator and could not be parsed by any peer, go-mms included. SPDUs now use the extended length form of ISO 8327-1 up to 65535 bytes, and the parser reads it.
- Invoke IDs are Unsigned32: a negative or wider invoke ID from a peer was wrapped and could match another outstanding request; it is now rejected (the server answers with a Reject). Invoke IDs from 2^31 upwards, which carry a 0x00 pad octet, were refused and are now accepted.
- A
UtcTimevalue outside 1970-01-01..2106-02-07, including the zerotime.Time, was written with wrapped seconds; encoding now fails with an error. - BER lengths of 16 MiB or more were truncated to three octets; integers outside the int32 range were truncated; context tags above 30 lost their tag number. All now encode correctly.
- Negative alternate-access indices, DataAccessError codes and object classes, and file sizes outside Unsigned32, are rejected instead of wrapped.
Changed
- Interop CI pins the iec61850-interop v1.1.1 images (same peer stack commits and
mms-v2fixture as v1.0.1); shared workflows at v3.3.1. - Retry jitter is drawn from crypto/rand. Request writes that outlive the caller's context keep its values (
context.WithoutCancel). - Tests: end-to-end long-password associations, a chaos test of large associations under fragmentation, invoke-ID range tests, and three new fuzz targets (BER integer and length round trips, session SPDU round trip); 60 fuzz targets in all.
go-mms v1.2.4
Date: 2026-10-31
Previous release: v1.2.3
Changed
- Tests, CI and documents only. The coverage variable
COVERAGE_MINis nowCOVER_PKG_MIN. Compiled binaries are gone from git history, and tags were rewritten.
go-mms v1.2.3
Date: 2026-10-31
Previous release: v1.2.2
Changed
- Tests, CI and documents only. The coverage variable
COVERAGE_MINis nowCOVER_PKG_MIN. Compiled binaries are gone from git history, and tags were rewritten. - Built on go-cotp v1.1.1, go-tpkt v1.0.2.
go-mms v1.2.2
Date: 2026-10-28
Previous release: v1.2.1
Summary
Interop and documentation release; the library code is unchanged. The
interop peers' repository, otfabric/mms-interop, is now
otfabric/iec61850-interop,
and iec61850bean joins the CI matrix as a fourth stack.
Changes
Tests
- iec61850bean — iec61850-interop packages iec61850bean with IEC 61850 IED adapters only. The new
TestIEDServer_*suite drives the go-mms client againstiec61850bean-ied-serverover plain MMS (association, name lists, data set attributes, variable access attributes, fixture reads, writes, Abort and Close). Its quirks are asserted, not skipped: no Identify and no Conclude response, and a read of an unknown name disconnects. The MMS contract suite skips for this stack. - iec61850-interop v1.0.1 — CI pins the v1.0.1 images (
ghcr.io/otfabric/iec61850-interop-<stack>) for libiec61850, dscsystems, iec61850-rust and iec61850bean.
Docs
- INTEROP.md, COMPATIBILITY.md and interop/README.md use the new repository and image names and list iec61850bean. Earlier entries in this file keep the image names they were published with.
go-mms v1.2.1
Date: 2026-10-28
Previous release: v1.2.0
Summary
Moves to go-cotp v1.1.0,
which arms read and write deadlines per direction, and removes the go-mms
workaround for the shared deadline of go-cotp v1.0.4. No API changes.
Changes
Changed
- go-cotp v1.1.0 (go-tpkt stays at v1.0.1).
transport/isono longer wraps the connection given to go-cotp (splitDeadlineConnand its per-direction deadline handling are removed);Receivepasses its context to go-cotp,Sendpasses the deadline but not the cancellation of its context, so a cancelled request still never abandons a TSDU half-written. - Receive with an expired context — a context that ends before any octet of the next TSDU arrived returns the context error and leaves the connection usable (it used to abort it); one that ends mid-TSDU aborts the connection. Errors caused by a context match
context.Canceled/context.DeadlineExceededwithout being wrapped twice. The MMS client reader and the server association timeout behave as before (ErrClosed,ErrAssociationFailed+context.DeadlineExceeded).
Docs
- TIMEOUT_AND_CLOSE.md and RACE_NOTES.md describe go-cotp's per-direction deadlines; the go-cotp entry is removed from KNOWN_LIMITATIONS.md.
go-mms v1.2.0
Date: 2026-10-28
Previous release: v1.1.0
Summary
Operability and quality release. Errors are classified so errors.Is /
errors.As work across every failure path; a pluggable Metrics interface,
configurable retry with backoff and jitter, Server.Shutdown, association and
handshake timeouts and Client.Done / Client.Err are added. A cancelled
request no longer tears down its association, and a silent peer can no longer
block iso.Listener.Accept. Fuzzing found and this release fixes several
codec bugs (Cancel PDU tags, BinaryTime, UTCTime fractions, long-form tags).
Every package now has ≥90% test coverage, enforced in CI. All API changes are
additive.
Interop runs against three MMS peers from the mms-interop
v0.3.0 images,
pinned by digest: libiec61850 (120 pass), dscsystems/go-iec61850 (112 pass,
8 skipped peer limitations) and iec61850-rust (120 pass).
Changes
Added
- Errors —
ErrServerClosed,ErrInvalidArgument;PhaseSession,PhasePresentation,PhaseACSE,PhaseMMS;RejectType(withString) andRejectErrorfor MMS Reject PDUs (it unwraps to the previous*ProtocolError, soerrors.Is(err, ErrProtocol)still holds);ServiceError.CodeName()(ISO 9506-2 code names). Failures carry sentinel kinds without changing their messages: association →ErrAssociationFailed, negotiation →ErrNegotiationFailed, connection loss →ErrClosed(+ErrDecodeFailed/ErrInvalidPDU), request deadline →ErrInvokeTimeout+context.DeadlineExceeded, argument validation →ErrInvalidArgument. - Metrics —
Metricsinterface (ObserveRequest,ObserveConnection),NopMetrics,Role,Service,RequestEvent,ConnectionState,ConnectionEvent; set viaDialOptions.MetricsandServerOptions.Metrics. OBSERVABILITY.md has a Prometheus adapter example. - Retry —
RetryPolicy(attempts, exponential backoff, jitter,Retryable,OnRetry),DefaultRetryPolicy,IsRetryable,Retry;iso.WithRetryretriesDialTCPandDial(connect, TLS, COTP and association), honouring the context. - Lifecycle —
Client.Done,Client.Err,DialOptions.RequestTimeout,Server.Shutdown(ctx)(closes listeners, waits for in-flight handlers, aborts associations;Serve/ListenAndServethen returnErrServerClosed),ServerOptions.AssociationTimeout,iso.WithHandshakeTimeout,iso.WithDialer. RawHookSend/RawHookRecvconstants for the raw PDU hook direction.
Changed
- Request cancellation — a request whose context ends mid-write returns at once; the write completes in the background (an unstarted one is skipped) instead of closing the association.
- Association timeout —
Serveaborts an association that has not completed its handshake within 30s by default (ServerOptions.AssociationTimeout; negative disables).Servealso closes the transport when its context is cancelled. - iso.Listener — TLS and COTP handshakes run concurrently (up to 64 pending) with a 10s default timeout, so a peer that connects and stays silent no longer blocks other connections. Temporary accept errors (such as EMFILE) are retried with backoff.
ServiceError.Error()includes the code name, e.g.(object-non-existent). Compare witherrors.Asand theClass/Codefields, never the string.- Server error mapping — a
*DataAccessErrorreturned by a Read/Write callback is sent with its code (other errors: temporarily-unavailable, as before); a*ServiceErrorfrom a handler is sent as returned; a malformed Status request gets the invalid-request service error. Value.Equalcompares floats bitwise (all NaNs equal,+0≠-0).- Logs carry
roleandremote_addr; requests are logged at debug level withservice,invoke_id,durationanderror. Messages are prefixedmms:/iso:.
Fixed
- Cancel PDUs — cancel-Request/Response/Error use tags
[5],[6],[7](the tag constants were wrong). - BinaryTime — values below one day encode in the 4-octet TimeOfDay form (previously not re-encodable after decoding); the decoder rejects a millisecond-of-day ≥ 86 400 000.
- UTCTime — the fraction of a second round-trips losslessly.
- GeneralizedTime — a fractional second accepted on decode is kept on encode (it was dropped); whole seconds encode as before.
- Long-form tags — MMS PDUs with a long-form outer tag are rejected; tag numbers above 30 encode correctly.
- Deadline classification — an I/O timeout caused by a context deadline is reported as
context.DeadlineExceededeven when the connection deadline fires just before the context's timer (association timeout, ISO transport). - Full-duplex deadlines — go-cotp v1.0.4 arms a both-direction deadline around each read and write; a write deadline could cut a concurrent read (
read tpkt header: i/o timeout). transport/iso now uses per-direction deadlines. Client.Aborthonours its context; closing a transport whose deadline already fired no longer returns a timeout error.- Server callback errors that are not wire errors no longer reach the error-code encoder (possible panic path).
Tests
- Coverage ≥90% in every package and in total, enforced by
scripts/coverage-gate.sh(make coverage, CI quality job). internal/chaos: fault-injectingnet.Conn/net.Listener(delays, chunked I/O, bandwidth limit, drops, resets, stalls).e2echaos tests (lossy, drops with reconnect viaRetry, slow peer) and a concurrent stress test under-race;make chaos.- 57 fuzz targets, including
FuzzValueCodec,FuzzServerDispatch,FuzzServerSessionand new session/presentation/ACSE, codec and BER targets;scripts/fuzz.shdiscovers them (make fuzz,make fuzz-shortinmake checkand CI). - Interop is stack-parametrized (
MMS_INTEROP_STACK,make interop-all): one test body per scenario, run in CI against libiec61850, dscsystems/go-iec61850 and iec61850-rust from mms-interop v0.3.0. Peer limitations are explicit per-stack quirks or skips with reasons (INTEROP.md). NewTestClient_PeerDisconnect(Done/Err) andTestServer_Shutdown.
Docs
- ERRORS.md, OBSERVABILITY.md, TIMEOUT_AND_CLOSE.md, API.md, README.md, LIMITS.md, KNOWN_LIMITATIONS.md, COMPLIANCE.md, INTEROP.md, RACE_NOTES.md and RELEASE_CHECKLIST.md updated; PROGRESS.md replaced by a current-status summary. Removed stale PLAN.md, REQUIREMENTS.md, extract_c_functions.py and mms_functions.csv.