Fix Crc16.update(ByteBuffer) reading past limit for positioned buffers - #159
Conversation
update(ByteBuffer) indexed the buffer at offset + i while i already started at the buffer's position, so any buffer with a non-zero position was read at doubled indices and threw IndexOutOfBoundsException once offset + i reached the limit. Read each absolute index i in [position, limit) instead, matching the documented "remaining bytes" contract. Fixes digitalpetri#157
📝 WalkthroughSummary by CodeRabbit
Walkthrough
ChangesCRC16 ByteBuffer handling
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@modbus/src/test/java/com/digitalpetri/modbus/Crc16Test.java`:
- Around line 20-30: Extend crc16WithBufferPosition to cover a ByteBuffer with
limit below capacity, an empty range where position equals limit, and
preservation of the original buffer.position() after crc.update(buffer). Assert
the expected CRC for the bounded remaining bytes, zero for the empty range, and
the unchanged position for each relevant case.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: eeaf114f-be2a-42e6-92ac-848901f7c3a8
📒 Files selected for processing (2)
modbus/src/main/java/com/digitalpetri/modbus/Crc16.javamodbus/src/test/java/com/digitalpetri/modbus/Crc16Test.java
| @Test | ||
| void crc16WithBufferPosition() { | ||
| // A buffer whose position is advanced past leading bytes must be CRC'd | ||
| // over only the remaining bytes [position, limit). Prefixing the known | ||
| // vector with two bytes and skipping them must yield the same CRC. See #157. | ||
| Crc16 crc = new Crc16(); | ||
| ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x12, 0x34, 0x56, 0x78, 0x09}); | ||
| buffer.position(2); | ||
| crc.update(buffer); | ||
|
|
||
| assertEquals(0x2590, crc.getValue()); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add the remaining ByteBuffer boundary tests.
The current test uses a buffer whose limit equals its capacity. It does not cover a limit below capacity, an empty [position, limit) range, or preservation of buffer.position().
Add these assertions and cases to match the PR objectives:
Proposed test additions
crc.update(buffer);
+ assertEquals(2, buffer.position());
assertEquals(0x2590, crc.getValue());
}
+ `@Test`
+ void crc16WithLimitBelowCapacity() {
+ Crc16 crc = new Crc16();
+ ByteBuffer buffer =
+ ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x12, 0x34, 0x56, 0x78, 0x09, 0x7F});
+ buffer.position(2);
+ buffer.limit(7);
+
+ crc.update(buffer);
+
+ assertEquals(0x2590, crc.getValue());
+ assertEquals(2, buffer.position());
+ }
+
+ `@Test`
+ void crc16WithEmptyRemainingRange() {
+ Crc16 crc = new Crc16();
+ ByteBuffer buffer = ByteBuffer.allocate(8);
+ buffer.position(4);
+ buffer.limit(4);
+
+ crc.update(buffer);
+
+ assertEquals(0xFFFF, crc.getValue());
+ assertEquals(4, buffer.position());
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Test | |
| void crc16WithBufferPosition() { | |
| // A buffer whose position is advanced past leading bytes must be CRC'd | |
| // over only the remaining bytes [position, limit). Prefixing the known | |
| // vector with two bytes and skipping them must yield the same CRC. See #157. | |
| Crc16 crc = new Crc16(); | |
| ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x12, 0x34, 0x56, 0x78, 0x09}); | |
| buffer.position(2); | |
| crc.update(buffer); | |
| assertEquals(0x2590, crc.getValue()); | |
| `@Test` | |
| void crc16WithBufferPosition() { | |
| // A buffer whose position is advanced past leading bytes must be CRC'd | |
| // over only the remaining bytes [position, limit). Prefixing the known | |
| // vector with two bytes and skipping them must yield the same CRC. See `#157`. | |
| Crc16 crc = new Crc16(); | |
| ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x12, 0x34, 0x56, 0x78, 0x09}); | |
| buffer.position(2); | |
| crc.update(buffer); | |
| assertEquals(2, buffer.position()); | |
| assertEquals(0x2590, crc.getValue()); | |
| } | |
| `@Test` | |
| void crc16WithLimitBelowCapacity() { | |
| Crc16 crc = new Crc16(); | |
| ByteBuffer buffer = | |
| ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x12, 0x34, 0x56, 0x78, 0x09, 0x7F}); | |
| buffer.position(2); | |
| buffer.limit(7); | |
| crc.update(buffer); | |
| assertEquals(0x2590, crc.getValue()); | |
| assertEquals(2, buffer.position()); | |
| } | |
| `@Test` | |
| void crc16WithEmptyRemainingRange() { | |
| Crc16 crc = new Crc16(); | |
| ByteBuffer buffer = ByteBuffer.allocate(8); | |
| buffer.position(4); | |
| buffer.limit(4); | |
| crc.update(buffer); | |
| assertEquals(0xFFFF, crc.getValue()); | |
| assertEquals(4, buffer.position()); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@modbus/src/test/java/com/digitalpetri/modbus/Crc16Test.java` around lines 20
- 30, Extend crc16WithBufferPosition to cover a ByteBuffer with limit below
capacity, an empty range where position equals limit, and preservation of the
original buffer.position() after crc.update(buffer). Assert the expected CRC for
the bounded remaining bytes, zero for the empty range, and the unchanged
position for each relevant case.
Summary
Fixes #157.
Crc16.update(ByteBuffer)throwsIndexOutOfBoundsExceptionfor any buffer whose position is non-zero.Root cause
ialready starts atoffset(the position), sobuffer.get(offset + i)reads the buffer at doubled indices. Whenoffset > 0,offset + iruns off the end andByteBuffer.get(int)throws once the index reacheslimit(). The bug is masked only whenposition() == 0(offset + i == i), which is why the existing callers that pass position-0 buffers were unaffected.Fix
Index each absolute position
iin[position, limit)exactly once:This matches the method's documented contract of CRC-ing the buffer's remaining bytes and does not advance the buffer position (absolute
get).Test
Added
Crc16Test.crc16WithBufferPosition, which prefixes the existing known CRC vector with two bytes and advancespositionpast them; the CRC over the remaining bytes must equal the same known value (0x2590).IndexOutOfBoundsExceptionatCrc16.java.Verification
mvn -pl modbus test— 79 tests, 0 failures/errors (the RTU frame accumulator paths that CRC positioned buffers are covered).mvn -pl modbus checkstyle:check— 0 violations.mvn -pl modbus spotless:check— clean.Disclosure: this fix was prepared with AI assistance (Claude). I have reviewed it and verified the reasoning, the red-green test, and the full module build myself.