better-smpp
Core

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.
  • shortMessage text is encoded per dataCoding — or auto-detected (GSM → Latin-1 → UCS2) with the detected dataCoding stamped onto the PDU.
  • A message with a udh sets the UDHI bit on esmClass automatically.
  • A non-zero commandStatus encodes 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 bytes

Useful when implementing your own transport; SmppConnection already does this for you.

On this page