Commit Graph

37 Commits

Author SHA1 Message Date
lilleman 18de565aad Reassemble a concatenated message segmented with the sar_* TLVs (#91)
* Regression tests for inbound sar_* concatenation

* Reassemble a concatenated message segmented with the sar_* TLVs

* Record the sar_* fix in the jasmin and Java-client findings

* Name the sar_* TLVs in the refusal, and separate group keys the addresses cannot forge

* Name the field a refused segment got wrong, and key groups on a separator no address holds

* Bound the new session tests, and derive Concat from the UDH fields
2026-09-06 21:03:14 +02:00
lilleman 30eedc81ee Let a signal abort a send queued behind a full send window (#90)
* Regression tests for aborting a send queued behind a full window

* Let a signal abort a send queued behind a full send window

* Log a queued send, tighten its regression tests and settle the decision record

* Take the send window's queue off an array so an abort cannot stall the loop

---------

Co-authored-by: Mikael Göransson <mikael.goransson@timewave.se>
2026-09-06 18:17:01 +02:00
lilleman 039951e69b Refuse a PDU whose optional parameters do not end on command_length (#87)
* Regression tests for a truncated TLV tail refused rather than accepted

* Refuse a PDU whose optional parameters do not end on command_length

* Assert the bare TLV header refusal against jsmpp instead of recording it as a defect

* Note the truncated TLV tail defect as fixed in the java-client findings

* Derive the padding position, share the bare TLV fixture and trim the decision record

* Regression tests for a PDU whose trailing C-Octet String a peer left out

* An absent trailing C-Octet String consumes no octet, so a bodyless PDU still parses

* Bound the TLV loop by the buffer it was given rather than a second spelling of its length

* Answer the stability review's questions in the record and pin the array contract
2026-09-06 18:01:35 +02:00
lilleman bcbb042c1b Give server() consumers the onRequest hook the docs promise (#86)
* Regression tests for an onRequest hook on server()

* Compose the application's onRequest hook into server() past bind

* Document the server's onRequest hook and record the composition decision

* Pin that a re-bind and a pre-bind unbind never reach the hook, and that a non-function hook is refused at startup

* Keep every bind and all pre-bind traffic out of the application's hook

* Make the hook's published promises exact, and record the src layout deferral

* Trim the record to what the tests do not already forbid

* Pin that nothing answers a request whose hook failed

* Answer nothing for a request whose hook failed, on both surfaces alike

* State the hook's failure policy as it now behaves

* Name the request a failed handler left unanswered, and say what a wedged hook reports
2026-09-06 17:20:45 +02:00
lilleman 184d1dc7af Read a body from message_payload and accept data_sm (#84)
* Regression tests for a body in message_payload and for data_sm

* Read a body from message_payload and accept data_sm

* Assert the fixed message_payload and data_sm behaviour against Jasmin

* Record the message_payload and data_sm fixes in the Jasmin findings

* Read an inbound data_sm as the direction it travelled, and export messageOctets

* Refuse a segment with the code its stand-in command defines

* Name the stand-in the refusal status is read from
2026-09-06 05:59:27 +02:00
lilleman 5b7b563dc2 Answer every segment of an inbound concatenated message as it arrives (#83)
* Regression tests for answering every inbound segment as it arrives

* Answer every segment of an inbound concatenated message as it arrives

* Record the segment-by-segment answer in AGENTS.md and README

* Regression tests for an empty message_id on deliver_sm_resp

* Answer a deliver_sm with the empty message_id SMPP 3.4 makes it

* Record Jasmin's refusal of a deliver_sm_resp message_id

* Mark the Jasmin multipart deadlock fixed

* Regression tests for the architecture review's findings

* Give the segment id notation an owner, and every segment a status

* Correct what the ids reach and what a lost group tells the application

* Regression tests for a group lost to its own octet overrun

* Report a group lost to its own overrun, and refuse by the command it arrived on

* Keep the docs true about what a segment is answered with

* Count only the answered segments of a group lost to an overrun

* Report only what a lost group cost, and say which cap bit
2026-09-06 04:16:11 +02:00
lilleman db02f0058d Tell a refused PDU apart from a session that failed (#82)
* Regression tests for telling a refused PDU from a failed session

* Export PduRefusedError so a refusal can be told from a session failure

* Document telling a refused PDU from a session failure

* Share the malformed-PDU fixtures and pin that a dead stream is no refusal

* Say once what a refusal is, and why the header type is published

* Fold the refused-response test into the one that already staged it

* Drop the refusal-rate claim the interop finding does not support

* Build a raw PDU one way, sequence number included

* Follow PduRefusedError into pdu-refusal.ts
2026-09-05 23:16:59 +02:00
lilleman afe188eecd Retry the first connect and bind on reconnect fromStart (#80)
* Regression tests for a client that retries its first connect and bind

* Retry the first connect and bind on reconnect: { fromStart: true }

* Pin that the signal bounding the first bind also closes the session

* Name a failed connect once, default the backoff where it lives, and fix the deadline advice

* Carry the failure that started the retries on the abort that ends them

* Pin one retry announcement per attempt when a link drops mid-bind

* Announce a retry when its wait is over, not when it is scheduled

* Say what the process-hold assertion expects, not what a failure would mean
2026-09-05 22:47:06 +02:00
lilleman 45d2f5548e Read a delivery receipt's body as the octets that arrived (#81)
* Regression tests for a receipt body read under a data_coding it is not written in

* Read a delivery receipt's body as the octets that arrived, not by its data_coding

* Assert SMPPSim's UCS2 receipt body parses like every other segment's

* Record the receipt-body defect as fixed

* Retain a multipart segment's octets once, not twice

* Sort the new type import into its file's order
2026-09-05 22:37:05 +02:00
lilleman ea42bc6d20 Refuse a single unreadable PDU instead of the whole link (#79)
* Regression tests: a PDU the codec cannot read costs that PDU, not the link

* Refuse a single unreadable PDU instead of the whole link

* smscsim interop asserts the first attempt gets every DLR

* Record the smscsim sequence number defect as fixed

* One framing rule and one response-command lookup, per the architecture review

* Apply the stability review's nits: honest sessionError docs and one link-survives assertion
2026-09-05 21:00:52 +02:00
lilleman 2e422c7584 Contain a throw from the application's logger instead of letting it escape 2026-09-04 11:33:20 +02:00
lilleman 80530c6265 Ignore a delivery receipt for a part the send never registered 2026-09-04 11:33:17 +02:00
lilleman 50679ec2fe Read an intermediate delivery notification as a report, and keep it out of the merge 2026-09-03 17:30:09 +02:00
lilleman 2c0dfd172f Keep the process alive while a send waits for a link 2026-09-01 10:38:16 +02:00
lilleman 8d9656b4e0 Hold a send with no link for the next one, and count the rest as unanswered 2026-09-01 08:02:37 +02:00
lilleman bfc85ee4dd Say in the options table that a proven link resets the backoff
maxDelay bounds the wait and sets the bar a link must clear, and only the
first was documented, so tuning it up quietly lengthened every recovery.

The unreadable PDU the tests write is one named constant now.
2026-08-31 22:59:57 +02:00
lilleman 8e4d472f72 Back off from a link that dies as soon as it comes up
A bind that returns is not proof the link works: an unreadable stream is
only found afterwards, so resetting the delay there handed a link that
died on arrival a fresh minDelay every cycle — one connect and bind per
second forever, which is how an account gets blocked for bind flooding.
The loop resets only once a link has outlasted maxDelay.

Also covers the unreadable stream that has no loop to retry it.
2026-08-31 22:45:29 +02:00
lilleman 880454b4f4 Split a retried drop out of close, and range-check the backoff
`close` fires only where nothing will bring the session back; a drop the
reconnect loop will retry is `disconnected`, pairing with `reconnected`.
`minDelay: 0` never doubles, so the backoff never started — both delays
are range-checked now, and only `false` spells reconnect off.
2026-08-31 21:11:19 +02:00
lilleman 098891bb84 Cover the enquire_link we answer and stop draining a peer that answers nothing 2026-08-30 22:17:27 +02:00
lilleman 685495f534 Let a listener's teardown run last and answer the sends the tests left hanging 2026-08-30 22:03:23 +02:00
lilleman 427565ed3e Tear down what a test opens in t.after so a failing assertion ends the run 2026-08-30 21:35:09 +02:00
lilleman bebd74b42b Drain the requests already on the wire before close() and unbind() tear down 2026-08-30 20:08:36 +02:00
lilleman c06eda4582 Pin the spent-base cap and state the boundary the merge rule covers 2026-08-30 11:39:09 +02:00
lilleman 5c797f5383 Forget the base closed longest ago rather than the one handed out again 2026-08-30 11:31:22 +02:00
lilleman aa3cf41c7f Merge a message id base once so a straggler cannot join a later report 2026-08-30 11:17:02 +02:00
lilleman a5fe43d7a9 Normalise a listener's rejection reason before routing it to sessionError 2026-08-27 22:41:26 +02:00
lilleman 0e8f298957 Route a rejecting async listener to sessionError instead of the process 2026-08-27 22:22:51 +02:00
lilleman 4c08fe8fa3 Classify a deliver_sm by esm_class and document the messageDlr precondition 2026-08-27 15:52:33 +02:00
lilleman 366fa26b1c Enforce bind direction, cap segments per message and pin the README examples 2026-08-27 14:07:42 +02:00
lilleman ff3667e120 Reject a renumbered multipart total and check abort before the send window 2026-08-27 12:43:17 +02:00
lilleman 8a182604b7 Address the CodeRabbit review: inbound SMS, binary payloads and untrusted input 2026-08-27 11:52:43 +02:00
lilleman 884afdb87b Stop a thrown listener escaping, bound what a peer can pin, and drop the body from failure responses 2026-08-27 10:55:34 +02:00
lilleman 5af75a3c57 Close the stability findings and settle the sendResp, sendSms and unbind contracts 2026-08-27 00:17:48 +02:00
lilleman 6d842a8539 Split the session, gate optional parameters on the peer version, own the SMPP version in defs 2026-08-26 22:41:28 +02:00
lilleman f0858aacf8 Answer 3.4 binds with sc_interface_version and cover TLS with tests 2026-08-26 17:16:54 +02:00
lilleman 694c3a506e Bind as SMPP 3.4 by default, with interfaceVersion to override it 2026-08-26 15:41:44 +02:00
lilleman fd2f98464f Add session, client and server with the promise result API 2026-08-25 16:46:32 +02:00