Run SMPPSim 2.6.11 as the SMSC with nine fault-injection variants: 25 scenarios pass, two defects recorded

This commit is contained in:
2026-09-05 20:08:39 +02:00
parent 05ea90afeb
commit fec4fda082
15 changed files with 1602 additions and 2 deletions
+3 -1
View File
@@ -36,7 +36,9 @@ Both counts must be zero for a phase to pass.
## Rules for an experiment
1. Peers run in Docker with full patch-version pins. Nothing is installed on the host. Node runs
only through the `node` service.
only through the `node` service. Every peer service caps its logs (`logging: json-file`,
`max-size`/`max-file`) and healthchecks something the peer does not log a stack trace for —
SMPPSim once filled the whole host disk in twenty minutes from a TCP-probe healthcheck.
2. `src/` and `test/` are read-only during an experiment. A defect is recorded with a reproducer,
never fixed here — a fix is a separate change with a regression test in `test/`.
3. No git command that changes state: no add, commit, push, checkout, stash, reset. The
+2 -1
View File
@@ -193,7 +193,8 @@ document, library changes, and the AGENTS.md decision record each one needs.
| --- | --- | --- |
| 0 | done | [01-smscsim.md](findings/01-smscsim.md) |
| 1 | done | [01-smscsim.md](findings/01-smscsim.md) |
| 23 | not started | — |
| 2 | done | [02-smppsim.md](findings/02-smppsim.md) |
| 3 | not started | — |
| 4 | done | [04-kannel.md](findings/04-kannel.md) |
| 510 | not started | — |
+130
View File
@@ -0,0 +1,130 @@
x-smppsim-healthcheck: &smppsim-healthcheck
# Not a raw TCP probe on 2775: SMPPSim reads an empty connection as a malformed PDU and logs a
# full stack trace per attempt: with DECODE_PDUS_IN_LOG and a 1s interval that fills the disk
# within tens of minutes (24GB+ across the 9 services in-house). The HTTP admin port answers a
# bare request harmlessly.
test: ["CMD-SHELL", "curl -sf -o /dev/null http://127.0.0.1:8884/"]
interval: 1s
retries: 30
timeout: 2s
# A second line of defence for the same runaway-logging risk: DECODE_PDUS_IN_LOG dumps every PDU,
# so a stuck reconnect loop or a bad probe fills the disk long before anyone notices.
x-log-limits: &log-limits
logging:
driver: json-file
options:
max-file: "3"
max-size: 20m
services:
smppsim:
build:
context: interop-tests/peers/smppsim
image: larvitsmpp-interop/smppsim:bc29982
<<: *log-limits
command: ["conf/smppsim.props"]
healthcheck: *smppsim-healthcheck
smppsim-textdlr:
image: larvitsmpp-interop/smppsim:bc29982
<<: *log-limits
command: ["conf/smppsim-textdlr.props"]
healthcheck: *smppsim-healthcheck
smppsim-transition:
image: larvitsmpp-interop/smppsim:bc29982
<<: *log-limits
command: ["conf/smppsim-transition.props"]
healthcheck: *smppsim-healthcheck
smppsim-undeliv:
image: larvitsmpp-interop/smppsim:bc29982
<<: *log-limits
command: ["conf/smppsim-undeliv.props"]
healthcheck: *smppsim-healthcheck
smppsim-rejected:
image: larvitsmpp-interop/smppsim:bc29982
<<: *log-limits
command: ["conf/smppsim-rejected.props"]
healthcheck: *smppsim-healthcheck
smppsim-accepted:
image: larvitsmpp-interop/smppsim:bc29982
<<: *log-limits
command: ["conf/smppsim-accepted.props"]
healthcheck: *smppsim-healthcheck
smppsim-delayed:
image: larvitsmpp-interop/smppsim:bc29982
<<: *log-limits
command: ["conf/smppsim-delayed.props"]
healthcheck: *smppsim-healthcheck
smppsim-queuefull:
image: larvitsmpp-interop/smppsim:bc29982
<<: *log-limits
command: ["conf/smppsim-queuefull.props"]
healthcheck: *smppsim-healthcheck
# OUTBIND_ESME_IP_ADDRESS in smppsim-outbind.props names the node service by its --use-aliases
# hostname; our own server() in smppsim.test.ts listens on port 2776 for it to connect to.
smppsim-outbind:
image: larvitsmpp-interop/smppsim:bc29982
<<: *log-limits
command: ["conf/smppsim-outbind.props"]
healthcheck: *smppsim-healthcheck
capture:
image: nicolaka/netshoot:v0.16
network_mode: "service:smppsim"
cap_add:
- NET_ADMIN
- NET_RAW
depends_on:
smppsim:
condition: service_started
command: ["dumpcap", "-i", "any", "-f", "tcp port 2775", "-w", "/captures/smppsim.pcapng"]
volumes:
- ./interop-tests/captures:/captures
# A second sidecar, cheap to add, so a textdlr-specific wire question can be checked without a
# second run. Not read by run.py's own pass/fail (that only decodes captures/smppsim.pcapng).
capture-textdlr:
image: nicolaka/netshoot:v0.16
network_mode: "service:smppsim-textdlr"
cap_add:
- NET_ADMIN
- NET_RAW
depends_on:
smppsim-textdlr:
condition: service_started
command: ["dumpcap", "-i", "any", "-f", "tcp port 2775", "-w", "/captures/smppsim-textdlr.pcapng"]
volumes:
- ./interop-tests/captures:/captures
node:
depends_on:
capture:
condition: service_started
capture-textdlr:
condition: service_started
smppsim:
condition: service_healthy
smppsim-textdlr:
condition: service_healthy
smppsim-transition:
condition: service_healthy
smppsim-undeliv:
condition: service_healthy
smppsim-rejected:
condition: service_healthy
smppsim-accepted:
condition: service_healthy
smppsim-delayed:
condition: service_healthy
smppsim-queuefull:
condition: service_healthy
smppsim-outbind:
condition: service_healthy
+204
View File
@@ -0,0 +1,204 @@
# 02 smppsim
Date: 2026-09-05. Repo commit: `9c4939f`. Host Docker: 29.6.2. Images: `larvitsmpp-interop/smppsim:bc29982`
(built locally here from `kwahome/smpp-sim-docker` at commit `bc299828af9046ab290da3b4957dfbc472f02bfb`,
running SMPPSim 2.6.11 on `eclipse-temurin:8u452-b09-jre`; cloned during the build with
`debian:13.2-slim`, never vendored), `nicolaka/netshoot:v0.16` (capture sidecars and tshark),
`node:24.18.0-bookworm-slim` (test runner, from the root `compose.yaml`).
## Setup
`interop-tests/peers/smppsim/Dockerfile` clones the pinned commit in a build stage and copies just
`smppsim.jar`, `lib`, `www`, `mo` and `conf/logging.properties` into the runtime stage; each
`smppsim*.props` file under the same directory is copied in and selected via the container
`command`. `interop-tests/compose.smppsim.yaml` runs nine variants of the one image (`smppsim`,
`-textdlr`, `-transition`, `-undeliv`, `-rejected`, `-accepted`, `-delayed`, `-queuefull`,
`-outbind`) plus `capture` (on `smppsim`) and `capture-textdlr`, all on one network so
`./interop-tests/run.py smppsim` covers everything in one run.
Two snags fixed while building the harness:
- **The first healthcheck (`bash -c 'echo > /dev/tcp/127.0.0.1/2775'`, 1s interval) filled the
host's disk.** SMPPSim reads an empty connection as a malformed PDU and logs a full Java stack
trace per attempt; with `DECODE_PDUS_IN_LOG=true` and a 1s probe interval, nine containers left
running for ~20 minutes wrote 24GB+ of container logs between them and took the whole shared host
to 0 bytes free (`docker run` itself started failing with "no space left on device"). Fixed by
probing the HTTP admin port instead (`curl -sf -o /dev/null http://127.0.0.1:8884/`), which
SMPPSim answers harmlessly, plus a `json-file` log cap (`max-size: 20m`, `max-file: "3"`) on every
`smppsim*` service as a second line of defence. Worth knowing for anyone else pointing a 1s
TCP-connect healthcheck at this peer.
- The `capture` sidecar's `dumpcap` needs the same root/DAC-override handling phase 1 documented
(`interop-tests/captures/` chowned to `1000:1000`/`0777` by `run.py` after every run, stale
`<peer>.pcapng` unlinked before the next). Once, a capture attempt failed outright
("Permission denied" opening the pcapng) for a reason not pinned down - not reproduced since;
treat as a rare, unexplained flake in this harness rather than a peer issue.
- Two early runs showed `malformed: 2`, `expert errors: 2`. Both traced to this test file, not
SMPPSim: C17's raw-UDH test used IEI `0x01` ("Special SMS Message Indication"), a real GSM 03.40
information element with its own defined length (2 bytes), at length 1 - Wireshark's
`gsm_sms_ud` dissector correctly flags that as malformed. Fixed by using IEI `0x70`
(reserved-for-future-use, so no dissector validates its length) for what the test only ever
needed to be an arbitrary, unparsed UDH element.
Two runs of `./interop-tests/run.py smppsim`, back to back, after that fix: both exit 0, 25/25
tests passing, `malformed: 0`, `expert errors: 0` in both.
## Scenarios
| Id (from PLAN.md) | Result | Evidence |
| --- | --- | --- |
| C2 (`smppsim-textdlr`) | pass | `smppsim-textdlr - C2 text-only receipts`; no TLVs, `dlr.smsId` matches `submit_sm_resp` |
| C3 (`smppsim`, TLVs on) | pass | `smppsim - C3+C7 …`; `receipted_message_id`/`message_state` TLVs present and agree with the body on every segment |
| C4 (`smppsim-transition`) | fail (peer defect, see below) | `smppsim-transition - C4 …`; intermediate report arrives, no final ever does |
| C5 (single-state variants) | pass | `smppsim single-state variants - C5 …`; UNDELIVERABLE/REJECTED/ACCEPTED each map correctly; 2-segment message's shared status confirmed, `messageDlr` confirmed absent (see Open questions) |
| C6 (`smppsim-delayed`) | pass | `smppsim-delayed - C6 …`; disconnect+reconnect observed, delayed receipt still reaches `dlr` on the new link |
| C7 (loopback, GSM 1/2/3/10-segment) | pass | same `C3+C7` suite; one id per segment, receipt per id, loopback reassembles the exact text including € and \[ \] |
| C7 (loopback, UCS2 2-segment) | pass with a documented defect | `… 2-segment UCS2 …: TLVs stay right, the receipt body is corrupted`; TLVs and loopback reassembly both correct, receipt body unreadable - `@larvit/smpp` defect below |
| C11 (bind refusal + backoff) | pass | `smppsim - C11 …`, three sub-tests, see below for what each shows |
| C12 (`smppsim-queuefull`) | pass | `smppsim-queuefull - C12 …`; `ESME_RMSGQFUL` returned, session stays bound, later send succeeds once the one-slot queue drains |
| C13 (`maxOutstanding: 1`, 10 parallel) | pass | `smppsim - C13 …`; 10 distinct, strictly-increasing ids, none lost |
| C15 (bind version) | pass (peer never declares, see below) | `smppsim - C15 …`, both 0x34 and 0x50 |
| C17 (encodings over loopback) | pass | `smppsim - C17 …`, 5 sub-tests: Latin-1, UCS-2, flash (0x10), raw 0xF0 (not flash), raw UDH+8-bit-binary |
| C18 (`smppsim-outbind`) | pass (record, not judge) | `smppsim-outbind - C18 …`; see below for the wire facts |
## Defects in @larvit/smpp
### A delivery receipt's `data_coding` is trusted to decode its body, even though the spec makes the receipt a fixed text format
**What happened.** A message sent with `encoding: 'UCS2'` gets `data_coding: 8` on its `submit_sm`.
SMPPSim's delivery receipt for it (confirmed against source, `DeliveryReceipt` extends `DeliverSM`
by copy-constructing the original `SubmitSM`) inherits that same `data_coding: 8`, but its
`short_message` is always plain ASCII text (`id:… sub:… dlvrd:… stat:…`). `pdu.ts`'s parser decodes
any non-UDH PDU's `short_message` using its own `data_coding` unconditionally
(`params.short_message = decodeMessage(message, paramNumber(params.data_coding, 0)).message`), so
the ASCII receipt bytes are read back as UCS2 (byte pairs swapped) - `id:001 sub:…` becomes
unrecoverable CJK-range glyphs, and `dlr.receipt` (hence `.stat`, `.id`, every body field) is
garbage or `undefined` for every receipt whose triggering message used a non-ASCII encoding.
**What the spec says.** SMPP 3.4 5.2.25 defines `short_message`/`message_payload` generically, but
Appendix B's receipt format is specified as fixed ASCII fields; `data_coding` on the *receipt* PDU
describes nothing about how to read that text, since the MC is reporting on a message rather than
carrying one. Trusting `data_coding` to decode a receipt body is reading a field the spec never
attaches that meaning to. The `receipted_message_id`/`message_state` TLVs are unaffected (typed
fields, not text), which is why `dlr.statusMsg` and `dlr.smsId` stay correct here - only the
body-derived `dlr.receipt` (and anything relying on it, e.g. a peer without TLVs) is lost.
**Reproducer.** `session.sendSms({ encoding: 'UCS2', dlr: true, message: 'x', from, to })` against
`smppsim`, then inspect the `dlr` event's second argument (the raw `PduObject`):
`pduObj.params.data_coding === 8` and `dlr.receipt === undefined`, while
`pduObj.tlvs.receipted_message_id`/`message_state` are present and correct. Confirmed both against
SMPPSim and by constructing the PDU directly: a `deliver_sm` with `data_coding=8`, `esm_class=4`
and an ASCII `id:0 sub:001 dlvrd:001 …` body decodes to garbage text via `pduToObj`.
**Severity.** Medium: harmless when TLVs are present (as here), since `statusMsg`/`smsId` still
resolve correctly - but total for a peer that answers `DELIVERY_RECEIPT_OPTIONAL_PARAMS=false`
(text-only, `smppsim-textdlr`'s own style) *and* accepts non-ASCII submits, where nothing would be
left to fall back on. Not reproduced against `smppsim-textdlr` here (that variant's own C2 test
only sends ASCII), so this is inferred from the mechanism, not independently confirmed there - see
Open questions.
### `reconnect` never retries the very first connect or bind attempt
**What happened.** `client()` performs the socket connect and the initial bind directly, not
through the `ReconnectLoop`; on either failing (`ESME_RINVPASWD`, `ESME_RBINDFAIL`, connection
refused, …) it calls `session.close()` and returns `{ err }` with no session at all - the
`reconnect` option is never consulted. Confirmed with a wrong password and with a closed port
against `smppsim` (`smppsim - C11 …`, both "one attempt, no retry" sub-tests): each is exactly one
bind attempt, logged once, no matter how long the test then waits. `ReconnectLoop.schedule()` only
runs from `onClose()`/a failed `comeBackUp()` after a session has been up at least once - confirmed
separately by binding once, then mutating the same `options` object's `password` before dropping
the live link: the loop *does* retry with proper backoff then (`smppsim - C11 …, "a rebind refused
after a live link drops"`: 2-6 attempts over 12s, each gap ≥150ms).
**What the spec/README say.** Neither documents this boundary explicitly; `README.md`'s `reconnect`
entry reads as covering any drop uniformly. Given hard rule/goal 4 ("no bind flooding"), never
retrying a cold failure is arguably the safer default - a bad password retried forever would flood
exactly as much as this avoids it entirely - but it means a client started before its peer's
listener is up gets one shot and gives up for good, which a caller relying on `reconnect` for that
race would not expect.
**Reproducer.** `client({ host, password: 'wrong', reconnect: { minDelay: 1000, maxDelay: 4000 } })`
against `smppsim` (valid `system_id`, wrong `password`): resolves `{ err }` once, no `session`; a
`log.info` capture shows exactly one `'client - bind refused'` line even after a further 2s wait.
**Severity.** Low / informational - plausibly intentional, not previously written down anywhere
`git grep`-able in this repo.
## Peer quirks
- **`registered_delivery` 0x11 (final + intermediate together) never produces a final receipt.**
SMPPSim's own user guide (v2.5 release notes) says "Set to 0x11 for both intermediate
notification and final delivery receipts." `LifeCycleManager.setState()` tests
`registered_delivery_flag == 1` or `== 2` by exact integer equality rather than a bitmask
(`InboundQueue.addMessageState()`'s own intermediate-notification check correctly uses
`(rd & 0x10) == 0x10`), so 17 (0x11) matches neither branch: the intermediate report (esm_class
0x20, `ENROUTE`) always arrives, a final one never does, however long you wait. Confirmed by
reading `LifeCycleManager.java`, `MessageState.java` and `OutboundQueue.java`, and empirically
with a 16s wait against `smppsim-transition`.
- **Two `submit_sm`'s on the same connection without a response in between - exactly how this
library always sends a multi-segment message's segments (`Promise.all`, never one after the
previous one's response, a documented, deliberate design choice, not something to change here) -
sometimes make SMPPSim lose one of the resulting receipts or loopback echoes entirely.** Never
seen as a malformed/corrupted PDU on the wire (both kept runs, and every other run once the
harness's own UDH bug above was fixed, show zero); the PDU that should have arrived just never
does. Confirmed by direct A/B: a script sending two segments concurrently (this library's own
shape) got only one of two receipts about half the time across several tries; the same script
sending two independent `submit_sm`'s sequentially, awaiting each response before the next, never
lost one in the same number of tries. `interop-tests/smppsim.test.ts`'s own multi-segment tests
work around it by resending a fresh message (up to 10 times) until every segment's receipt and,
where relevant, the loopback reassembly are all present in one attempt - see
`sendUntilAllDlrsArrive`/`sendUntilComplete`/`dlrLooksIntact` there. The mechanism is unconfirmed
(see Open questions).
- **`bind_resp` never carries `sc_interface_version`, whatever the ESME declared.** No
`BindTransceiverResp`/`BindReceiverResp`/`BindTransmitterResp` class sets that TLV (confirmed
against source). `@larvit/smpp`'s client therefore always records
`peerInterfaceVersion === 0x00` (undeclared) and `acceptsOptionalParams() === false` against this
peer, whether the client declared 0x34 or 0x50 - yet `smppsim` (with
`DELIVERY_RECEIPT_OPTIONAL_PARAMS=true`) still sends `receipted_message_id`/`message_state` TLVs
on every receipt regardless, because that gate reads the *client's own declared* version from the
bind PDU it received, not anything it echoes back. Confirmed for both 0x34 and 0x50 (`smppsim -
C15 …`).
- **`DelayedDrQueue`'s own poll loop is hardcoded to 5000ms** (`private static final int period =
5000;`, not a props knob - `DELAYED_INBOUND_QUEUE_PROCESSING_PERIOD` is a different queue, for
redelivery after `ESME_RMSGQFUL`). `DELAY_DELIVERY_RECEIPTS_BY` is a floor, not the delay: a
receipt configured for an 8000ms delay can take up to ~13000ms in practice. Confirmed by direct
measurement (an 11s wait saw nothing; 16s did).
- **Message ids are decimal, a per-process global counter** (`message_id++`, not per-connection),
starting at 0 unless `START_MESSAGE_ID_AT`/`MESSAGE_ID_PREFIX` are set (neither is here) - matches
the research notes; `submit_sm_resp` and every receipt spelling (TLV and body `id:`) agree, so no
`smsIdFormat` was needed anywhere in this suite.
- **The receipt body's echoed-message field is `Text:` (capitalised, not `text:`)** and carries up
to the first 20 bytes of the original message when it is non-empty - never the empty `text:` some
other peers (documented: LINK) send. `@larvit/smpp`'s parsing is already case-insensitive, so this
needed no special handling.
- **`outbind()` closes the socket immediately after writing, without reading anything back.**
Confirmed against source (`Smsc.outbind()`: `out.write(...); out.flush(); out.close(); s.close();`
with no read in between) and on the wire (`smppsim-outbind - C18 …`): our server's
`incomingPduObj` sees the `outbind` (`system_id: smppclient1`), `onRequest` tries to answer
`ESME_RINVBNDSTS` and fails before writing anything (`outbind_resp` is not a defined command, so
`pduReturn` errors first) - `sessionError` fires with `"outbind" has no response command`, no PDU
reaches the wire, matching target 6. Whether our socket sees SMPPSim's own close land as a clean
`close` was observed but not asserted against, per the task ("record, do not judge").
- **`outbind` fires the moment SMPPSim has an MO to deliver and no receiver is bound** - not on a
timer. `InboundQueue.processQueue()`'s wait/notify wakes on `iq.addMessage(...)`, and moves
straight to `PENDING_QUEUE` + `outbind()` when `getReceiverBoundCount() == 0`; the MO Injection
endpoint (`GET /inject_mo?source_addr=…&destination_addr=…&short_message=…`, not `/inject_mo.htm`
as the docs might suggest - that path is the *form*, `/inject_mo` is the handler) works with zero
ESMEs ever bound, which is what let this test trigger `outbind` deterministically instead of
racing `DELIVERY_MESSAGES_PER_MINUTE` against container startup.
## Open questions
- Whether the `data_coding`-decodes-the-receipt-body defect also loses receipts against a peer
answering `DELIVERY_RECEIPT_OPTIONAL_PARAMS=false` (no TLV fallback) - `smppsim-textdlr`'s own
scenario here only exercises an ASCII message, so the compounding case (non-ASCII + no TLVs) is
untested.
- The exact mechanism behind the occasional lost receipt/loopback echo under concurrent
`submit_sm`'s. A plausible read of SMPPSim's threading (the connection-handler thread writing
`submit_sm_resp`s and loopback echoes, a separate `OutboundQueue`/`InboundQueue` thread writing
receipts, both against the same socket with no synchronisation found in
`StandardConnectionHandler`) would predict corruption, not a clean loss - but no malformed PDU
was ever observed for a genuine SMPPSim-authored frame in this suite, so that read is unconfirmed
and the actual mechanism (dropped at the SMPPSim side before it ever reaches the wire, versus
something on our side discarding a well-formed PDU) is still open.
- Phase 2's own instructions call for `smppsim-accepted`/`-rejected` as "similar single-state
variants... if cheap"; both were cheap and are included, alongside `-undeliv`.
+30
View File
@@ -0,0 +1,30 @@
# SMPPSim's own site (seleniumsoftware.com) answers 522; kwahome/smpp-sim-docker vendors the last
# surviving 2.6.11 build (see ../../research/smsc-simulators.md #1). Cloned at a pinned commit here
# rather than vendored into this repo.
FROM debian:13.2-slim AS source
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates git \
&& rm -rf /var/lib/apt/lists/*
ARG SMPPSIM_COMMIT=bc299828af9046ab290da3b4957dfbc472f02bfb
RUN git clone https://github.com/kwahome/smpp-sim-docker.git /src \
&& cd /src && git checkout "${SMPPSIM_COMMIT}"
# SMPPSim 2.6.x targets Java 6/7; runs unmodified on this 8 JRE.
FROM eclipse-temurin:8u452-b09-jre
WORKDIR /app
COPY --from=source /src/SMPPSim/smppsim.jar ./smppsim.jar
COPY --from=source /src/SMPPSim/lib ./lib
COPY --from=source /src/SMPPSim/www ./www
COPY --from=source /src/SMPPSim/mo ./mo
COPY --from=source /src/SMPPSim/conf/logging.properties ./conf/logging.properties
COPY *.props ./conf/
EXPOSE 2775 8884
ENTRYPOINT ["java", "-Djava.net.preferIPv4Stack=true", "-Djava.util.logging.config.file=conf/logging.properties", "-jar", "smppsim.jar"]
CMD ["conf/smppsim.props"]
@@ -0,0 +1,51 @@
SMPP_PORT=2775
SMPP_CONNECTION_HANDLERS=20
CONNECTION_HANDLER_CLASS=com.seleniumsoftware.SMPPSim.StandardConnectionHandler
PROTOCOL_HANDLER_CLASS=com.seleniumsoftware.SMPPSim.StandardProtocolHandler
LIFE_CYCLE_MANAGER=com.seleniumsoftware.SMPPSim.LifeCycleManager
MESSAGE_STATE_CHECK_FREQUENCY=200
MAX_TIME_ENROUTE=300
DELAY_DELIVERY_RECEIPTS_BY=0
PERCENTAGE_THAT_TRANSITION=100
PERCENTAGE_DELIVERED=0
PERCENTAGE_UNDELIVERABLE=0
PERCENTAGE_ACCEPTED=100
PERCENTAGE_REJECTED=0
DISCARD_FROM_QUEUE_AFTER=60000
HTTP_PORT=8884
HTTP_THREADS=4
DOCROOT=www
AUTHORISED_FILES=/css/style.css,/index.htm,/inject_mo.htm,/favicon.ico,/images/logo.gif,/images/dots.gif,/user-guide.htm,/images/homepage.gif,/images/inject_mo.gif
INJECT_MO_PAGE=/inject_mo.htm
SYSTEM_IDS=smppclient1,smppclient2,smppclient3
PASSWORDS=password,password,password
OUTBIND_ENABLED=false
OUTBIND_ESME_IP_ADDRESS=127.0.0.1
OUTBIND_ESME_PORT=2776
OUTBIND_ESME_SYSTEMID=smppclient1
OUTBIND_ESME_PASSWORD=password
DELIVERY_MESSAGES_PER_MINUTE=0
DELIVER_MESSAGES_FILE=mo/deliver_messages.csv
LOOPBACK=false
ESME_TO_ESME=false
OUTBOUND_QUEUE_MAX_SIZE=1000
INBOUND_QUEUE_MAX_SIZE=1000
DELAYED_INBOUND_QUEUE_PROCESSING_PERIOD=60
DELAYED_INBOUND_QUEUE_MAX_ATTEMPTS=50
DECODE_PDUS_IN_LOG=true
CAPTURE_SME_BINARY=false
CAPTURE_SME_BINARY_TO_FILE=sme_binary.capture
CAPTURE_SMPPSIM_BINARY=false
CAPTURE_SMPPSIM_BINARY_TO_FILE=smppsim_binary.capture
CAPTURE_SME_DECODED=false
CAPTURE_SME_DECODED_TO_FILE=sme_decoded.capture
CAPTURE_SMPPSIM_DECODED=false
CAPTURE_SMPPSIM_DECODED_TO_FILE=smppsim_decoded.capture
CALLBACK=false
CALLBACK_ID=SIM1
CALLBACK_TARGET_HOST=localhost
CALLBACK_PORT=3333
DELIVER_SM_INCLUDES_USSD_SERVICE_OP=false
DELIVERY_RECEIPT_OPTIONAL_PARAMS=true
SMSCID=SMPPSim
SIMULATE_VARIABLE_SUBMIT_SM_RESPONSE_TIMES=false
@@ -0,0 +1,51 @@
SMPP_PORT=2775
SMPP_CONNECTION_HANDLERS=20
CONNECTION_HANDLER_CLASS=com.seleniumsoftware.SMPPSim.StandardConnectionHandler
PROTOCOL_HANDLER_CLASS=com.seleniumsoftware.SMPPSim.StandardProtocolHandler
LIFE_CYCLE_MANAGER=com.seleniumsoftware.SMPPSim.LifeCycleManager
MESSAGE_STATE_CHECK_FREQUENCY=200
MAX_TIME_ENROUTE=300
DELAY_DELIVERY_RECEIPTS_BY=8000
PERCENTAGE_THAT_TRANSITION=100
PERCENTAGE_DELIVERED=100
PERCENTAGE_UNDELIVERABLE=0
PERCENTAGE_ACCEPTED=0
PERCENTAGE_REJECTED=0
DISCARD_FROM_QUEUE_AFTER=60000
HTTP_PORT=8884
HTTP_THREADS=4
DOCROOT=www
AUTHORISED_FILES=/css/style.css,/index.htm,/inject_mo.htm,/favicon.ico,/images/logo.gif,/images/dots.gif,/user-guide.htm,/images/homepage.gif,/images/inject_mo.gif
INJECT_MO_PAGE=/inject_mo.htm
SYSTEM_IDS=smppclient1,smppclient2,smppclient3
PASSWORDS=password,password,password
OUTBIND_ENABLED=false
OUTBIND_ESME_IP_ADDRESS=127.0.0.1
OUTBIND_ESME_PORT=2776
OUTBIND_ESME_SYSTEMID=smppclient1
OUTBIND_ESME_PASSWORD=password
DELIVERY_MESSAGES_PER_MINUTE=0
DELIVER_MESSAGES_FILE=mo/deliver_messages.csv
LOOPBACK=false
ESME_TO_ESME=false
OUTBOUND_QUEUE_MAX_SIZE=1000
INBOUND_QUEUE_MAX_SIZE=1000
DELAYED_INBOUND_QUEUE_PROCESSING_PERIOD=60
DELAYED_INBOUND_QUEUE_MAX_ATTEMPTS=50
DECODE_PDUS_IN_LOG=true
CAPTURE_SME_BINARY=false
CAPTURE_SME_BINARY_TO_FILE=sme_binary.capture
CAPTURE_SMPPSIM_BINARY=false
CAPTURE_SMPPSIM_BINARY_TO_FILE=smppsim_binary.capture
CAPTURE_SME_DECODED=false
CAPTURE_SME_DECODED_TO_FILE=sme_decoded.capture
CAPTURE_SMPPSIM_DECODED=false
CAPTURE_SMPPSIM_DECODED_TO_FILE=smppsim_decoded.capture
CALLBACK=false
CALLBACK_ID=SIM1
CALLBACK_TARGET_HOST=localhost
CALLBACK_PORT=3333
DELIVER_SM_INCLUDES_USSD_SERVICE_OP=false
DELIVERY_RECEIPT_OPTIONAL_PARAMS=true
SMSCID=SMPPSim
SIMULATE_VARIABLE_SUBMIT_SM_RESPONSE_TIMES=false
@@ -0,0 +1,51 @@
SMPP_PORT=2775
SMPP_CONNECTION_HANDLERS=20
CONNECTION_HANDLER_CLASS=com.seleniumsoftware.SMPPSim.StandardConnectionHandler
PROTOCOL_HANDLER_CLASS=com.seleniumsoftware.SMPPSim.StandardProtocolHandler
LIFE_CYCLE_MANAGER=com.seleniumsoftware.SMPPSim.LifeCycleManager
MESSAGE_STATE_CHECK_FREQUENCY=200
MAX_TIME_ENROUTE=300
DELAY_DELIVERY_RECEIPTS_BY=0
PERCENTAGE_THAT_TRANSITION=100
PERCENTAGE_DELIVERED=100
PERCENTAGE_UNDELIVERABLE=0
PERCENTAGE_ACCEPTED=0
PERCENTAGE_REJECTED=0
DISCARD_FROM_QUEUE_AFTER=60000
HTTP_PORT=8884
HTTP_THREADS=4
DOCROOT=www
AUTHORISED_FILES=/css/style.css,/index.htm,/inject_mo.htm,/favicon.ico,/images/logo.gif,/images/dots.gif,/user-guide.htm,/images/homepage.gif,/images/inject_mo.gif
INJECT_MO_PAGE=/inject_mo.htm
SYSTEM_IDS=smppclient1,smppclient2,smppclient3
PASSWORDS=password,password,password
OUTBIND_ENABLED=true
OUTBIND_ESME_IP_ADDRESS=node
OUTBIND_ESME_PORT=2776
OUTBIND_ESME_SYSTEMID=smppclient1
OUTBIND_ESME_PASSWORD=password
DELIVERY_MESSAGES_PER_MINUTE=0
DELIVER_MESSAGES_FILE=mo/deliver_messages.csv
LOOPBACK=false
ESME_TO_ESME=false
OUTBOUND_QUEUE_MAX_SIZE=1000
INBOUND_QUEUE_MAX_SIZE=1000
DELAYED_INBOUND_QUEUE_PROCESSING_PERIOD=60
DELAYED_INBOUND_QUEUE_MAX_ATTEMPTS=50
DECODE_PDUS_IN_LOG=true
CAPTURE_SME_BINARY=false
CAPTURE_SME_BINARY_TO_FILE=sme_binary.capture
CAPTURE_SMPPSIM_BINARY=false
CAPTURE_SMPPSIM_BINARY_TO_FILE=smppsim_binary.capture
CAPTURE_SME_DECODED=false
CAPTURE_SME_DECODED_TO_FILE=sme_decoded.capture
CAPTURE_SMPPSIM_DECODED=false
CAPTURE_SMPPSIM_DECODED_TO_FILE=smppsim_decoded.capture
CALLBACK=false
CALLBACK_ID=SIM1
CALLBACK_TARGET_HOST=localhost
CALLBACK_PORT=3333
DELIVER_SM_INCLUDES_USSD_SERVICE_OP=false
DELIVERY_RECEIPT_OPTIONAL_PARAMS=true
SMSCID=SMPPSim
SIMULATE_VARIABLE_SUBMIT_SM_RESPONSE_TIMES=false
@@ -0,0 +1,51 @@
SMPP_PORT=2775
SMPP_CONNECTION_HANDLERS=20
CONNECTION_HANDLER_CLASS=com.seleniumsoftware.SMPPSim.StandardConnectionHandler
PROTOCOL_HANDLER_CLASS=com.seleniumsoftware.SMPPSim.StandardProtocolHandler
LIFE_CYCLE_MANAGER=com.seleniumsoftware.SMPPSim.LifeCycleManager
MESSAGE_STATE_CHECK_FREQUENCY=100
MAX_TIME_ENROUTE=200
DELAY_DELIVERY_RECEIPTS_BY=0
PERCENTAGE_THAT_TRANSITION=100
PERCENTAGE_DELIVERED=100
PERCENTAGE_UNDELIVERABLE=0
PERCENTAGE_ACCEPTED=0
PERCENTAGE_REJECTED=0
DISCARD_FROM_QUEUE_AFTER=200
HTTP_PORT=8884
HTTP_THREADS=4
DOCROOT=www
AUTHORISED_FILES=/css/style.css,/index.htm,/inject_mo.htm,/favicon.ico,/images/logo.gif,/images/dots.gif,/user-guide.htm,/images/homepage.gif,/images/inject_mo.gif
INJECT_MO_PAGE=/inject_mo.htm
SYSTEM_IDS=smppclient1,smppclient2,smppclient3
PASSWORDS=password,password,password
OUTBIND_ENABLED=false
OUTBIND_ESME_IP_ADDRESS=127.0.0.1
OUTBIND_ESME_PORT=2776
OUTBIND_ESME_SYSTEMID=smppclient1
OUTBIND_ESME_PASSWORD=password
DELIVERY_MESSAGES_PER_MINUTE=0
DELIVER_MESSAGES_FILE=mo/deliver_messages.csv
LOOPBACK=false
ESME_TO_ESME=false
OUTBOUND_QUEUE_MAX_SIZE=1
INBOUND_QUEUE_MAX_SIZE=1000
DELAYED_INBOUND_QUEUE_PROCESSING_PERIOD=60
DELAYED_INBOUND_QUEUE_MAX_ATTEMPTS=50
DECODE_PDUS_IN_LOG=true
CAPTURE_SME_BINARY=false
CAPTURE_SME_BINARY_TO_FILE=sme_binary.capture
CAPTURE_SMPPSIM_BINARY=false
CAPTURE_SMPPSIM_BINARY_TO_FILE=smppsim_binary.capture
CAPTURE_SME_DECODED=false
CAPTURE_SME_DECODED_TO_FILE=sme_decoded.capture
CAPTURE_SMPPSIM_DECODED=false
CAPTURE_SMPPSIM_DECODED_TO_FILE=smppsim_decoded.capture
CALLBACK=false
CALLBACK_ID=SIM1
CALLBACK_TARGET_HOST=localhost
CALLBACK_PORT=3333
DELIVER_SM_INCLUDES_USSD_SERVICE_OP=false
DELIVERY_RECEIPT_OPTIONAL_PARAMS=true
SMSCID=SMPPSim
SIMULATE_VARIABLE_SUBMIT_SM_RESPONSE_TIMES=false
@@ -0,0 +1,51 @@
SMPP_PORT=2775
SMPP_CONNECTION_HANDLERS=20
CONNECTION_HANDLER_CLASS=com.seleniumsoftware.SMPPSim.StandardConnectionHandler
PROTOCOL_HANDLER_CLASS=com.seleniumsoftware.SMPPSim.StandardProtocolHandler
LIFE_CYCLE_MANAGER=com.seleniumsoftware.SMPPSim.LifeCycleManager
MESSAGE_STATE_CHECK_FREQUENCY=200
MAX_TIME_ENROUTE=300
DELAY_DELIVERY_RECEIPTS_BY=0
PERCENTAGE_THAT_TRANSITION=100
PERCENTAGE_DELIVERED=0
PERCENTAGE_UNDELIVERABLE=0
PERCENTAGE_ACCEPTED=0
PERCENTAGE_REJECTED=100
DISCARD_FROM_QUEUE_AFTER=60000
HTTP_PORT=8884
HTTP_THREADS=4
DOCROOT=www
AUTHORISED_FILES=/css/style.css,/index.htm,/inject_mo.htm,/favicon.ico,/images/logo.gif,/images/dots.gif,/user-guide.htm,/images/homepage.gif,/images/inject_mo.gif
INJECT_MO_PAGE=/inject_mo.htm
SYSTEM_IDS=smppclient1,smppclient2,smppclient3
PASSWORDS=password,password,password
OUTBIND_ENABLED=false
OUTBIND_ESME_IP_ADDRESS=127.0.0.1
OUTBIND_ESME_PORT=2776
OUTBIND_ESME_SYSTEMID=smppclient1
OUTBIND_ESME_PASSWORD=password
DELIVERY_MESSAGES_PER_MINUTE=0
DELIVER_MESSAGES_FILE=mo/deliver_messages.csv
LOOPBACK=false
ESME_TO_ESME=false
OUTBOUND_QUEUE_MAX_SIZE=1000
INBOUND_QUEUE_MAX_SIZE=1000
DELAYED_INBOUND_QUEUE_PROCESSING_PERIOD=60
DELAYED_INBOUND_QUEUE_MAX_ATTEMPTS=50
DECODE_PDUS_IN_LOG=true
CAPTURE_SME_BINARY=false
CAPTURE_SME_BINARY_TO_FILE=sme_binary.capture
CAPTURE_SMPPSIM_BINARY=false
CAPTURE_SMPPSIM_BINARY_TO_FILE=smppsim_binary.capture
CAPTURE_SME_DECODED=false
CAPTURE_SME_DECODED_TO_FILE=sme_decoded.capture
CAPTURE_SMPPSIM_DECODED=false
CAPTURE_SMPPSIM_DECODED_TO_FILE=smppsim_decoded.capture
CALLBACK=false
CALLBACK_ID=SIM1
CALLBACK_TARGET_HOST=localhost
CALLBACK_PORT=3333
DELIVER_SM_INCLUDES_USSD_SERVICE_OP=false
DELIVERY_RECEIPT_OPTIONAL_PARAMS=true
SMSCID=SMPPSim
SIMULATE_VARIABLE_SUBMIT_SM_RESPONSE_TIMES=false
@@ -0,0 +1,51 @@
SMPP_PORT=2775
SMPP_CONNECTION_HANDLERS=20
CONNECTION_HANDLER_CLASS=com.seleniumsoftware.SMPPSim.StandardConnectionHandler
PROTOCOL_HANDLER_CLASS=com.seleniumsoftware.SMPPSim.StandardProtocolHandler
LIFE_CYCLE_MANAGER=com.seleniumsoftware.SMPPSim.LifeCycleManager
MESSAGE_STATE_CHECK_FREQUENCY=200
MAX_TIME_ENROUTE=300
DELAY_DELIVERY_RECEIPTS_BY=0
PERCENTAGE_THAT_TRANSITION=100
PERCENTAGE_DELIVERED=100
PERCENTAGE_UNDELIVERABLE=0
PERCENTAGE_ACCEPTED=0
PERCENTAGE_REJECTED=0
DISCARD_FROM_QUEUE_AFTER=60000
HTTP_PORT=8884
HTTP_THREADS=4
DOCROOT=www
AUTHORISED_FILES=/css/style.css,/index.htm,/inject_mo.htm,/favicon.ico,/images/logo.gif,/images/dots.gif,/user-guide.htm,/images/homepage.gif,/images/inject_mo.gif
INJECT_MO_PAGE=/inject_mo.htm
SYSTEM_IDS=smppclient1,smppclient2,smppclient3
PASSWORDS=password,password,password
OUTBIND_ENABLED=false
OUTBIND_ESME_IP_ADDRESS=127.0.0.1
OUTBIND_ESME_PORT=2776
OUTBIND_ESME_SYSTEMID=smppclient1
OUTBIND_ESME_PASSWORD=password
DELIVERY_MESSAGES_PER_MINUTE=0
DELIVER_MESSAGES_FILE=mo/deliver_messages.csv
LOOPBACK=false
ESME_TO_ESME=false
OUTBOUND_QUEUE_MAX_SIZE=1000
INBOUND_QUEUE_MAX_SIZE=1000
DELAYED_INBOUND_QUEUE_PROCESSING_PERIOD=60
DELAYED_INBOUND_QUEUE_MAX_ATTEMPTS=50
DECODE_PDUS_IN_LOG=true
CAPTURE_SME_BINARY=false
CAPTURE_SME_BINARY_TO_FILE=sme_binary.capture
CAPTURE_SMPPSIM_BINARY=false
CAPTURE_SMPPSIM_BINARY_TO_FILE=smppsim_binary.capture
CAPTURE_SME_DECODED=false
CAPTURE_SME_DECODED_TO_FILE=sme_decoded.capture
CAPTURE_SMPPSIM_DECODED=false
CAPTURE_SMPPSIM_DECODED_TO_FILE=smppsim_decoded.capture
CALLBACK=false
CALLBACK_ID=SIM1
CALLBACK_TARGET_HOST=localhost
CALLBACK_PORT=3333
DELIVER_SM_INCLUDES_USSD_SERVICE_OP=false
DELIVERY_RECEIPT_OPTIONAL_PARAMS=false
SMSCID=SMPPSim
SIMULATE_VARIABLE_SUBMIT_SM_RESPONSE_TIMES=false
@@ -0,0 +1,51 @@
SMPP_PORT=2775
SMPP_CONNECTION_HANDLERS=20
CONNECTION_HANDLER_CLASS=com.seleniumsoftware.SMPPSim.StandardConnectionHandler
PROTOCOL_HANDLER_CLASS=com.seleniumsoftware.SMPPSim.StandardProtocolHandler
LIFE_CYCLE_MANAGER=com.seleniumsoftware.SMPPSim.LifeCycleManager
MESSAGE_STATE_CHECK_FREQUENCY=200
MAX_TIME_ENROUTE=300
DELAY_DELIVERY_RECEIPTS_BY=0
PERCENTAGE_THAT_TRANSITION=100
PERCENTAGE_DELIVERED=100
PERCENTAGE_UNDELIVERABLE=0
PERCENTAGE_ACCEPTED=0
PERCENTAGE_REJECTED=0
DISCARD_FROM_QUEUE_AFTER=60000
HTTP_PORT=8884
HTTP_THREADS=4
DOCROOT=www
AUTHORISED_FILES=/css/style.css,/index.htm,/inject_mo.htm,/favicon.ico,/images/logo.gif,/images/dots.gif,/user-guide.htm,/images/homepage.gif,/images/inject_mo.gif
INJECT_MO_PAGE=/inject_mo.htm
SYSTEM_IDS=smppclient1,smppclient2,smppclient3
PASSWORDS=password,password,password
OUTBIND_ENABLED=false
OUTBIND_ESME_IP_ADDRESS=127.0.0.1
OUTBIND_ESME_PORT=2776
OUTBIND_ESME_SYSTEMID=smppclient1
OUTBIND_ESME_PASSWORD=password
DELIVERY_MESSAGES_PER_MINUTE=0
DELIVER_MESSAGES_FILE=mo/deliver_messages.csv
LOOPBACK=false
ESME_TO_ESME=false
OUTBOUND_QUEUE_MAX_SIZE=1000
INBOUND_QUEUE_MAX_SIZE=1000
DELAYED_INBOUND_QUEUE_PROCESSING_PERIOD=60
DELAYED_INBOUND_QUEUE_MAX_ATTEMPTS=50
DECODE_PDUS_IN_LOG=true
CAPTURE_SME_BINARY=false
CAPTURE_SME_BINARY_TO_FILE=sme_binary.capture
CAPTURE_SMPPSIM_BINARY=false
CAPTURE_SMPPSIM_BINARY_TO_FILE=smppsim_binary.capture
CAPTURE_SME_DECODED=false
CAPTURE_SME_DECODED_TO_FILE=sme_decoded.capture
CAPTURE_SMPPSIM_DECODED=false
CAPTURE_SMPPSIM_DECODED_TO_FILE=smppsim_decoded.capture
CALLBACK=false
CALLBACK_ID=SIM1
CALLBACK_TARGET_HOST=localhost
CALLBACK_PORT=3333
DELIVER_SM_INCLUDES_USSD_SERVICE_OP=false
DELIVERY_RECEIPT_OPTIONAL_PARAMS=true
SMSCID=SMPPSim
SIMULATE_VARIABLE_SUBMIT_SM_RESPONSE_TIMES=false
@@ -0,0 +1,51 @@
SMPP_PORT=2775
SMPP_CONNECTION_HANDLERS=20
CONNECTION_HANDLER_CLASS=com.seleniumsoftware.SMPPSim.StandardConnectionHandler
PROTOCOL_HANDLER_CLASS=com.seleniumsoftware.SMPPSim.StandardProtocolHandler
LIFE_CYCLE_MANAGER=com.seleniumsoftware.SMPPSim.LifeCycleManager
MESSAGE_STATE_CHECK_FREQUENCY=200
MAX_TIME_ENROUTE=300
DELAY_DELIVERY_RECEIPTS_BY=0
PERCENTAGE_THAT_TRANSITION=100
PERCENTAGE_DELIVERED=0
PERCENTAGE_UNDELIVERABLE=100
PERCENTAGE_ACCEPTED=0
PERCENTAGE_REJECTED=0
DISCARD_FROM_QUEUE_AFTER=60000
HTTP_PORT=8884
HTTP_THREADS=4
DOCROOT=www
AUTHORISED_FILES=/css/style.css,/index.htm,/inject_mo.htm,/favicon.ico,/images/logo.gif,/images/dots.gif,/user-guide.htm,/images/homepage.gif,/images/inject_mo.gif
INJECT_MO_PAGE=/inject_mo.htm
SYSTEM_IDS=smppclient1,smppclient2,smppclient3
PASSWORDS=password,password,password
OUTBIND_ENABLED=false
OUTBIND_ESME_IP_ADDRESS=127.0.0.1
OUTBIND_ESME_PORT=2776
OUTBIND_ESME_SYSTEMID=smppclient1
OUTBIND_ESME_PASSWORD=password
DELIVERY_MESSAGES_PER_MINUTE=0
DELIVER_MESSAGES_FILE=mo/deliver_messages.csv
LOOPBACK=false
ESME_TO_ESME=false
OUTBOUND_QUEUE_MAX_SIZE=1000
INBOUND_QUEUE_MAX_SIZE=1000
DELAYED_INBOUND_QUEUE_PROCESSING_PERIOD=60
DELAYED_INBOUND_QUEUE_MAX_ATTEMPTS=50
DECODE_PDUS_IN_LOG=true
CAPTURE_SME_BINARY=false
CAPTURE_SME_BINARY_TO_FILE=sme_binary.capture
CAPTURE_SMPPSIM_BINARY=false
CAPTURE_SMPPSIM_BINARY_TO_FILE=smppsim_binary.capture
CAPTURE_SME_DECODED=false
CAPTURE_SME_DECODED_TO_FILE=sme_decoded.capture
CAPTURE_SMPPSIM_DECODED=false
CAPTURE_SMPPSIM_DECODED_TO_FILE=smppsim_decoded.capture
CALLBACK=false
CALLBACK_ID=SIM1
CALLBACK_TARGET_HOST=localhost
CALLBACK_PORT=3333
DELIVER_SM_INCLUDES_USSD_SERVICE_OP=false
DELIVERY_RECEIPT_OPTIONAL_PARAMS=true
SMSCID=SMPPSim
SIMULATE_VARIABLE_SUBMIT_SM_RESPONSE_TIMES=false
+51
View File
@@ -0,0 +1,51 @@
SMPP_PORT=2775
SMPP_CONNECTION_HANDLERS=20
CONNECTION_HANDLER_CLASS=com.seleniumsoftware.SMPPSim.StandardConnectionHandler
PROTOCOL_HANDLER_CLASS=com.seleniumsoftware.SMPPSim.StandardProtocolHandler
LIFE_CYCLE_MANAGER=com.seleniumsoftware.SMPPSim.LifeCycleManager
MESSAGE_STATE_CHECK_FREQUENCY=200
MAX_TIME_ENROUTE=300
DELAY_DELIVERY_RECEIPTS_BY=0
PERCENTAGE_THAT_TRANSITION=100
PERCENTAGE_DELIVERED=100
PERCENTAGE_UNDELIVERABLE=0
PERCENTAGE_ACCEPTED=0
PERCENTAGE_REJECTED=0
DISCARD_FROM_QUEUE_AFTER=60000
HTTP_PORT=8884
HTTP_THREADS=4
DOCROOT=www
AUTHORISED_FILES=/css/style.css,/index.htm,/inject_mo.htm,/favicon.ico,/images/logo.gif,/images/dots.gif,/user-guide.htm,/images/homepage.gif,/images/inject_mo.gif
INJECT_MO_PAGE=/inject_mo.htm
SYSTEM_IDS=smppclient1,smppclient2,smppclient3
PASSWORDS=password,password,password
OUTBIND_ENABLED=false
OUTBIND_ESME_IP_ADDRESS=127.0.0.1
OUTBIND_ESME_PORT=2776
OUTBIND_ESME_SYSTEMID=smppclient1
OUTBIND_ESME_PASSWORD=password
DELIVERY_MESSAGES_PER_MINUTE=0
DELIVER_MESSAGES_FILE=mo/deliver_messages.csv
LOOPBACK=true
ESME_TO_ESME=false
OUTBOUND_QUEUE_MAX_SIZE=1000
INBOUND_QUEUE_MAX_SIZE=1000
DELAYED_INBOUND_QUEUE_PROCESSING_PERIOD=60
DELAYED_INBOUND_QUEUE_MAX_ATTEMPTS=50
DECODE_PDUS_IN_LOG=true
CAPTURE_SME_BINARY=false
CAPTURE_SME_BINARY_TO_FILE=sme_binary.capture
CAPTURE_SMPPSIM_BINARY=false
CAPTURE_SMPPSIM_BINARY_TO_FILE=smppsim_binary.capture
CAPTURE_SME_DECODED=false
CAPTURE_SME_DECODED_TO_FILE=sme_decoded.capture
CAPTURE_SMPPSIM_DECODED=false
CAPTURE_SMPPSIM_DECODED_TO_FILE=smppsim_decoded.capture
CALLBACK=false
CALLBACK_ID=SIM1
CALLBACK_TARGET_HOST=localhost
CALLBACK_PORT=3333
DELIVER_SM_INCLUDES_USSD_SERVICE_OP=false
DELIVERY_RECEIPT_OPTIONAL_PARAMS=true
SMSCID=SMPPSim
SIMULATE_VARIABLE_SUBMIT_SM_RESPONSE_TIMES=false
+774
View File
@@ -0,0 +1,774 @@
import assert from 'node:assert/strict';
import test, { describe } from 'node:test';
import type { Dlr } from '../src/dlr.ts';
import type { EncodingName } from '../src/defs/encodings.ts';
import type { MessageDlr } from '../src/dlr-merger.ts';
import type { PduObject } from '../src/pdu.ts';
import type { SendSmsResult } from '../src/send-sms.ts';
import type { Session } from '../src/session.ts';
import type { Sms } from '../src/sms.ts';
import { client } from '../src/client.ts';
import { closeAfter } from '../test/teardown.ts';
import { consts } from '../src/defs/constants.ts';
import { paramText } from '../src/defs/types.ts';
import { server } from '../src/server.ts';
const PEER_HOST = process.env.PEER_HOST ?? 'smppsim';
const PEER_PORT = Number(process.env.PEER_PORT ?? '2775');
const TEXTDLR_HOST = process.env.TEXTDLR_HOST ?? 'smppsim-textdlr';
const TRANSITION_HOST = process.env.TRANSITION_HOST ?? 'smppsim-transition';
const UNDELIV_HOST = process.env.UNDELIV_HOST ?? 'smppsim-undeliv';
const REJECTED_HOST = process.env.REJECTED_HOST ?? 'smppsim-rejected';
const ACCEPTED_HOST = process.env.ACCEPTED_HOST ?? 'smppsim-accepted';
const DELAYED_HOST = process.env.DELAYED_HOST ?? 'smppsim-delayed';
const QUEUEFULL_HOST = process.env.QUEUEFULL_HOST ?? 'smppsim-queuefull';
const OUTBIND_HOST = process.env.OUTBIND_HOST ?? 'smppsim-outbind';
const OUTBIND_HTTP_PORT = Number(process.env.OUTBIND_HTTP_PORT ?? '8884');
const OUR_OUTBIND_PORT = Number(process.env.OUR_OUTBIND_PORT ?? '2776');
const USERNAME = 'smppclient1';
const PASSWORD = 'password';
const FROM = '46701113311';
const TO = '46709771337';
function delay(ms: number): Promise<void> {
return new Promise(resolve => { setTimeout(resolve, ms); });
}
/** Polls until `get()` stops returning undefined, or the budget runs out. */
async function waitFor<T>(get: () => T | undefined, budget = 3000): Promise<T | undefined> {
const deadline = Date.now() + budget;
let value = get();
while (value === undefined && Date.now() < deadline) {
await delay(20);
value = get();
}
return value;
}
async function bind(host: string, options: Parameters<typeof client>[0] = {}): ReturnType<typeof client> {
return client({ host, password: PASSWORD, port: PEER_PORT, username: USERNAME, ...options });
}
/**
* A GSM-7 message sized to a target septet count, always carrying the three extension-table
* characters (€, [, ]) that cost two septets each via an ESC prefix.
*/
function gsmFiller(targetSeptets: number): string {
const extension = '€[]';
return extension + 'a'.repeat(targetSeptets - extension.length * 2);
}
/** Delivery-receipt fields the wire actually carried, alongside the parsed Dlr. */
type Received = { dlr: Dlr; pduObj: PduObject };
function collectDlrs(session: Session): Received[] {
const received: Received[] = [];
session.on('dlr', (dlr, pduObj) => { received.push({ dlr, pduObj }); });
return received;
}
function collectSms(session: Session): Sms[] {
const collected: Sms[] = [];
session.on('sms', sms => { collected.push(sms); });
return collected;
}
const DLR_RETRY_BUDGET_MS = 3000;
const DLR_MAX_ATTEMPTS = 10;
/**
* Two concurrent submit_sm's on the same connection - which is how this library always sends a
* multi-segment message's segments, never one after the previous one's response - sometimes make
* SMPPSim interleave two PDUs it writes back without synchronising, corrupting one on the wire
* (tshark's own malformed/expert counts confirm it; see findings/02-smppsim.md). A corrupted
* receipt still reaches `dlr` with an empty or partial body. Sequential, awaited submit_sm's never
* see this.
*/
function dlrLooksIntact(received: Received | undefined): received is Received {
return received?.dlr.receipt?.stat !== undefined
&& received.pduObj.tlvs.receipted_message_id?.tagValue !== undefined
&& received.pduObj.tlvs.message_state?.tagValue !== undefined;
}
/**
* Resends a fresh message until one attempt's segments are every one matched by an intact `dlr`.
* Retrying works around the peer+library interaction above without hiding it: a scenario only
* fails here if it keeps missing well past what that alone explains.
*/
async function sendUntilAllDlrsArrive(
session: Session,
dlrs: Received[],
message: string,
encoding?: EncodingName,
): Promise<string[]> {
for (let attempt = 0; attempt < DLR_MAX_ATTEMPTS; attempt++) {
const sent = await session.sendSms({
dlr: true,
from: FROM,
message,
to: TO,
...(encoding ? { encoding } : {}),
});
assert.equal(sent.err, undefined);
const complete = await waitFor(
() => (sent.smsIds.every(id => dlrLooksIntact(dlrs.find(r => r.dlr.smsId === id))) ? true : undefined),
DLR_RETRY_BUDGET_MS,
);
if (complete) return sent.smsIds;
}
throw new Error(`no attempt got an intact DLR for every segment within ${String(DLR_MAX_ATTEMPTS)} tries`);
}
/** As above, but also waits for the loopback deliver_sm(s) to reassemble into the original text. */
async function sendUntilComplete(
session: Session,
dlrs: Received[],
sms: Sms[],
message: string,
encoding?: EncodingName,
): Promise<{ reassembled: Sms; smsIds: string[] }> {
for (let attempt = 0; attempt < DLR_MAX_ATTEMPTS; attempt++) {
const sent = await session.sendSms({
dlr: true,
from: FROM,
message,
to: TO,
...(encoding ? { encoding } : {}),
});
assert.equal(sent.err, undefined);
const complete = await waitFor(() => {
const allIntact = sent.smsIds.every(id => dlrLooksIntact(dlrs.find(r => r.dlr.smsId === id)));
const reassembled = sms.find(s => s.message === message);
return allIntact && reassembled ? { reassembled } : undefined;
}, DLR_RETRY_BUDGET_MS);
if (complete) return { reassembled: complete.reassembled, smsIds: sent.smsIds };
}
throw new Error(`no attempt got both an intact DLR per segment and a loopback reassembly within ${String(DLR_MAX_ATTEMPTS)} tries`);
}
describe('smppsim - C3+C7 long MT, receipts and loopback reassembly', () => {
const cases: { expectedSegments: number; label: string; message: string }[] = [
{ expectedSegments: 1, label: 'single-segment GSM with extension chars', message: gsmFiller(100) },
{ expectedSegments: 2, label: '2-segment GSM with extension chars', message: gsmFiller(200) },
{ expectedSegments: 3, label: '3-segment GSM with extension chars', message: gsmFiller(400) },
{ expectedSegments: 10, label: '10-segment GSM with extension chars', message: gsmFiller(1450) },
];
for (const testCase of cases) {
test(testCase.label, async t => {
const { err, session } = await bind(PEER_HOST);
assert.equal(err, undefined);
assert.ok(session);
closeAfter(t, session);
const dlrs = collectDlrs(session);
const sms = collectSms(session);
const { reassembled, smsIds } = await sendUntilComplete(session, dlrs, sms, testCase.message);
assert.equal(smsIds.length, testCase.expectedSegments);
for (const id of smsIds) {
const received = dlrs.find(r => r.dlr.smsId === id);
assert.ok(received);
assert.equal(received.dlr.statusMsg, 'DELIVERED');
// TLVs and body both present, and agree - "TLV wins" is unobservable when they
// match, which is what a well-behaved SMSC gives you (C3).
assert.equal(received.pduObj.tlvs.receipted_message_id?.tagValue, id);
assert.equal(received.pduObj.tlvs.message_state?.tagValue, consts.MESSAGE_STATE.DELIVERED);
assert.equal(received.dlr.receipt?.stat, 'DELIVRD');
}
assert.equal((await reassembled.sendResp()).err, undefined);
});
}
test('2-segment UCS2 with 一 and an emoji: TLVs stay right, the receipt body is corrupted', async t => {
const { err, session } = await bind(PEER_HOST);
assert.equal(err, undefined);
assert.ok(session);
closeAfter(t, session);
const dlrs = collectDlrs(session);
const sms = collectSms(session);
const message = `一😀${'x'.repeat(70)}`;
let smsIds: string[] | undefined;
let reassembled: Sms | undefined;
for (let attempt = 0; attempt < DLR_MAX_ATTEMPTS && !reassembled; attempt++) {
const sent: SendSmsResult = await session.sendSms(
{ dlr: true, encoding: 'UCS2', from: FROM, message, to: TO },
);
assert.equal(sent.err, undefined);
assert.equal(sent.smsIds.length, 2);
const complete = await waitFor(() => {
const tlvsIntact = sent.smsIds.every(id => {
const received = dlrs.find(r => r.dlr.smsId === id);
return received?.pduObj.tlvs.receipted_message_id?.tagValue !== undefined
&& received.pduObj.tlvs.message_state?.tagValue !== undefined;
});
const found = sms.find(s => s.message === message);
return tlvsIntact && found ? { found } : undefined;
}, DLR_RETRY_BUDGET_MS);
if (complete) {
smsIds = sent.smsIds;
reassembled = complete.found;
}
}
assert.ok(smsIds);
assert.ok(reassembled, 'expected the loopback deliver_sm(s), unaffected by the defect, to reassemble');
for (const id of smsIds) {
const received = dlrs.find(r => r.dlr.smsId === id);
assert.ok(received);
// The TLVs are typed fields, unaffected by the defect below.
assert.equal(received.dlr.statusMsg, 'DELIVERED');
assert.equal(received.pduObj.tlvs.receipted_message_id?.tagValue, id);
assert.equal(received.pduObj.tlvs.message_state?.tagValue, consts.MESSAGE_STATE.DELIVERED);
// Defect (see findings/02-smppsim.md): SMPPSim's delivery receipt echoes the
// original submit_sm's data_coding (8, UCS2) but writes its short_message as plain
// ASCII text. pdu.ts decodes short_message for any non-UDH PDU using that same
// data_coding at parse time, so the ASCII receipt bytes are read back as UCS2 -
// every "stat:"/"id:" field becomes unrecoverable CJK-range garbage.
assert.equal(received.dlr.receipt?.stat, undefined);
}
assert.equal((await reassembled.sendResp()).err, undefined);
});
});
describe('smppsim-textdlr - C2 text-only receipts', () => {
test('no TLVs; dlr.smsId parsed from the body matches the submit_sm_resp id', async t => {
const { err, session } = await bind(TEXTDLR_HOST);
assert.equal(err, undefined);
assert.ok(session);
closeAfter(t, session);
const dlrs = collectDlrs(session);
const sent = await session.sendSms({ dlr: true, from: FROM, message: 'text-only dlr', to: TO });
assert.equal(sent.err, undefined);
assert.equal(sent.smsIds.length, 1);
const [smsId] = sent.smsIds;
assert.ok(smsId);
const received = await waitFor(() => dlrs.find(r => r.dlr.smsId === smsId));
assert.ok(received);
assert.equal(received.dlr.statusMsg, 'DELIVERED');
assert.equal(received.pduObj.tlvs.receipted_message_id, undefined);
assert.equal(received.pduObj.tlvs.message_state, undefined);
assert.equal(received.dlr.receipt?.id, smsId);
});
});
describe('smppsim-transition - C4 intermediate then final', () => {
test('an intermediate report arrives, but registered_delivery 0x11 never gets a final one', async t => {
const { err, session } = await bind(TRANSITION_HOST);
assert.equal(err, undefined);
assert.ok(session);
closeAfter(t, session);
const dlrs = collectDlrs(session);
const messageDlrs: unknown[] = [];
session.on('messageDlr', merged => { messageDlrs.push(merged); });
// registered_delivery 0x11: final (0x01) + intermediate (0x10). sendSms() only ever
// requests 0x01, so this scenario needs the raw passthrough - which also means
// dlrMerger.expect() is never called, so messageDlr cannot fire here (see findings).
const sent = await session.send({
cmdName: 'submit_sm',
params: {
data_coding: consts.ENCODING.ASCII,
destination_addr: TO,
registered_delivery: 0x11,
short_message: Buffer.from('transition test', 'latin1'),
source_addr: FROM,
},
});
assert.equal(sent.err, undefined);
assert.ok(sent.pduObj);
const messageId = paramText(sent.pduObj.params.message_id);
assert.notEqual(messageId, '');
const intermediate = await waitFor(() => dlrs.find(r => r.dlr.smsId === messageId));
assert.ok(intermediate, 'expected an intermediate report');
assert.equal(intermediate.dlr.intermediate, true);
assert.equal(intermediate.dlr.statusMsg, 'ENROUTE');
// SMPPSim's own user guide (v2.5 release notes): "Set to 0x11 for both intermediate
// notification and final delivery receipts". Its LifeCycleManager.setState() tests
// registered_delivery_flag == 1 or == 2 by exact equality rather than a bitmask, so 0x11
// (17) matches neither branch and no final receipt is ever queued - confirmed against
// source (see findings/02-smppsim.md). Recorded, not asserted as something to fix here.
const final = await waitFor(() => dlrs.find(r => r.dlr.smsId === messageId && !r.dlr.intermediate), 3000);
assert.equal(final, undefined);
assert.equal(messageDlrs.length, 0);
});
});
describe('smppsim single-state variants - C5 failure states', () => {
const variants: { expected: Dlr['statusMsg']; host: string; label: string }[] = [
{ expected: 'UNDELIVERABLE', host: UNDELIV_HOST, label: 'PERCENTAGE_UNDELIVERABLE=100' },
{ expected: 'REJECTED', host: REJECTED_HOST, label: 'PERCENTAGE_REJECTED=100' },
{ expected: 'ACCEPTED', host: ACCEPTED_HOST, label: 'PERCENTAGE_ACCEPTED=100' },
];
for (const variant of variants) {
test(`${variant.label} maps to ${variant.expected}`, async t => {
const { err, session } = await bind(variant.host);
assert.equal(err, undefined);
assert.ok(session);
closeAfter(t, session);
const dlrs = collectDlrs(session);
const sent = await session.sendSms({ dlr: true, from: FROM, message: 'failure state test', to: TO });
assert.equal(sent.err, undefined);
const [smsId] = sent.smsIds;
assert.ok(smsId);
const received = await waitFor(() => dlrs.find(r => r.dlr.smsId === smsId));
assert.ok(received);
assert.equal(received.dlr.statusMsg, variant.expected);
});
}
test('a 2-segment message: both segments report the same status; messageDlr never fires', async t => {
const { err, session } = await bind(UNDELIV_HOST);
assert.equal(err, undefined);
assert.ok(session);
closeAfter(t, session);
const dlrs = collectDlrs(session);
const messageDlrs: MessageDlr[] = [];
session.on('messageDlr', merged => { messageDlrs.push(merged); });
const smsIds = await sendUntilAllDlrsArrive(session, dlrs, gsmFiller(200));
assert.equal(smsIds.length, 2);
for (const id of smsIds) {
const received = dlrs.find(r => r.dlr.smsId === id);
assert.ok(received);
assert.equal(received.dlr.statusMsg, 'UNDELIVERABLE');
}
// SMPPSim assigns each segment its own, independent message_id rather than this
// library's own server's <base>-<n> convention, so DlrMerger can never recognise the
// pair as one message (README: "an SMSC that hands out unrelated ids per segment never
// fires it") - recorded here, not asserted as a defect.
await delay(500);
assert.equal(messageDlrs.length, 0);
});
});
describe('smppsim-delayed - C6 receipt delayed past a link drop', () => {
test('the merge survives a reconnect; the late receipt still reaches dlr', async t => {
const { err, session } = await bind(DELAYED_HOST, { reconnect: { maxDelay: 1000, minDelay: 200 } });
assert.equal(err, undefined);
assert.ok(session);
closeAfter(t, session);
const dlrs = collectDlrs(session);
const disconnected: true[] = [];
const reconnected: true[] = [];
session.on('disconnected', () => { disconnected.push(true); });
session.on('reconnected', () => { reconnected.push(true); });
const sendStart = Date.now();
const sent = await session.sendSms({ dlr: true, from: FROM, message: 'delayed dlr test', to: TO });
assert.equal(sent.err, undefined);
const [smsId] = sent.smsIds;
assert.ok(smsId);
await delay(300);
session.sock.destroy();
assert.ok(await waitFor(() => (disconnected.length > 0 ? true : undefined), 2000), 'expected disconnected');
assert.ok(await waitFor(() => (reconnected.length > 0 ? true : undefined), 4000), 'expected reconnected');
// DELAY_DELIVERY_RECEIPTS_BY (8000ms) is a floor, not the actual delay: DelayedDrQueue's
// own poll loop only wakes every 5000ms (hardcoded, not a props knob), so delivery can
// land up to ~13s after submission - confirmed against source and empirically.
const remaining = Math.max(1000, 16_000 - (Date.now() - sendStart));
const received = await waitFor(() => dlrs.find(r => r.dlr.smsId === smsId), remaining);
assert.ok(received, 'expected the delayed receipt to still arrive on the new link');
assert.equal(received.dlr.statusMsg, 'DELIVERED');
});
});
describe('smppsim - C11 bind refusal and reconnect backoff', () => {
test('wrong password on the very first bind: one attempt, no retry', async () => {
const refusals: { cmdStatus: unknown }[] = [];
const log = {
debug: () => undefined,
error: () => undefined,
info: (msg: string, metadata?: Record<string, boolean | number | string>) => {
if (msg === 'client - bind refused') refusals.push({ cmdStatus: metadata?.cmdStatus });
},
verbose: () => undefined,
warn: () => undefined,
};
const { err, session } = await bind(PEER_HOST, { log, password: 'wrong', reconnect: { maxDelay: 4000, minDelay: 1000 } });
assert.ok(err);
assert.equal(session, undefined);
await delay(2000);
assert.equal(refusals.length, 1, 'expected exactly one bind attempt, never a retry');
assert.equal(refusals[0]?.cmdStatus, 'ESME_RINVPASWD');
});
test('a closed port on the very first connect: one attempt, no retry', async () => {
const { err, session } = await bind(PEER_HOST, { port: 46775, reconnect: { maxDelay: 4000, minDelay: 1000 } });
assert.ok(err);
assert.equal(session, undefined);
});
test('a rebind refused after a live link drops: backs off, never floods', async t => {
const options: Parameters<typeof client>[0] = {
host: PEER_HOST,
password: PASSWORD,
port: PEER_PORT,
reconnect: { maxDelay: 4000, minDelay: 1000 },
username: USERNAME,
};
const { err, session } = await client(options);
assert.equal(err, undefined);
assert.ok(session);
closeAfter(t, session);
const disconnectedAt: number[] = [];
session.on('disconnected', () => { disconnectedAt.push(Date.now()); });
// Mutates the same object reference the reconnect loop's onConnected closure reads, so
// every rebind attempt from here on is refused - see interop-tests/findings/02-smppsim.md.
options.password = 'wrong-after-drop';
session.sock.destroy();
await delay(12_000);
assert.ok(disconnectedAt.length >= 2 && disconnectedAt.length <= 6, `expected a handful of attempts, got ${String(disconnectedAt.length)}`);
for (let i = 1; i < disconnectedAt.length; i++) {
const previous = disconnectedAt[i - 1];
const current = disconnectedAt[i];
assert.ok(previous !== undefined && current !== undefined);
assert.ok(current - previous >= 150, 'expected each retry to wait at least close to minDelay');
}
});
});
describe('smppsim-queuefull - C12 ESME_RMSGQFUL', () => {
test('refuses when the queue is full; the session stays bound; a later send succeeds once it drains', async t => {
const { err, session } = await bind(QUEUEFULL_HOST);
assert.equal(err, undefined);
assert.ok(session);
closeAfter(t, session);
const first = await session.sendSms({ from: FROM, message: 'occupies the one queue slot', to: TO });
assert.equal(first.err, undefined);
const second = await session.sendSms({ from: FROM, message: 'should be refused', to: TO });
assert.ok(second.err);
assert.match(second.err.message, /ESME_RMSGQFUL/);
const keepalive = await session.send({ cmdName: 'enquire_link' });
assert.equal(keepalive.err, undefined);
assert.ok(keepalive.pduObj);
assert.equal(keepalive.pduObj.cmdStatus, 'ESME_ROK');
const deadline = Date.now() + 5000;
let drained: Awaited<ReturnType<typeof session.sendSms>> | undefined;
while (!drained && Date.now() < deadline) {
const attempt = await session.sendSms({ from: FROM, message: 'after drain', to: TO });
if (!attempt.err) drained = attempt;
else await delay(100);
}
assert.ok(drained, 'expected a later send to succeed once the queue drained');
});
});
describe('smppsim - C13 maxOutstanding 1 with 10 parallel sends', () => {
test('every send is answered, in order, none lost', async t => {
const { err, session } = await bind(PEER_HOST, { maxOutstanding: 1 });
assert.equal(err, undefined);
assert.ok(session);
closeAfter(t, session);
const results = await Promise.all(
Array.from({ length: 10 }, async (_unused, index) => session.sendSms({
from: FROM,
message: `order test ${String(index)}`,
to: TO,
})),
);
const ids: number[] = [];
for (const result of results) {
assert.equal(result.err, undefined);
assert.equal(result.smsIds.length, 1);
const [smsId] = result.smsIds;
assert.ok(smsId);
ids.push(Number(smsId));
}
assert.equal(new Set(ids).size, ids.length, 'expected 10 distinct message ids, none lost or duplicated');
for (let i = 1; i < ids.length; i++) {
const previous = ids[i - 1];
const current = ids[i];
assert.ok(previous !== undefined && current !== undefined);
assert.ok(current > previous, 'expected ids in call order, proving maxOutstanding:1 serialised the sends');
}
});
});
describe('smppsim - C15 bind version negotiation', () => {
for (const interfaceVersion of [0x34, 0x50]) {
test(`interfaceVersion 0x${interfaceVersion.toString(16)}: SMPPSim's bind_resp never declares a version`, async t => {
const { err, session } = await bind(PEER_HOST, { interfaceVersion });
assert.equal(err, undefined);
assert.ok(session);
closeAfter(t, session);
// Confirmed from source (no BindXResp class ever sets sc_interface_version): SMPPSim
// never declares its own version, whatever we declared - so acceptsOptionalParams()
// reads it as pre-3.4, even though it happily sends us TLVs (see C3).
assert.equal(session.peerInterfaceVersion, 0x00);
assert.equal(session.acceptsOptionalParams(), false);
});
}
});
describe('smppsim - C17 encodings round trip over loopback', () => {
test('Latin-1 (å ä ö)', async t => {
const { err, session } = await bind(PEER_HOST);
assert.equal(err, undefined);
assert.ok(session);
closeAfter(t, session);
const sms = collectSms(session);
await session.sendSms({ encoding: 'LATIN1', from: FROM, message: 'å ä ö', to: TO });
const received = await waitFor(() => sms.find(s => s.message === 'å ä ö'));
assert.ok(received);
assert.equal(received.flash, false);
});
test('UCS-2', async t => {
const { err, session } = await bind(PEER_HOST);
assert.equal(err, undefined);
assert.ok(session);
closeAfter(t, session);
const sms = collectSms(session);
await session.sendSms({ encoding: 'UCS2', from: FROM, message: 'ucs2 round trip', to: TO });
const received = await waitFor(() => sms.find(s => s.message === 'ucs2 round trip'));
assert.ok(received);
assert.equal(received.flash, false);
});
test('flash (data_coding records the message-class group)', async t => {
const { err, session } = await bind(PEER_HOST);
assert.equal(err, undefined);
assert.ok(session);
closeAfter(t, session);
const sms = collectSms(session);
await session.sendSms({ flash: true, from: FROM, message: 'flash test', to: TO });
const received = await waitFor(() => sms.find(s => s.message === 'flash test'));
assert.ok(received);
assert.equal(received.flash, true);
assert.equal(received.pduObjs[0]?.params.data_coding, 0x10);
});
test('a raw submit_sm with data_coding 0xF0 is not read as flash', async t => {
const { err, session } = await bind(PEER_HOST);
assert.equal(err, undefined);
assert.ok(session);
closeAfter(t, session);
const sms = collectSms(session);
const body = 'message class test';
const sent = await session.send({
cmdName: 'submit_sm',
params: {
data_coding: 0xF0,
destination_addr: TO,
short_message: Buffer.from(body, 'latin1'),
source_addr: FROM,
},
});
assert.equal(sent.err, undefined);
const received = await waitFor(() => sms.find(s => s.message === body));
assert.ok(received, 'expected the 0xF0-coded loopback message to arrive');
assert.equal(received.flash, false);
});
test('a raw submit_sm with 8-bit binary and a UDH (esm_class 0x40)', async t => {
const { err, session } = await bind(PEER_HOST);
assert.equal(err, undefined);
assert.ok(session);
closeAfter(t, session);
const sms = collectSms(session);
// A UDH carrying no recognised concatenation IE (0x00/0x08): one element in GSM 03.40's
// reserved-for-future-use range (0x70), so Wireshark's gsm_sms_ud dissector - which
// validates the *typed* IEs' own lengths (0x01 "Special SMS Message Indication" must be
// 2 bytes, flagged malformed otherwise) - passes it through as opaque, unparsed data.
const udh = Buffer.from([0x03, 0x70, 0x01, 0xAA]);
const payload = Buffer.from([0xDE, 0xAD, 0xBE, 0xEF]);
const sent = await session.send({
cmdName: 'submit_sm',
params: {
data_coding: consts.ENCODING.BINARY,
destination_addr: TO,
esm_class: consts.ESM_CLASS.UDH_INDICATOR,
short_message: Buffer.concat([udh, payload]),
source_addr: FROM,
},
});
assert.equal(sent.err, undefined);
const expected = payload.toString('latin1');
const received = await waitFor(() => sms.find(s => s.message === expected));
assert.ok(received, 'expected the UDH-stripped, Latin-1-decoded payload to arrive unchanged');
assert.equal(received.flash, false);
assert.deepEqual(Buffer.from(received.message, 'latin1'), payload);
});
});
describe('smppsim-outbind - C18 SMSC-initiated outbind', () => {
test('arrives with no bogus response PDU; sessionError names it', async t => {
const { err, server: smpp } = await server({ port: OUR_OUTBIND_PORT });
assert.equal(err, undefined);
assert.ok(smpp);
closeAfter(t, smpp);
const incoming: PduObject[] = [];
const sessionErrors: Error[] = [];
const closes: true[] = [];
smpp.on('session', session => {
session.on('incomingPduObj', pduObj => { incoming.push(pduObj); });
session.on('sessionError', sessionError => { sessionErrors.push(sessionError); });
session.on('close', () => { closes.push(true); });
});
const injected = await fetch(`http://${OUTBIND_HOST}:${String(OUTBIND_HTTP_PORT)}/inject_mo?${
new URLSearchParams({
destination_addr: TO,
short_message: 'trigger outbind',
source_addr: FROM,
}).toString()
}`);
assert.equal(injected.status, 200);
const outbindPdu = await waitFor(() => incoming.find(pduObj => pduObj.cmdName === 'outbind'), 10_000);
assert.ok(outbindPdu, 'expected SMPPSim to connect to our server() and send outbind');
assert.equal(outbindPdu.params.system_id, 'smppclient1');
const sessionError = await waitFor(() => sessionErrors[0], 2000);
assert.ok(sessionError, 'expected sessionError: outbind has no response command to send back');
assert.match(sessionError.message, /outbind/);
// Recorded, not asserted either way (the task: "record, do not judge"): SMPPSim's own
// outbind() closes its end right after writing, before waiting for anything back.
await waitFor(() => (closes.length > 0 ? true : undefined), 1000);
});
});