diff --git a/RELEASE.md b/RELEASE.md index cace47a..e00a96b 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -21,6 +21,28 @@ manual workflow and no releases (`own/go-modbus/README.md`). --- +## v0.4.1 + +**Date:** 2026-11-09 + +**Previous release:** v0.4.0 + +## Summary + +j2mod declares `serverFileRecord` false, and the self-tests read two FC20 sub-requests at once. + +## Changes + +### Fixed + +- **`j2mod` declared `serverFileRecord` although j2mod 3.4.0 answers the second of two FC20 sub-requests with the wrong records** (FIXTURES.md rule 10; Modbus Application Protocol V1.1b3, 6.14): `140e0600010000000206000200000002` is answered `140c0506000b0506000100020000`, not `140c0506000b01a4050600010002`. The go-modbus interop suite found it. The server still answers FC20 and FC21 (`serverFunctions`), but the pairings that need `serverFileRecord` now skip the j2mod server. + +### Added + +- **Self-test** `values.fc20.two-sub-requests`: an FC20 with two sub-requests, sent with `raw`, must return the records of both from the fixture. + +--- + ## v0.4.0 **Date:** 2026-11-08 diff --git a/adapters/j2mod/README.md b/adapters/j2mod/README.md index 066dfad..e17d3ea 100644 --- a/adapters/j2mod/README.md +++ b/adapters/j2mod/README.md @@ -152,6 +152,7 @@ both are `connection-lost`. | `diagnostics` with other than two data bytes | `ReadSerialDiagnosticsRequest` has exactly one data word. Usage error, nothing sent. | | file numbers 10000 and above | `SimpleProcessImage.getFileByNumber` refuses them (exception 2). | | `serverIllegalFunction` | Over TCP an unknown function code closes the connection instead of exception 1: `IllegalFunctionRequest.readData` skips the MBAP length rather than the data length and reads past the frame (finding 1). Over UDP the library answers `c101` correctly, but the feature is declared for the adapter. | +| `serverFileRecord` | FC20 with two sub-requests answers the second one with the wrong records: `ReadFileRecordResponse.getMessage` advances the offset wrongly (finding 5; FIXTURES.md rule 10, spec 6.14). FC20 and FC21 are still answered by the library, with the adapter's checks in front. The self-tests send two sub-requests (`values.fc20.two-sub-requests`). | ## Findings about the library (3.4.0) diff --git a/adapters/j2mod/src/main/java/org/otfabric/modbusinterop/j2mod/Main.java b/adapters/j2mod/src/main/java/org/otfabric/modbusinterop/j2mod/Main.java index 90c098e..d37bd9e 100644 --- a/adapters/j2mod/src/main/java/org/otfabric/modbusinterop/j2mod/Main.java +++ b/adapters/j2mod/src/main/java/org/otfabric/modbusinterop/j2mod/Main.java @@ -155,7 +155,10 @@ private static JsonObject capabilities() { // FC08 and FC17 request classes answer exception 1 on the server side. features.addProperty("serverDiagnostics", false); features.addProperty("serverReportServerId", false); - features.addProperty("serverFileRecord", true); + // FC20 with two sub-requests answers the second with the wrong + // records (README, finding 5; FIXTURES.md rule 10). FC20 and FC21 + // are still answered by the library (serverFunctions). + features.addProperty("serverFileRecord", false); features.addProperty("serverFifoQueue", true); features.addProperty("clientDiagnostics", true); features.addProperty("clientReportServerId", true); diff --git a/docs/COMPATIBILITY.md b/docs/COMPATIBILITY.md index 923fd03..a30123a 100644 --- a/docs/COMPATIBILITY.md +++ b/docs/COMPATIBILITY.md @@ -61,7 +61,7 @@ also build and pass on the release before (v3.1.12 and 3.15.0). | `clientUnitIdCheck` | **no** | yes | **no** | yes | yes | **no** | **no** | **no** | yes | | `serverDiagnostics` | **no** | yes | **no** | yes ² | **no** | **no** | **no** | **no** | **no** | | `serverReportServerId` | **no** ³ | yes | **no** | **no** | **no** ⁴ | **no** | **no** | **no** | yes | -| `serverFileRecord` | **no** | yes | **no** | **no** | **no** | yes | **no** | **no** | **no** | +| `serverFileRecord` | **no** | yes | **no** | **no** | **no** | **no** | **no** | **no** | **no** | | `serverFifoQueue` | **no** | yes | **no** | **no** | **no** | yes | **no** | **no** | **no** | | `clientDiagnostics` | **no** | yes | **no** | **no** | **no** | yes | **no** | **no** | **no** | | `clientReportServerId` | yes | yes | **no** | **no** | yes | yes | **no** | **no** | yes | @@ -266,8 +266,10 @@ every response. Details and hex in `adapters/j2mod/README.md`. - FC20 with two sub-requests (spec 6.14): `140e0600010000000206000200000002` is answered `140c0506000b0506000100020000`, expected `140c0506000b01a4050600010002` (`ReadFileRecordResponse.getMessage` - advances the offset wrongly). Not in the self-tests, which read one - sub-request. + advances the offset wrongly). Found by the go-modbus interop suite after + v0.4.0, which declared `serverFileRecord` true; it is false since v0.4.1, + and the self-tests now send two sub-requests + (`values.fc20.two-sub-requests`). - Over UDP, FC24 and FC20 responses are framed by a data length that is never updated: FC24 `1804de` is answered one byte short, the empty queue carries 3 stale bytes, FC20 is answered with MBAP length 0 and no PDU. diff --git a/scripts/interop.sh b/scripts/interop.sh index 075dc72..6bca6fc 100755 --- a/scripts/interop.sh +++ b/scripts/interop.sh @@ -383,6 +383,17 @@ for server in "${ADAPTERS[@]}"; do 2 0 4 1 FILES else skip "${pair}: FC20 values"; fi + # Two sub-requests in one FC20 (FIXTURES.md rule 10): records 0..1 of + # file 1 and of file 2. Only raw sends more than one. + at values.fc20.two-sub-requests + if supported "${client}" "${server}" "server:serverFileRecord,client:clientRaw,client:clientRawAnyResponse"; then + got=$(run_client "${client}" raw --function 20 --data 0e0600010000000206000200000002 --host "${name}" | jq -r '.response.data') + # Byte count 0x0c, then per sub-response: length 5, reference type 6, two registers. + want="0c$(jq -r '[.fileRecords[0].records[0:2], .fileRecords[1].records[0:2]][] | map(tostring) | join(" ")' "${FIXTURE}" \ + | while read -r r0 r1; do printf '0506%04x%04x' "${r0}" "${r1}"; done)" + if [[ "${got}" == "${want}" ]]; then ok "${pair}: FC20 with two sub-requests returns the records of both" + else fail "${pair}: FC20 with two sub-requests returns ${got}, want ${want}"; fi + else skip "${pair}: FC20 with two sub-requests (needs server:serverFileRecord,client:clientRaw,client:clientRawAnyResponse)"; fi at values.fc24 if supported "${client}" "${server}" "server:serverFifoQueue,client:clientFifoQueue"; then for index in 0 1 2; do diff --git a/versions.yaml b/versions.yaml index e1ef8ee..0c70d8f 100644 --- a/versions.yaml +++ b/versions.yaml @@ -3,7 +3,7 @@ # The Dockerfiles are the source of truth for a build: # scripts/check-versions.sh fails when this file disagrees with them. -interop_version: "0.4.0" +interop_version: "0.4.1" contract_version: "1.2" # docs/CONTAINER_CONTRACT.md, schemaVersion of every document fixture_schema: "1.1" # fixtures/schema/modbus-fixture.schema.json