Refuse a PDU whose optional parameters do not end on command_length (#87)

* Regression tests for a truncated TLV tail refused rather than accepted

* Refuse a PDU whose optional parameters do not end on command_length

* Assert the bare TLV header refusal against jsmpp instead of recording it as a defect

* Note the truncated TLV tail defect as fixed in the java-client findings

* Derive the padding position, share the bare TLV fixture and trim the decision record

* Regression tests for a PDU whose trailing C-Octet String a peer left out

* An absent trailing C-Octet String consumes no octet, so a bodyless PDU still parses

* Bound the TLV loop by the buffer it was given rather than a second spelling of its length

* Answer the stability review's questions in the record and pin the array contract
This commit is contained in:
2026-09-06 18:01:35 +02:00
committed by GitHub
parent c89005168d
commit 039951e69b
11 changed files with 215 additions and 85 deletions
+10 -4
View File
@@ -136,19 +136,25 @@ plain Python socket, no Java involved):
```
This is a `deliver_sm` (`source_addr` `raw-from`, `destination_addr` `raw-to`, body "truncated tlv
probe silent") followed by `00 1d 00 c8` - tag `0x001D`, declared length 200, zero value octets.
probe") followed by `00 1d 00 c8` - tag `0x001D`, declared length 200, zero value octets.
Our server answers `command_status 0x00000000` (`ESME_ROK`) and delivers the text as `sms`.
Appending 4 more arbitrary octets to the same tail (8 total, still declaring length 200) correctly
triggers `ESME_RINVTLVSTREAM` instead - `jsmpp.test.ts`'s "a deliver_sm with a truncated TLV stream"
test uses that 8-octet form deliberately, to test the *documented* refusal path rather than this
adjacent bug. Reproduced with jsmpp's own driver too:
`jsmpp.test.ts`, "defect: a 4-octet truncated TLV tail is silently accepted rather than refused" -
same bytes, over a `net.Socket` opened directly against `server()` (not through jsmpp's typed API,
which cannot build this shape at all). Severity: low - a narrow boundary condition (exactly 4
`jsmpp.test.ts`, "a deliver_sm ending in a bare TLV header gets ESME_RINVTLVSTREAM, and reaches no
listener" - the same shape, over a `net.Socket` opened directly against `server()` (not through
jsmpp's typed API, which cannot build it at all). Severity: low - a narrow boundary condition (exactly 4
trailing octets, no value) rather than a general TLV-validation gap, but it is a hole in the fix
target 1 otherwise closed, silently dropping a TLV the peer meant to send instead of losing (and
counting) the one malformed PDU.
Fixed in [#87](https://github.com/larvit/larvitsmpp/pull/87): the optional parameters now have to end
on `command_length`, so this PDU is refused `ESME_RINVTLVSTREAM` like the sibling case. The hex above
is asserted octet for octet by `test/pdu.test.ts`, "refuses a bare TLV header the same way it refuses
a truncated value"; the same shape over a socket is `jsmpp.test.ts`, "a deliver_sm ending in a bare
TLV header gets ESME_RINVTLVSTREAM, and reaches no listener".
## Peer quirks
- **jsmpp's `session.getInterfaceVersion()` echoes what the driver declared, not what the earlier