diff --git a/README.md b/README.md index 416d7ba..eb2ab2a 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,8 @@ Every one is optional. | `host` / `port` | `localhost` / `2775` | Where to connect. | | `username` / `password` | `user` / `pass` | Bind credentials (`system_id` and `password`). | | `bindType` | `transceiver` | `transceiver`, `transmitter` or `receiver`. | -| `systemType`, `addressRange`, `addrTon`, `addrNpi`, `interfaceVersion` | `''`, `''`, `0`, `0`, `0x50` | The remaining bind fields, for operators that require them. | +| `interfaceVersion` | `0x34` | The SMPP version declared at bind. `0x50` for an SMSC that requires SMPP 5.0. | +| `systemType`, `addressRange`, `addrTon`, `addrNpi` | `''`, `''`, `0`, `0` | The remaining bind fields, for operators that require them. | | `tls` | `false` | `true` for defaults, or a `tls.ConnectionOptions` object for a private CA or a client certificate. | | `enquireLinkInterval` | `20000` | How often to send `enquire_link` on a quiet link. | | `responseTimeout` | `30000` | How long to wait for a response before giving up on it. | @@ -275,8 +276,9 @@ have worked around any of these, remove the workaround: reported the full length, so it went out corrupt. In UCS2 that is any message ending in a character like δΈ€ (U+4E00), which made the bug routine for CJK text. - Short or malformed PDUs threw out of the codec instead of being reported as a parse failure. -- Binds now declare `interface_version` 0x50. 0.4.0 declared 0x00, because the default in its own - command table was never applied. Pass `interfaceVersion: 0x34` to bind as SMPP 3.4. +- Binds now declare `interface_version` 0x34. 0.4.0 declared 0x00, which tells the SMSC the ESME + speaks SMPP 3.3 or earlier β€” and a spec-following SMSC then withholds every optional parameter, + including the TLVs delivery receipts are carried in. - `submit_multi` was missing its `sm_length` field, so its `short_message` never round-tripped. The corrected framing is cross-checked against [node-smpp](https://github.com/farhadi/node-smpp), an diff --git a/src/client.ts b/src/client.ts index 80b5c53..f74b88f 100644 --- a/src/client.ts +++ b/src/client.ts @@ -33,8 +33,8 @@ const defaults = { bindType: 'transceiver', enquireLinkInterval: 20_000, host: 'localhost', - /** SMPP 5.0. 0.4.0 declared 0x00 because its per-parameter default was never applied. */ - interfaceVersion: 0x50, + /** SMPP 3.4. The 3.4 spec reserves every value above it, so 0x50 is undefined to a 3.4 SMSC. */ + interfaceVersion: 0x34, password: 'pass', port: 2775, username: 'user', diff --git a/test/interop.test.ts b/test/interop.test.ts index 05ac18c..b6c2b1a 100644 --- a/test/interop.test.ts +++ b/test/interop.test.ts @@ -69,7 +69,7 @@ describe('our encoder against the reference parser', () => { const parsed = referenceParse(ourBuffer({ cmdName: 'bind_transceiver', params: { - interface_version: 0x50, + interface_version: 0x34, password: 'bar', system_id: 'foo', system_type: 'smpp', @@ -80,7 +80,7 @@ describe('our encoder against the reference parser', () => { assert.equal(parsed.command, 'bind_transceiver'); assert.equal(parsed.system_id, 'foo'); assert.equal(parsed.password, 'bar'); - assert.equal(parsed.interface_version, 0x50); + assert.equal(parsed.interface_version, 0x34); }); test('a deliver_sm carrying delivery receipt TLVs', () => { diff --git a/test/session.test.ts b/test/session.test.ts index 8972e62..a012143 100644 --- a/test/session.test.ts +++ b/test/session.test.ts @@ -5,6 +5,7 @@ import type { Session } from '../src/session.ts'; import type { Sms } from '../src/sms.ts'; import type { SmppServer } from '../src/server.ts'; import { client } from '../src/client.ts'; +import { isCommand } from '../src/pdu.ts'; import { server } from '../src/server.ts'; async function startServer(options: Parameters[0] = {}): Promise { @@ -93,6 +94,30 @@ describe('bind', () => { assert.equal(await responded, '00000010800000150000000400000001'); await smpp.close(); }); + + test('declares SMPP 3.4 by default and the version the caller asks for', async () => { + const smpp = await startServer(); + const declared: (number | undefined)[] = []; + + smpp.on('session', session => { + session.on('incomingPduObj', pduObj => { + if (isCommand(pduObj, 'bind_transceiver')) { + declared.push(pduObj.params.interface_version); + } + }); + }); + + const { session: byDefault } = await connect(smpp); + const { session: asFive } = await connect(smpp, { interfaceVersion: 0x50 }); + + assert.ok(byDefault); + assert.ok(asFive); + assert.deepEqual(declared, [0x34, 0x50]); + + await byDefault.unbind(); + await asFive.unbind(); + await smpp.close(); + }); }); describe('sending', () => { diff --git a/todo.md b/todo.md index 8c63316..367d212 100644 --- a/todo.md +++ b/todo.md @@ -5,7 +5,7 @@ rules there constrain every item below. ## Status -The rewrite is **feature complete and green**: 136 tests, lint and typecheck clean, verified on Node +The rewrite is **feature complete and green**: 137 tests, lint and typecheck clean, verified on Node 18, 20, 22 and 24. What is left is release work and a few things worth adding before or after 1.0.0. ```bash @@ -62,10 +62,6 @@ Every defect listed in the AGENTS.md table has a regression test naming the beha ## Before publishing 1.0.0 -- [ ] **Decide the `interface_version` default.** Binds now declare 0x50, which is what 0.4.0's own - command table intended but never applied β€” it actually sent 0x00. 0x34 (SMPP 3.4) is the more - conservative choice and is what most SMSCs implement. Flagged to the maintainer; the README - documents 0x50 and the `interfaceVersion` option overrides it either way. - [ ] Create the `@larvit/smpp` package on npm and add `NPM_TOKEN` to the repository secrets, which `.github/workflows/release.yaml` needs. - [ ] Tag `v1.0.0` to publish. @@ -78,6 +74,10 @@ Every defect listed in the AGENTS.md table has a regression test naming the beha - [ ] **In-flight sends across a reconnect.** They currently fail with "Session closed before a response arrived" and the caller retries. Re-queueing them automatically would be friendlier but risks duplicate delivery, so it needs a decision before it is built. +- [ ] **The server never returns the `sc_interface_version` TLV.** The 3.4 spec has an ESME read its + absence from a bind response as "this SMSC does not support optional parameters", and ours + does send DLR TLVs. Returning it has to be conditional: a peer that declared below 0x34 must + not be sent optional parameters at all. - [ ] **TLS is untested.** The code path is right (`tls.connect` / `tls.createServer`, options passed through) but no test exercises a handshake. Needs a self-signed certificate fixture. - [ ] **`submit_multi` and the broadcast commands** encode and decode, but nothing exercises them