Name the option a refused address came from, and the data a 0.5.0 consumer stored
Mirror / push (push) Successful in 4s
Test / lint (pull_request) Successful in 20s
Test / test (18) (pull_request) Successful in 35s
Test / test (20) (pull_request) Successful in 30s
Test / test (22) (pull_request) Successful in 29s
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:14:08 +02:00
parent f870b7530f
commit dc690b912f
6 changed files with 56 additions and 20 deletions
+13 -7
View File
@@ -480,19 +480,25 @@ rule and an index of the titles below.
- **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, 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
[#16](https://gitea.larvit.se/larvit/smpp-js/pulls/16). 3.4 calls these fields ASCII, so goal 3
settles the read alone — its generous clause is scoped to reading, and its sender clause is strict.
Goal 1 settles the write, being 3.4 as SMSCs actually run it: an operator routing an alphanumeric
sender through the upper half is traffic to keep, and Node's `ascii` write already emitted the low
octet, so `Kaffeé` went out as `4B 61 66 66 65 E9` before this change and goes out as the same
octets after it. Naming the write latin1 is what makes the round trip idempotent, and 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
field behind it under a `command_length` that counted the whole string. Goal 4 settles them twice
over, since for `一`, ` ` and most emoji that low octet is `0x00` and the field went out malformed
on the operator's parser. `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:
to stay strict to 3.4's ASCII, which would be a new restriction taking away traffic this library
already sends and operators already accept, on no defect; whether an SMSC wants a non-ASCII sender
stays its own call, answered as `ESME_RINVSRCADR`. 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.