PDUs & codec
Typed PDU objects and the pure encode/decode functions.
PDUs are plain objects discriminated by command. The codec is a pair of pure
functions — no I/O, no classes, no hidden state.
Encoding
import { encodePdu } from "@better-smpp/core";
const bytes = encodePdu({
command: "submit_sm",
sequenceNumber: 1,
destinationAddr: "46709771337",
shortMessage: "Hello!",
});- Missing body fields are filled with protocol defaults.
shortMessagetext is encoded perdataCoding— or auto-detected (GSM → Latin-1 → UCS2) with the detecteddataCodingstamped onto the PDU.- A message with a
udhsets the UDHI bit onesmClassautomatically. - A non-zero
commandStatusencodes header-only, per spec — error responses carry no body.
Decoding
import { decodePdu, isPdu } from "@better-smpp/core";
const pdu = decodePdu(frame);
if (isPdu(pdu, "deliver_sm")) {
pdu.sourceAddr; // string
pdu.shortMessage; // { udh?: Buffer[], message: string | Buffer }
pdu.esmClass; // number
}decodePdu returns fully-populated objects with commandStatus, sequenceNumber
and commandLength. The isPdu guard narrows the union to one command's type.
Naming convention: body fields and TLVs are the camelCase form of the SMPP spec
names; command names keep their spec form (submit_sm) as protocol identifiers.
Responses
import { createResponse } from "@better-smpp/core";
const resp = createResponse(pdu, { messageId: "42" });
// { command: "submit_sm_resp", sequenceNumber: <same as request>, messageId: "42" }Unknown-command PDUs get a generic_nack with ESME_RINVCMDID.
Unknown commands and TLVs
Inbound PDUs with unregistered command ids decode to
{ command: "unknown", commandId, body } instead of throwing. Unregistered TLV tags
are collected into an unknownTlvs: Map<number, Buffer> — and re-encode from it, so a
proxy passes vendor extensions through untouched:
const pdu = decodePdu(frame);
if (pdu.command !== "unknown") {
const reencoded = encodePdu(pdu); // unknown TLVs included
}Malformed input
decodePdu throws SmppProtocolError — carrying the appropriate CommandStatus
(ESME_RINVCMDLEN, ESME_RINVTLVSTREAM, …) — on unterminated C-Octet strings,
truncated TLVs, or oversized commandLength. The connection layer turns these into
generic_nack responses automatically.
Framing helpers
import { readPduLength, PDU_HEADER_LENGTH, MAX_SEQUENCE_NUMBER } from "@better-smpp/core";
readPduLength(buffer); // command_length of the next frame, or undefined if < 4 bytesUseful when implementing your own transport; SmppConnection already does this for
you.