Read the GSM 03.38 message class where the spec puts it (#95)

* Regression tests for the GSM 03.38 message class behind sms.flash

* Read the GSM 03.38 message class where the spec puts it, and refuse a flash message no alphabet can carry

* Keep the code span in the message-class decision on one line

* Stop offering the flash message class as an alphabet, and check the encoding option by name

* Settle every refusable send option in one check, and record what the review left open

* Say what the flash refusal and the encoding option actually do

* Keep the README off the alphabet whose long messages do not fit
This commit is contained in:
2026-09-09 16:40:32 +02:00
committed by GitHub
parent bd466d393a
commit e80b07167c
11 changed files with 422 additions and 31 deletions
+22 -2
View File
@@ -122,6 +122,16 @@ defines the delivery report away, so pair it with `dlr: true` and the send is re
leaving you waiting for a report that cannot come — as is any value naming no mode, both before a
segment goes out.
`encoding` names the alphabet, and is `ASCII` (GSM 03.38's own 7-bit table), `LATIN1` or `UCS2`;
anything else is refused by name rather than guessed at. Leave it out and a message that fits GSM
7-bit goes as `ASCII` and everything else as `UCS2`; `LATIN1` is only ever used when you name it.
`flash` asks for GSM 03.38 message class 0, the class a handset shows on arrival instead of storing.
It travels in `data_coding` beside the alphabet, so a flash UCS2 message stays UCS2. Pairing it with
`encoding: 'LATIN1'` is the one combination with nowhere to go — a message class carries GSM 7-bit,
8-bit data or UCS2, and 8-bit data is not text a handset will display — so the send is refused
before anything goes out.
Messages too long for one SMS are split automatically and sent as a concatenated message. You get
one id per segment:
@@ -157,6 +167,10 @@ session.on('sms', async sms => {
});
```
`sms.flash` is true where the message's `data_coding` carries GSM 03.38 message class 0, in every
coding group that carries one — so `0x10`, `0x18`, `0x50` and `0xF0` alike. The other three classes
name where the handset stores the message rather than that it displays it, so they are not flash.
Delivery receipts travel on the same SMPP command but reach you as `dlr`, so nothing you write has
to tell the two apart. `esm_class` is what tells them apart; where it names no message type a
`receipted_message_id` TLV does, and failing both the message body is read for the standard
@@ -545,7 +559,10 @@ answer to which of the two the peer used, and gives back the octets undecoded:
`decodeMessage(octets, pduObj.params.data_coding, pduObj.params.esm_class)` turns them into text and
hands back the UDH where the PDU carries one. `concatOf(pduObj)` is the same for concatenation: the
`part`, `total` and `reference` a PDU declares, and the `spelling``'udh'` or `'sar'` — that
carried them, or `undefined` where the PDU is a whole message.
carried them, or `undefined` where the PDU is a whole message. `messageClassOf(dataCoding)` is the
same for the GSM 03.38 message class: `0` for the flash class `sms.flash` already reports, `1`, `2`
and `3` for the ME-, SIM- and TE-specific ones, and `undefined` where that `data_coding`'s coding
group carries no class at all.
The spec tables are exported both individually (`cmds`, `consts`, `encodings`, `errors`, `tlvs`,
`types`, and the matching `*ById` maps) and grouped as `defs`.
@@ -599,7 +616,10 @@ have worked around any of these, remove the workaround:
- Every receipt went out as `esm_class` 0x04, which SMPP 3.4 defines as the report of a message's
final state. A receipt for a transient state — `sendDlr('ENROUTE')` — is now marked 0x20, the
intermediate delivery notification.
- `flash: true` discarded UCS2, mangling flash messages containing non-GSM characters.
- `flash: true` discarded UCS2, mangling flash messages containing non-GSM characters, and put the
GSM alphabet on a Latin-1 message that has no `data_coding` at all — that pair is refused now.
Inbound, only a `data_coding` of exactly 0x10 counted as flash, so a flash UCS2 message and the
whole 0xF0 coding group arrived as ordinary messages.
- The multipart reference counter was shared by every session in the process.
- `tls: true` never performed a handshake, so the connection was not actually encrypted.
- Alphanumeric senders were sent with TON 1 (international) instead of TON 5.