Give the esm_class bits one owner beside the constants

This commit is contained in:
2026-08-27 16:18:05 +02:00
parent b9f77ec969
commit c9c3121d40
7 changed files with 21 additions and 21 deletions
+2
View File
@@ -206,6 +206,8 @@ exactly 140.
keeps the scrape: SMSCs that send text-only receipts leave `esm_class` at 0, and reading that as keeps the scrape: SMSCs that send text-only receipts leave `esm_class` at 0, and reading that as
the spec's "default message type" would lose every one of them. A `receipted_message_id` TLV marks the spec's "default message type" would lose every one of them. A `receipted_message_id` TLV marks
a receipt on the same footing where the message type is 0, since nothing but a receipt carries one. a receipt on the same footing where the message type is 0, since nothing but a receipt carries one.
What gets scraped is the decoded `short_message` with any UDH stripped, so a concatenated receipt
is read like any other.
The `message_state` TLV is authoritative only where it names a state in the table — SMPP reserves The `message_state` TLV is authoritative only where it names a state in the table — SMPP reserves
0x80-0xFF for MC-vendor-specific values, so an unnameable one keeps its raw `statusId` and leaves 0x80-0xFF for MC-vendor-specific values, so an unnameable one keeps its raw `statusId` and leaves
`statusMsg` to the body. `statusMsg` to the body.
+9
View File
@@ -103,6 +103,15 @@ export const consts = {
}, },
} as const; } as const;
/** esm_class bits 5-2 name the message type; bit 6 is the UDH indicator. */
export function hasUdh(esmClass: number): boolean {
return (esmClass & consts.ESM_CLASS.UDH_INDICATOR) === consts.ESM_CLASS.UDH_INDICATOR;
}
export function messageTypeOf(esmClass: number): number {
return esmClass & 0x3c;
}
export type ConstGroup = keyof typeof consts; export type ConstGroup = keyof typeof consts;
export type MessageState = keyof typeof consts.MESSAGE_STATE; export type MessageState = keyof typeof consts.MESSAGE_STATE;
+2 -5
View File
@@ -1,7 +1,7 @@
import type { MessageState } from './defs/constants.ts'; import type { MessageState } from './defs/constants.ts';
import type { ParamValue } from './defs/types.ts'; import type { ParamValue } from './defs/types.ts';
import type { PduObject } from './pdu.ts'; import type { PduObject } from './pdu.ts';
import { consts, constsById } from './defs/constants.ts'; import { consts, constsById, messageTypeOf } from './defs/constants.ts';
import { decodeMessage } from './message.ts'; import { decodeMessage } from './message.ts';
import { paramNumber, paramText } from './defs/types.ts'; import { paramNumber, paramText } from './defs/types.ts';
@@ -123,13 +123,10 @@ export function parseReceipt(message: string): Receipt {
}; };
} }
/** esm_class bits 5-2 name the message type; the rest are the messaging mode and the GSM features. */
const messageTypeBits = 0x3c;
type MessageType = 'other' | 'receipt' | 'unmarked'; type MessageType = 'other' | 'receipt' | 'unmarked';
function messageType(pduObj: PduObject): MessageType { function messageType(pduObj: PduObject): MessageType {
const type = paramNumber(pduObj.params.esm_class, 0) & messageTypeBits; const type = messageTypeOf(paramNumber(pduObj.params.esm_class, 0));
if (type === consts.ESM_CLASS.MC_DELIVERY_RECEIPT) return 'receipt'; if (type === consts.ESM_CLASS.MC_DELIVERY_RECEIPT) return 'receipt';
if (type !== 0) return 'other'; if (type !== 0) return 'other';
+4 -6
View File
@@ -6,10 +6,10 @@ import type { SmppLog } from './log.ts';
import { Reassembler, decodeSegments } from './reassembly.ts'; import { Reassembler, decodeSegments } from './reassembly.ts';
import { bindCommands, defaults } from './session-options.ts'; import { bindCommands, defaults } from './session-options.ts';
import { concatInfo } from './udh.ts'; import { concatInfo } from './udh.ts';
import { consts } from './defs/constants.ts'; import { hasUdh } from './defs/constants.ts';
import { createSms } from './sms.ts'; import { createSms } from './sms.ts';
import { dlrFromPdu } from './dlr.ts'; import { dlrFromPdu } from './dlr.ts';
import { paramText } from './defs/types.ts'; import { paramNumber, paramText } from './defs/types.ts';
export type IncomingRequestsOptions = { export type IncomingRequestsOptions = {
dlrMerger: DlrMerger; dlrMerger: DlrMerger;
@@ -115,10 +115,8 @@ export class IncomingRequests {
private onMessage(pduObj: PduObject): void { private onMessage(pduObj: PduObject): void {
const message = pduObj.params.short_message; const message = pduObj.params.short_message;
const esmClass = pduObj.params.esm_class; const carriesUdh = hasUdh(paramNumber(pduObj.params.esm_class, 0));
const hasUdh = typeof esmClass === 'number' const concat = carriesUdh && Buffer.isBuffer(message) ? concatInfo(message) : undefined;
&& (esmClass & consts.ESM_CLASS.UDH_INDICATOR) === consts.ESM_CLASS.UDH_INDICATOR;
const concat = hasUdh && Buffer.isBuffer(message) ? concatInfo(message) : undefined;
if (!concat) { if (!concat) {
this.emitSms([pduObj]); this.emitSms([pduObj]);
+2 -2
View File
@@ -1,6 +1,6 @@
import type { Result } from './result.ts'; import type { Result } from './result.ts';
import type { EncodingName } from './defs/encodings.ts'; import type { EncodingName } from './defs/encodings.ts';
import { consts } from './defs/constants.ts'; import { hasUdh } from './defs/constants.ts';
import { detect, encodingByDataCoding, encodings } from './defs/encodings.ts'; import { detect, encodingByDataCoding, encodings } from './defs/encodings.ts';
/** A single SMS carries 1120 bits, whatever the alphabet. */ /** A single SMS carries 1120 bits, whatever the alphabet. */
@@ -33,7 +33,7 @@ export function decodeMessage(
): { message: string; udh: Buffer | undefined } { ): { message: string; udh: Buffer | undefined } {
const encoding = encodingByDataCoding(dataCoding); const encoding = encodingByDataCoding(dataCoding);
if ((esmClass & consts.ESM_CLASS.UDH_INDICATOR) !== consts.ESM_CLASS.UDH_INDICATOR) { if (!hasUdh(esmClass)) {
return { message: encodings[encoding].decode(buffer), udh: undefined }; return { message: encodings[encoding].decode(buffer), udh: undefined };
} }
+2 -2
View File
@@ -4,7 +4,7 @@ import type { ParamValue } from './defs/types.ts';
import type { Result, VoidResult } from './result.ts'; import type { Result, VoidResult } from './result.ts';
import type { Tlv } from './defs/tlvs.ts'; import type { Tlv } from './defs/tlvs.ts';
import { cmds, commandNameById, isCommandName } from './defs/commands.ts'; import { cmds, commandNameById, isCommandName } from './defs/commands.ts';
import { consts } from './defs/constants.ts'; import { consts, hasUdh } from './defs/constants.ts';
import { decodeMessage, encodeMessage } from './message.ts'; import { decodeMessage, encodeMessage } from './message.ts';
import { detect, encodingByDataCoding } from './defs/encodings.ts'; import { detect, encodingByDataCoding } from './defs/encodings.ts';
import { errorNameById, errors, isErrorName } from './defs/errors.ts'; import { errorNameById, errors, isErrorName } from './defs/errors.ts';
@@ -327,7 +327,7 @@ function parseOnce(pdu: Buffer, trailingNull: boolean): Result<{ aligned: boolea
const esmClass = paramNumber(params.esm_class, 0); const esmClass = paramNumber(params.esm_class, 0);
// A message carrying a UDH stays a buffer; the session needs the header intact to reassemble. // A message carrying a UDH stays a buffer; the session needs the header intact to reassemble.
if (Buffer.isBuffer(message) && (esmClass & consts.ESM_CLASS.UDH_INDICATOR) !== consts.ESM_CLASS.UDH_INDICATOR) { if (Buffer.isBuffer(message) && !hasUdh(esmClass)) {
params.short_message = decodeMessage(message, paramNumber(params.data_coding, 0)).message; params.short_message = decodeMessage(message, paramNumber(params.data_coding, 0)).message;
} }
-6
View File
@@ -147,12 +147,6 @@ session message is a change to every call site.
`reassembly`, `dlr-merger`, `send-window`, `link-timers`, `reconnect-loop`, `pending-requests` `reassembly`, `dlr-merger`, `send-window`, `link-timers`, `reconnect-loop`, `pending-requests`
and `send-sms`, so the directory would make that boundary visible. Do it on the next and `send-sms`, so the directory would make that boundary visible. Do it on the next
extraction out of `session.ts`, not as a move of its own. extraction out of `session.ts`, not as a move of its own.
- [ ] **Nothing owns the `esm_class` bits.** Three modules read them with their own literals:
`pdu.ts` decides decode-or-not, `incoming-requests.ts` reassemble-or-not and `dlr.ts`
receipt-or-not. That divergence is what let a UDH-carrying receipt reach `dlrFromPdu()` as an
undecoded buffer. Two predicates next to the constants would collapse it without touching
`index.ts`.
- [ ] **`submit_multi` and the broadcast commands** encode and decode, but nothing exercises them - [ ] **`submit_multi` and the broadcast commands** encode and decode, but nothing exercises them
end to end. The interop suite is the natural place. end to end. The interop suite is the natural place.
- [ ] **Move to TypeScript 7** once `typescript-eslint` supports it; `renovate.json` pins TypeScript - [ ] **Move to TypeScript 7** once `typescript-eslint` supports it; `renovate.json` pins TypeScript