Repository navigation
modbusctl 3.1.0: record and replay every spec read function; capture commands replace mcap - #2
Merged
Merged
Conversation
go-modbus v1.7.0 dispatches every function code and hands the ones a handler does not decode to an optional RawRequestHandler. The capture handler implements it: FC07, FC08, FC0B, FC0C, FC11, FC12, FC17, FC20, FC24, other FC43 MEI types and vendor functions are answered with the response recorded for exactly that request PDU, byte for byte. The typed FC07/0B/0C handlers go, as the raw path covers them. FC21, FC22 and FC23 writes are refused with Illegal Function. E2E tests record FC08, FC17, FC20 and FC24 from a go-modbus server and check the static and replay servers answer them byte for byte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Per the owner, vendor and unknown function codes are not supported. Drop the RawRequestHandler and serve FC08 (data-returning sub-functions 0x00, 0x02, 0x0B-0x12), FC17, FC20 and FC24 through go-modbus v1.7.0's DiagnosticsHandler, ReportServerIDHandler, ReadFileRecordHandler and FIFOQueueHandler, with the data taken from the recorded response PDU. FC07/0B/0C/43 keep their typed handlers. go-modbus answers every other function code with Illegal Function. capfile.IsSpecRead defines the supported reads. Load rejects a capture holding any other sample (capture_unsupported_function, exit 3), and the recorder refuses to write one. Tests cover refusal on load and in the recorder, Illegal Function for vendor, unknown, FC43/0D, FC08 state-changing and FC21-23 requests on a live server, and vendor --function values in read, scan and record. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Port race (the internal/modbus failure seen once): test helpers picked a port by binding 127.0.0.1:0 and closing it, and the server then bound 0.0.0.0:N. Under load another test's socket took N in between: either EADDRINUSE, or on macOS the wildcard bind succeeded next to another test's 127.0.0.1:N listener, the probe connected to that listener and the test client talked to the wrong device (unexpected EOF). The servers get --listen; they report "Serving" only after the listener is bound; test helpers bind 127.0.0.1 exactly, wait for that report and move to another port only on EADDRINUSE. Recording: the --blocks-file plan (internal/plan) may name any read function of the specification: FC01-04 blocks as before, FC07, FC08 data-returning sub-functions, FC11, FC12, FC17, FC20, FC24 and FC43/14. Anything else is refused before it is sent. client record sends each entry with the retry loop of reads (exchange). Tests: record -> file -> replay round trips for every function from a go-modbus server and mbtest devices over tcp, rtuovertcp and udp, with mbcapctl verify and convert; chaos (busy, drop, close, truncated, late) and parallel variants. mbtest frames every spec read over RTU. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `modbusctl mcap` becomes `modbusctl capture` (decode, extract, strings, frequencies). info, verify and convert are removed: they duplicate mbcapctl of go-modbus-capture, which the docs point to. internal/mcap becomes internal/analysis. - JSON summaries say capture_output_path; error codes capture_load_failed and capture_write_failed. - Docker volume /captures; compose, Makefile and .gitignore name .mbcap. - README: capture files section links go-modbus-capture (FORMAT.md, mbcapctl) instead of repeating it; the plan file format of client record; --listen. RELEASE.md: 3.1.0 with the CLI renames as breaking. - client record examples gave intervals in seconds; they are ms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.0.2's readers and writers accept only the read functions of the specification (capture.CheckPDUs). IsSpecRead now builds on capture.IsReadFunction with the same FC08 and FC43 rules. A load that the library rejects for a sample's function code is still reported as ErrUnsupportedFunction (capture_unsupported_function, exit 3) rather than a corrupt file. The test for it writes the bad samples as JSON Lines by hand, since the writers no longer produce them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
modbusctl 3.1.0: record and replay every read function of the Modbus specification on go-modbus v1.7.1 and go-modbus-capture (MBCAP). Vendor and unknown function codes are not supported anywhere. The
mcapcommand group becomescapture, and MCAP v1 references are removed.Dependencies: go-modbus v1.7.1 (v1.7.1 removes
RawRequestHandler, which this PR does not use) and go-modbus-capture v0.0.2. Itscapture.CheckPDUsaccepts only the spec read functions in every reader and writer;capfile.IsSpecReadbuilds oncapture.IsReadFunctionwith the same FC08/FC43 rules. A file the library rejects for a sample's function code is still reported ascapture_unsupported_function(exit 3), not as a corrupt file.Record and replay
client recordtakes a plan (--blocks-file). Entries withfunctionmay use FC01–04, FC07, FC08 (data-returning sub-functions 0x00, 0x02, 0x0B–0x12), FC11, FC12, FC17, FC20, FC24 or FC43/14. Plain address blocks work as before. Anything else is refused before it is sent (internal/plan). Each request uses the retry loop of reads.server static/server replayserve FC08, FC17, FC20 and FC24 through go-modbus v1.7.1's typed handlers, alongside FC01–04, FC07, FC11, FC12 and FC43. NoRawRequestHandler: go-modbus answers every other function code, vendor and unknown ones included, with Illegal Function.capture_unsupported_function, exit 3), and the recorder never writes one.--listenon both servers (default: every interface). The "🚀 Serving" line is printed once the listener is bound.MCAP v1 removal
modbusctl mcap …→modbusctl capture …(decode, extract, strings, frequencies).info,verifyandconvertare removed because they duplicatembcapctl; the README points to it.internal/mcap→internal/analysis.mcap_output_path→capture_output_path;mcap_load_failed/mcap_write_failed→capture_load_failed/capture_write_failed./captures;docker-compose.yml, Makefile and .gitignore name.mbcap. ALGOS, CONTRIBUTING, SECURITY and docs updated. The README says once that 2.x.mcapfiles are not read.The intermittent internal/modbus failure
Root cause: a port race in the test helpers. They bound
127.0.0.1:0, closed it, and started the server on0.0.0.0:N. Under load another test's socket took N in between. Then either the bind failed (address already in use), or, on macOS, the wildcard bind succeeded next to another test's127.0.0.1:Nlistener: the readiness probe connected to that listener and the test client talked to the wrong device (unexpected EOF).Evidence:
-race -count=50 -cpu 1,2,8on the old code under load gave 4 failures, all this race (twoaddress already in use, twounexpected EOF).Fix:
127.0.0.1exactly (--listen).EADDRINUSEmoves to another port.TestCollServer_portTakenOnLoopbackpins this down.Tests
TestRoundTrip_everyFunction: record → file → replay for every supported function. Sources: a go-modbus v1.7.1 server, and mbtest over tcp, rtuovertcp and udp. Each file passesmbcapctl verify; static and replay servers answer byte for byte.TestRoundTrip_textEncodings:mbcapctl convertto.mbcap.jsonland.mbcap.csvserves the same.TestChaosRoundTrip_everyFunction: busy, dropped, closed, truncated and late answers over tcp and rtuovertcp. Only valid answers the device actually sent are recorded.TestParallelRoundTrip_everyFunction: four devices recorded at once, then 8 clients per recording against static and replay servers.tcp+tlsis not usable from the CLI today).make checkpasses (fmt, staticcheck, golangci-lint 0 issues, vet,-racetests, coverage 96.4%).🤖 Generated with Claude Code