Cover the guarded address writers, and cut what the code already says
Mirror / push (push) Successful in 5s
Test / lint (pull_request) Successful in 22s
Test / test (18) (pull_request) Successful in 29s
Test / test (20) (pull_request) Successful in 30s
Test / test (22) (pull_request) Successful in 37s
Test / test (24) (pull_request) Successful in 29s
Test / test (26) (pull_request) Successful in 30s

This commit is contained in:
2026-09-21 08:08:42 +02:00
parent c1e0407942
commit f870b7530f
8 changed files with 68 additions and 45 deletions
+16 -23
View File
@@ -479,29 +479,22 @@ rule and an index of the titles below.
change reads exactly as it did.
- **Every text field on the wire is latin1, and what the field cannot carry is refused rather than
truncated.** Maintainer's call, 2026-09-21, from the 0.6.0 correctness list: the codec read these
fields with
`toString('ascii')`, which masks bit 7, and wrote them with `write(text, 'ascii')`, which does not,
so an inbound `source_addr` of `Kaffeé` reached the application as `Kaffei` and
`objToPdu(pduToObj(x))` was idempotent for none of `source_addr`, `destination_addr`, `system_id`,
`message_id`, `service_type` or the cstring TLVs. Goal 3 settles the read: 3.4 calls these fields
ASCII, and an operator routing an alphanumeric sender through the upper half is traffic to keep.
The write is named latin1 to match, which is what makes the round trip idempotent and is already
the octets Node put on the wire, so no peer sees a change. `wantText()` is the single place that
says so — which is why the `dest_address` and `unsuccess_sme` structures write their embedded
addresses through `cstring.write()` rather than reaching past it — and a character past `U+00FF`
is refused there rather than truncated to its low octet: a `size()` that agreed with a `write()`
that dropped half a character is a wrong answer about what went out (goal 2), and it is how an
alphabet that cannot carry a message body is already handled. Rejected: reading latin1 and leaving
the write spelled ASCII, which leaves two halves agreeing only by accident. Rejected: refusing the
upper half on send to stay strict to 3.4's ASCII, which would drop exactly the traffic the read
keeps. The security pass over this chunk found the field's other end open the same way: a caller's
own `U+0000` was written verbatim, and since the peer reads a C-Octet String to its first NULL,
every mandatory field behind it shifted under a `command_length` that had counted the whole string
— so an application forwarding a customer's sender id could have a PDU rewritten from inside one.
`wantCstringText()` refuses that where `wantText()` refuses the upper end. Rejected: refusing NULL
in every text field, which would buy one spelling by taking a legitimate octet away from the
length-prefixed Octet String, whose length octet is what ends it.
truncated.** Maintainer's call, 2026-09-21, the refusals from the security and stability passes on
[#16](https://gitea.larvit.se/larvit/smpp-js/pulls/16). Goal 3 settles the alphabet: 3.4 calls
these fields ASCII, and an operator routing an alphanumeric sender through the upper half is
traffic to keep, so the read is latin1 and the write is named latin1 to match — which is what
makes the round trip idempotent, and is already the octets Node put on the wire, so no peer sees a
change. Goal 2 settles the refusals, both of them a `size()` that would have agreed with a
`write()` that put something else on the wire: a character past `U+00FF` written as its low octet,
and a caller's own `U+0000`, which the peer reads as the end of the field, shifting every mandatory
field behind it under a `command_length` that counted the whole string. `wantText()` and
`wantCstringText()` are the only two places that decide it, which is why the `dest_address` and
`unsuccess_sme` structures write their embedded addresses through `cstring.write()` rather than
reaching past it into `writeCstring()`. Rejected: reading latin1 and leaving the write spelled
ASCII, which leaves two halves agreeing only by accident. Rejected: refusing the upper half on send
to stay strict to 3.4's ASCII, which would drop exactly the traffic the read keeps. Rejected:
refusing `U+0000` in every text field, which would buy one spelling by taking a legitimate octet
away from the length-prefixed Octet String, whose length octet is what ends it.
## The session's life