Repository navigation
Releases: otfabric/go-modbus
Release list
v1.1.3
Date: 2026-07-30
Previous release: v1.1.2
Summary
Patch release: bump go-serial to v0.1.6. No API, behaviour, or wire-semantics changes.
Changes
Dependencies
- go-serial —
v0.1.5→ v0.1.6 (docs/tooling; no serial API change).
Unchanged
- Library API, codec, sunspec, server/client behaviour, and supported function codes are identical to v1.1.2.
v1.1.2
Date: 2026-07-30
Previous release: v1.1.1
Summary
Patch release: align the README Go version with go.mod, document write retry at-least-once semantics, add thin ERRORS.md / OBSERVABILITY.md guides, trim README overlap with API.md, and harden the Makefile (govulncheck, isolate from a parent go.work). No API, behaviour, or wire-semantics changes.
Changes
Documentation
- ERRORS.md — Short error taxonomy: library/transport failures vs wire Modbus exceptions; quick-reference table; pointers into
API.md§ 4 and retry notes. - OBSERVABILITY.md — Silent-by-default logging, debug frame-payload caveats,
ClientMetricsvs optionalAttemptMetrics; pointers intoAPI.md§ 5–6. - README — Install floor corrected from Go 1.21 to Go 1.23 (matches badge /
go.mod); links to the new guides; Logging / Error / Metrics sections trimmed. - README — New Project structure section (top-level folders and key docs).
- README — Retry, Pool sections trimmed to short summaries that point at
API.md. - API.md § 7 — New Write operations and at-least-once delivery note: retries apply to reads and writes alike; the classifier does not inspect function code; a retry after bytes may have been sent can deliver a write at least once. Prefer
NoRetry/ nil policy (or application-level idempotency) for non-idempotent writes. - Godoc —
Config.RetryPolicyand public/internalRetryPolicydocument the same write-safety caveat.
Build / tooling
- Makefile
vuln—govulncheck ./..., included inmake check. - Makefile — exports
GOWORK=offso checks ignore a parentotfabric/go.workthat does not list this module.
Unchanged
- Retry defaults remain
nil/NoRetry(). No change to retry classification or when writes are retried — behaviour is documented only. - Library API, codec, sunspec, server/client behaviour, and supported function codes are identical to v1.1.1.
v1.1.1
Date: 2026-07-08
Previous release: v1.1.0
Summary
Patch release preparing the repository for public open-source release under the MIT License: standardized root license and SPDX headers, normalized README badges for a public GitHub repo, and a dependency bump of github.com/otfabric/go-serial to the latest public v0.1.5. No API, behaviour, or wire-semantics changes.
Changes
License & open-source hygiene
- LICENSE — Replaced
LICENSE.txtwith a root-levelLICENSEfile using standard MIT text (Copyright (c) 2026 OT Fabric). - SPDX headers — Added
// SPDX-License-Identifier: MITto all first-party.gosource files (141 files). - README — License section normalized to point at LICENSE.
README badges
- Standardized badge block — Reordered and updated badges for public release: Go version (
1.23+, fromgo.mod), pkg.go.dev, License, CI, Codecov, Release. - Go Reference — Added pkg.go.dev badge linking to
github.com/otfabric/go-modbus. - Codecov — Removed embedded
?token=...from the badge URL; uses the publiccodecov.io/gh/otfabric/go-modbusendpoint (works after repo is public and first upload completes). - Go Report Card — Removed; the service is no longer maintained.
- Release badge — Normalized to
label=release.
Dependencies
- go-serial — Bumped from v0.1.3 to v0.1.5 (
github.com/otfabric/go-serial).
Unchanged
- No breaking changes. Library API, codec, sunspec, server/client behaviour, and supported function codes are identical to v1.1.0.
v1.1.0
Date: 2026-07-05
Previous release: v1.0.4
Summary
Server-side Read Device Identification (FC43) and a full back-to-back conformance suite. The server now supports FC43 / MEI type 0x0E (Read Device Identification) via a new optional DeviceIdentificationHandler interface, with the server owning all MEI framing, category filtering, ordering, and MoreFollows/NextObjectID pagination. This completes server-side coverage of the target function-code set (FC1–6, 15, 16, 22, 23, 43) used to exercise gateways with modbusctl. Alongside the feature, a comprehensive client-vs-server back-to-back test suite is added (deterministic conformance, property-based differential testing, and native fuzzing) and normal unit-test coverage is expanded, raising core package coverage from ~79% to ~88%. Additive change only — no existing API, behaviour, or wire semantics change.
Changes
Server — FC43 Read Device Identification
- New optional interface —
DeviceIdentificationHandlerwithHandleDeviceIdentification(ctx, *DeviceIdentificationRequest) (*DeviceIdentificationResponse, error). If theRequestHandleralso implements it, FC43 is dispatched to it; otherwise the server returnsIllegal Function. - Server-owned framing — The handler returns the complete object set plus a conformity level; the server performs MEI validation (only 0x0E accepted), Read-Device-ID-code validation (basic/regular/extended/individual), category filtering, object-ID ordering, single-response PDU sizing, and stream pagination via MoreFollows / NextObjectID. Individual access to an unknown object returns
Illegal Data Address. - Conformity derivation — When the handler leaves
ConformityLevelunset (0), the server derives a sensible level from the object set and advertises individual access. - Dispatch —
server_transport.goroutesFCEncapsulatedInterfaceto the newhandleReadDeviceIdentification.
Tests — back-to-back conformance suite
- Harness (
backtoback_test.go) — Reference in-memory device implementing every server handler interface,startPairhelper for connected client/server pairs over TCP and TCP+TLS on ephemeral ports, aforEachTransportmatrix runner, and MBAP invariant assertion helpers. - Deterministic conformance (
conformance_test.go) — Per-FC round-trip / echo / packing / boundary / exception tables across the TCP and TLS matrix, including FC22/23/43 semantics. - Adversarial server branches (
conformance_adversarial_test.go) — Raw-TCP malformed frames for FC05/06/15/16/22/23/43 asserting correct exception codes or link close on protocol errors. - Property-based (
property_test.go) — Seeded randomized differential testing against a shadow model for read/write/mask/read-write-multiple, plus randomized FC43 pagination reassembly. - Fuzzing (
fuzz_test.go) —FuzzServerRequest(arbitrary PDUs to a running server) andFuzzClientResponseParse(arbitrary bytes to the client parser) with seed corpora; seeds run as regular tests undergo test.
Tests — expanded unit coverage
- Programmable mock server (
mockserver_test.go) for scripting adversarial client-facing responses. - Client protocol/response validation (
client_protocol_test.go) — wrong FC, byte-count and echo mismatches, and error branches for FIFO, read/write-multiple, file records, and register-byte writes. - Diagnostics & probing (
client_diag_test.go) — FC08 wrappers (happy + bad-length),Diagnostics,ReadExceptionStatus, comm-event counter/log,ReportServerID, andProbeFunction/SupportsFunctionoutcomes. - Serial wrapper (
serial_test.go) — configuration and not-open error paths that need no hardware. - Construction/dial errors (
construction_errors_test.go) — client and server config validation plus TCP/RTU/TLS dial and listen failures. - Coverage — Core package statement coverage increased from ~79.2% to ~88.0%;
go test -race,go vet ./..., andgolangci-lintare clean.
Build / CI
- Makefile — New
fuzztarget running both fuzz targets for a configurableFUZZTIME(default 30s). - ci.yml — Added an opt-in
fuzzjob (make fuzz FUZZTIME=5m) gated behind aworkflow_dispatchboolean inputrun_fuzz(defaultfalse), so it runs only on a manual trigger with the toggle enabled and keeps push/PR CI fast; seed corpora still run as unit tests in the main job.
Documentation
- README.md — Server capabilities table and notes updated for FC43 and its optional handler.
- API.md — Documented
DeviceIdentificationHandlerand its request/response types, and the server's auto-framing/pagination behaviour.
Examples
- examples/tcp_server —
exampleHandlernow implementsDeviceIdentificationHandler, serving static identification objects.
Unchanged
- No breaking changes. FC43 support and all new tests are additive; existing library API, codec, sunspec, wire behaviour, and previously supported function codes are identical to v1.0.4.
v1.0.4
Date: 2026-03-23
Previous release: v1.0.3
Summary
Binary name fix. The binary name is now correctly set to modbus-cli iso sclgen.
v1.0.3
Date: 2026-03-23
Previous release: v1.0.2
Summary
CLI overhaul. The modbus-cli command line tool is migrated from flag to cobra, gaining shell completion (bash, zsh, fish, powershell), a version subcommand with full build metadata, and structured help. Build-time version injection switches from an embedded version.txt to ldflags (version, tag, commit, buildDate). The Go module minimum version is bumped to 1.23. CI and release workflows are updated to the v2 shared workflows with multi-version testing and a separate binary release job for modbus-cli.
Changes
CLI — cobra migration
- Framework — Replaced
flagwithgithub.com/spf13/cobra. All existing flags are preserved as persistent cobra flags with identical names and defaults. - Shell completion — Built-in
completionsubcommand generates completion scripts for bash, zsh, fish, and powershell. Flag values (--parity,--endianness,--word-order) and operation prefixes (rc:,rh:,scan:, etc.) have custom completion functions. - Help — Cobra-generated help replaces the monolithic
displayHelp(). TheLongdescription includes the full operations reference; examples are shown via theExamplefield. - Backward compatible — Operations are still passed as positional arguments using the existing colon-separated DSL. No command syntax changes.
CLI — version subcommand
modbus-cli version— Prints version, tag, commit, and build date. All four fields are injected via ldflags at build time; without ldflags (e.g.go run) sensible defaults are shown (dev,none,unknown).- Removed —
cmd/modbus-cli/version.txtdeleted. The--version/-vflag is removed; use theversionsubcommand instead. - ldflags contract —
-s -w -X main.version=${VERSION} -X main.tag=${TAG} -X main.commit=${COMMIT} -X main.buildDate=${BUILD_DATE}.
CI/CD
- ci.yml — Updated to shared workflow
otfabric/.github/.github/workflows/go-ci.yml@v2. Explicit Go version matrix: 1.23, 1.24, 1.25, 1.26. - release.yml — Split into two jobs:
release-package(library,go-package-release.yml@v2) andrelease-modbus-cli-binary(CLI binary,go-binary-release.yml@v2with ldflags injection). Major tag update disabled.
Build
- Makefile —
build-cmdderivesVERSION,TAG,COMMIT, andBUILD_DATEfrom git and injects them via ldflags. All four variables are overridable (?=). Fixed pre-existingBIN_DIRtrailing-space bug caused by inline comment.
Dependencies
- Go — Minimum version bumped from 1.21 to 1.23.
- go-serial — Bumped from v0.1.2 to v0.1.3.
- cobra — Added
github.com/spf13/cobrav1.10.2 (and transitive depsspf13/pflag,mousetrap).
Documentation
- README.md — CLI help output updated:
versionsubcommand description changed to "Print build version information";--versionflag removed from flags listing.
Unchanged
- No changes to the Modbus library API, codec, sunspec, or server. All types, functions, and constants are identical to v1.0.2.
go-modbus v1.0.2
Date: 2026-03-21
Previous release: v1.0.1
Summary
Patch release: unit test coverage expansion and CI/CD simplification. Four new test files add ~700 lines of coverage across the codec client API, ADU wire encoding, and protocol error paths. CI and release workflows are replaced with org-level reusable workflows from otfabric/.github. The go-serial dependency is bumped to v0.1.2. No API or behavioural changes.
Changes
Tests
- codec/client_test.go — New. Covers
ReadFromClient,WriteToClient,ReadRuntimeFromClient,WriteRuntimeToClient,DecodeWithDescriptor,EncodeWithDescriptor, and their error paths (nil codec, read/write failures, zero descriptors, encode errors). - coverage_extra_test.go — New. Additional edge-case coverage across the codebase.
- internal/adu/wire_test.go — New. ADU wire encoding round-trip and boundary tests.
- internal/protocol/errors_test.go — New. Protocol error type and sentinel tests.
CI/CD
- ci.yml — Replaced inline multi-version matrix workflow with the shared reusable workflow
otfabric/.github/.github/workflows/go-ci.yml@v1. - release.yml — Replaced inline release workflow with the shared reusable workflow
otfabric/.github/.github/workflows/go-release.yml@v1. Supportsupdate-major-tagandrelease-name-prefix.
Dependencies
- go-serial — Bumped from v0.1.1 to v0.1.2.
Other
- README.md — Codecov badge URL updated to use token-authenticated endpoint.
- .gitignore — Added
*.outpattern for coverage output files.
Unchanged
- No API, behaviour, or functional changes. All types, functions, and constants are identical to v1.0.1.
v1.0.1
Release v1.0.1
Date: 2026-03-16
Previous release: v1.0.0
Summary
Module rename: The Go module path has been changed from github.com/otfabric/modbus to github.com/otfabric/go-modbus. This aligns the repository name with the Go convention of prefixing Go-specific libraries with go-. No functional changes.
Changes
Changed
- Module path —
go.modmodule declaration changed fromgithub.com/otfabric/modbustogithub.com/otfabric/go-modbus. All internal import paths updated accordingly. - README.md — Title, badge URLs, install command, TOC anchor, and all import examples updated to
go-modbus. - API.md / ARCHITECTURE.md / CODECS.md — All import path references updated.
- CI — Release workflow name updated to
otfabric/go-modbus.
Migration
Update your go.mod and all import paths:
github.com/otfabric/modbus → github.com/otfabric/go-modbus
github.com/otfabric/modbus/codec → github.com/otfabric/go-modbus/codec
github.com/otfabric/modbus/sunspec → github.com/otfabric/go-modbus/sunspec
The Go package name remains modbus (e.g. modbus.New(...), modbus.Config{...}). Only the module and import paths change.
Unchanged
- No API, behaviour, or functional changes. All types, functions, and constants are identical to v1.0.0.
v1.0.0
Release v1.0.0
Date: 2026-03-16
Previous release: v0.4.2
Summary
Major release. The library reaches API stability with a comprehensive architectural refactor, full Modbus protocol compliance, and a clean package structure. The monolithic client and server have been split into focused files; internal subsystems (adu, transport, session, protocol, logging) are properly encapsulated in internal/ packages; the codec and sunspec subpackages are standalone importable modules. All public type names have been streamlined (Config, Client, Server, ServerConfig) and legacy helpers removed. Protocol compliance now covers all standard Modbus function codes including FC07, FC0B, FC0C client and server support, tightened FC43/14 validation, and comprehensive FC08 diagnostic wrappers.
Breaking changes
Renamed types
| Old name | New name | Notes |
|---|---|---|
ClientConfiguration |
Config |
Flat config struct |
ModbusClient |
Client |
|
NewClient(*ClientConfiguration) |
New(Config) |
Now takes value, not pointer |
ServerConfiguration |
ServerConfig |
|
ModbusServer |
Server |
|
NewServer(*ServerConfiguration, ...) |
NewServer(*ServerConfig, ...) |
|
ReportServerIdResponse |
ReportServerIDResponse |
Consistent Go naming |
Moved to subpackages
| Old location (root) | New location | Import path |
|---|---|---|
ReadWithCodec, WriteWithCodec, all codec types |
codec/ |
github.com/otfabric/modbus/codec |
DetectSunSpec, ReadSunSpecModelHeaders, DiscoverSunSpec |
sunspec/ |
github.com/otfabric/modbus/sunspec |
Root-package convenience methods on Client still exist for SunSpec, delegating to the subpackage.
Removed
- SetEncoding — Removed. Byte and word order are defined per-codec via
RegisterLayout. - ReadBytes / WriteBytes — Use
ReadRegisterBytes/WriteRegisterBytesfor raw byte transport; use codecs for typed interpretation. - All legacy typed read/write helpers —
ReadUint16(s),ReadUint32(s),ReadFloat32(s),WriteInt16(s),WriteAscii,WriteBCD, etc. UseReadRegisters/WriteRegistersfor raw access; usecodec.ReadFromClient/codec.WriteToClientfor typed access. - client_runtime.go / client_codec.go — Runtime and codec client helpers moved into
codec/client.go.
Changed
- Logger default —
Config.Loggerdefaults to a no-op logger (logging disabled) instead ofslog.Default(). UseNewStdLogger,NewSlogLogger, orNewFieldLoggerto enable logging. - Config.DialTimeout — New field. Zero uses sensible defaults (5s TCP/UDP, 15s TLS). Does not apply to serial RTU.
- NewConfig() — New constructor that accepts
TransportConfig,ExecutionConfig, andObservabilityConfigfor structured configuration as an alternative to the flatConfigliteral. - Error types —
ExceptionError,ProtocolError,ParameterError, andConfigurationErrorare now type aliases re-exported frominternal/protocol. Behaviour is identical;errors.Isanderrors.Aswork as before.
Added
Modbus protocol compliance (FC07, FC0B, FC0C)
- ReadExceptionStatus (FC07) — Client method returning the 8-bit exception status coil output. Server-side
ExceptionStatusHandleroptional interface. - GetCommEventCounter (FC0B) — Client method returning
*CommEventCounterResponse(Status + EventCount). Server-sideCommEventCounterHandleroptional interface. - GetCommEventLog (FC0C) — Client method returning
*CommEventLogResponse(Status + EventCount + MessageCount + Events). Server-sideCommEventLogHandleroptional interface. - RTU response length —
ExpectedRTUResponseLengthnow handles FC0B (fixed 4-byte) and FC0C (byte-count-prefixed), with exception entries for both.
FC08 Diagnostics — typed convenience wrappers
DiagnosticForceListenOnlyMode— Sub-function 0x0004; no response expected (serial-line semantics).DiagnosticClearCounters— Sub-function 0x000A; validates 2-byte echo.DiagnosticBusCommunicationErrorCount,DiagnosticBusExceptionErrorCount,DiagnosticServerMessageCount,DiagnosticServerNoResponseCount,DiagnosticServerNAKCount,DiagnosticServerBusyCount,DiagnosticBusCharacterOverrunCount— Each returns auint16counter with 2-byte payload validation.DiagnosticClearOverrunCounterAndFlag— Sub-function 0x0014; validates 2-byte echo.
FC43/14 (Read Device Identification) — tightened validation
- Conformity level validated against spec-allowed values (0x01–0x03, 0x81–0x83).
- MoreFollows + NextObjectID consistency check (NextObjectID must be 0x00 when MoreFollows is 0x00).
- Individual access enforces MoreFollows = 0x00 and exactly one object.
SupportsStreamAccess()andSupportsIndividualAccess()helpers onDeviceIdentification.- MEI type 13 (CANopen General Reference) explicitly documented as unsupported.
Server optional handlers (FC07, FC0B, FC0C)
ExceptionStatusHandler,CommEventCounterHandler,CommEventLogHandler— Follow the existing optional-handler pattern (MaskWriteHandler,ReadWriteHandler). If not implemented, server returnsIllegal Function.
Architecture refactor
- Client split —
client.go(config, lifecycle),client_exec.go(request execution),client_bits.go(coil/discrete ops),client_registers.go(register ops),client_device_id.go(FC43),client_diagnostics.go(FC07/FC08/FC0B/FC0C),client_file.go(FC20/FC21),client_probe.go(device detection). - Server split —
server.go(config, interfaces),server_transport.go(dispatch),server_bits.go(coil/DI handling),server_registers.go(register handling). - Internal packages —
internal/adu(MBAP, RTU CRC, wire encoding),internal/transport(TCP, RTU),internal/session(engine, pool, retry),internal/protocol(constants, FC, errors, detection),internal/logging(prefixed adapter). - Codec subpackage —
codec/is a standalone public package withcodec.ReadFromClient/codec.WriteToClient,codec.RegisterLayout, all codec constructors, runtime codecs, and discovery. - SunSpec subpackage —
sunspec/is a standalone public package withsunspec.Detect,sunspec.ReadModelHeaders,sunspec.Discover. - CLI restructured —
cmd/modbus-cli/split intomain.go,parser.go,execute.go,scan.go,help.go. - ARCHITECTURE.md — New document describing the package layout, dependency graph, ownership rules, locking model, and request/response flow.
Transport-neutral policy
All function codes are supported on every transport (TCP, TLS, UDP, RTU, RTU-over-TCP/UDP). The spec labels FC07, FC08, FC0B, FC0C as "Serial Line only," but gateways make them reachable over any lower layer. The library does not restrict any FC by transport type.
Protocol validation
protocol_validate.go— Centralised request validation helpers used by client methods.protocol_limits_test.go— Explicit tests for global Modbus PDU/ADU size limits (MBAP max 254, RTU max 256, PDU max 253, TCP ADU max 260).
Tests
client_diagnostics_test.go— FC07, FC0B, FC0C client tests (happy path, exceptions, malformed payloads).server_serial_fc_test.go— Server dispatch tests for FC07/FC0B/FC0C with and without optional handlers.fc43_test.go— Expanded with conformity level validation, MoreFollows/NextObjectID, individual access constraints, bad object ID behaviour (stream restart from 0, individual exception 02),SupportsStreamAccess/SupportsIndividualAccesshelpers.protocol_limits_test.go— MBAP bounds, RTU frame max, PDU max, TCP ADU max, protocol ID validation, quantity constants.coverage_extra_test.go— Additional coverage for edge cases across the codebase.
Documentation
- README.md — Updated FC tables (client and server), transport-neutral policy note, MEI 43/13 note, API tiers table, five-tier architecture description.
- API.md — New sections for FC07/FC0B/FC0C, FC08 convenience wrappers, conformity level helpers, FC43 validation rules, MEI 43/13, server optional handler interfaces (FC07/FC0B/FC0C). Updated table of contents.
- ARCHITECTURE.md — New file documenting package layout, dependency graph, ownership rules, client locking model, and codec/SunSpec architecture.
- CODECS.md — Minor alignment with codec subpackage move.
v0.4.2
Release v0.4.2
Date: 2026-03-17
Previous release: v0.4.1
Summary
Patch release: time codec family for typed time.Time read/write. Three formats are supported — seconds since 2000 (s2000), calendar YMDhms (6 registers), and IEC 60870-5 CP56Time2a (4 registers) — with UTC, local, and default-UTC variants. All time codecs use CodecFamilyTime / CodecValueTime, strict calendar validation, and consistent CodecValueError on invalid values (including CP56 decode failures). Descriptor registration and runtime registry support discovery and CLI use.
Changes
Added
- Time codecs —
NewDateTime2S2000Codec()(2 regs, uint32 seconds since 2000-01-01 UTC),NewDateTime3S2000Codec()(3 regs, 48-bit seconds),NewDateTimeYMDhmsUTCCodec(),NewDateTimeYMDhmsLocalCodec(),NewDateTimeYMDhmsCodec()(6 regs: year, month, day, hour, minute, second),NewDateTimeIEC870UTCCodec(),NewDateTimeIEC870LocalCodec(),NewDateTimeIEC870Codec()(4 regs, CP56Time2a, 7-byte payload + pad). Stable IDs:datetime2_s2000,datetime3_s2000,datetime_ymdhms_utc,datetime_ymdhms_local,datetime_ymdhms,datetime_iec870_utc,datetime_iec870_local,datetime_iec870. - CodecFamilyTime / CodecValueTime — New family and value kind for time codecs; used by descriptors and
FindRuntimeCodecs/ discovery. - Layout metadata for s2000 —
datetime2_s2000anddatetime3_s2000descriptors expose layout 4321 and 654321 for tooling; YMDhms and IEC870 are structural formats and do not expose layout metadata. - Strict calendar validation — Helper
strictDateTimerejects invalid dates (e.g. Feb 31, April 31, non-leap Feb 29); YMDhms and CP56 decode paths use it. Coarse range checks (month 1–12, day 1–31, etc.) return targeted errors; normalisation failures yield a single “invalid calendar date/time” error. - CP56 behaviour — Decode ignores status/flag bits and uses only timestamp fields; encode writes a clean timestamp (flag bits unset). Eighth byte of the 4-register frame is padding and ignored on decode. Millisecond precision (milliseconds within minute 0–59999) is preserved. Year 2000–2127; decode maps year byte 0→2000, 127→2127.
- Nil-location guards —
strictDateTime,decodeCP56Time2a,encodeCP56Time2a, and the YMDhms/IEC870 codecEncodeRegistersmethods reject nil*time.Locationwith a clear error instead of panicking. - CP56 decode errors as CodecValueError —
dateTimeIEC870Codec.DecodeRegisterswraps CP56 decode failures in*CodecValueErrorsoerrors.Is(err, ErrCodecValue)works for value-validation tests and callers.
Documentation
- CODECS.md — §5 Time codecs: s2000, YMDhms, CP56Time2a; UTC/default/local semantics; DOW written on encode, ignored on decode.
- README.md — Supported Go types table includes
time.Timevia time codecs; codec list mentions time codecs. - API.md — Time codec constructors and stable IDs already documented; aligned with CODECS.md.
Unchanged
- No breaking changes. Serial transport, TCP/TLS, codec API, and all other behaviour unchanged.