Commit Graph

59 Commits

Author SHA1 Message Date
lilleman 13e3ff8f1f Say undefined where the SMSC named no id for a segment it took (#101)
* Cover the ids a send gets back where the SMSC named only some of them

* Say undefined where the SMSC named no id for a segment it took

* Record why the merger takes the wider type, and de-duplicate the interop narrowing

* Guard the numbering hole the merger must refuse, and correct the phase record
2026-09-12 22:37:55 +02:00
lilleman 7d6039796d Fit every concatenated segment in an SMS, and close the encoding helpers' throw (#96)
* Regression tests for the concatenated segment budget and the encoding-name door

* Budget unpacked segments at 134 octets, and export isEncodingName

* Name the length a Latin-1 message starts costing more segments at

* Fold the totality clause into hard rule 1, and log the unrepresentable-alphabet finding

* Make the maxSegments regression discriminate, and drop the narrating comments

* State the per-alphabet capacities accurately, and log the invalid-date path
2026-09-09 17:21:51 +02:00
lilleman 5a0cf56754 Fixtures for the receipt bodies operator documentation publishes (#93)
* Fixtures for the receipt bodies operator documentation publishes

* Read the stat:FAILED several operators write as UNDELIVERABLE

* Cover the 16-bit UDH concatenation element with a fixture

* Record phase 9 of the interoperability plan

* Share the dummy SMSC and tighten the operator fixtures

* Correct what the test conventions claim about the doubles and the tree

* Read a receipt date that carries its century

* Read the stat spelling CM.com publishes

* Say what the receipt status table actually holds
2026-09-08 18:56:57 +02:00
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 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 144567c294 Claim an error only where the state failed, and freeze the two state lists 2026-09-04 08:51:13 +02:00
lilleman 54493c12b2 Write a transient state as an intermediate delivery notification 2026-09-04 08:38:29 +02:00
lilleman 42e6e97447 Read a receipt reporting a transient state as non-final too, so the merge waits for the real one 2026-09-03 19:28:08 +02:00
lilleman dd458bfa53 Say only what the SMSC marked, and trim the comments the review found redundant 2026-09-03 19:16:13 +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 94184a9c63 Re-wrap the shutdown paragraph and leave the throw-versus-rejection note in one place 2026-09-02 12:40:51 +02:00
lilleman 3eeb0b82c0 Give a message up only once every listener has, and only count a response the wire took 2026-09-02 12:36:01 +02:00
lilleman 3aadb64df8 Release a message's hold when the response goes out, or when its listener failed 2026-09-02 08:15:26 +02:00
lilleman 471923a7b6 Log the dropped request, and scope the receipt sentence to a reconnect 2026-09-01 20:53:58 +02:00
lilleman e6250ef44e Drop a request whose link went while onRequest ran, and pin the wire in the test 2026-09-01 20:40:17 +02:00
lilleman c8d265d5fa Tie the refusal to the link, not to the hold, so a released message is refused too 2026-09-01 20:25:57 +02:00
lilleman 82a95593bd Refuse to answer a message whose link went, instead of reporting it delivered 2026-09-01 19:20:06 +02:00
lilleman f0327044b1 Cover the receipt refusal and the sweep that wakes a drain 2026-09-01 18:13:35 +02:00
lilleman 042a62be97 Sweep held messages before each hold, so expiry needs no live timer 2026-09-01 18:02:15 +02:00
lilleman 61985fb914 Say what a peer did with each receipt segment, and leave no drop unlogged 2026-09-01 17:59:37 +02:00
lilleman 67c0402def Bound the messages held for a shutdown, and report a partial receipt 2026-09-01 17:42:22 +02:00
lilleman 1e7e113bd7 Put a receipt's segments on the wire together, like a message's 2026-09-01 17:03:33 +02:00
lilleman 8becbc0ab6 Give the gate, the window and the pending map one owner 2026-09-01 16:52:15 +02:00
lilleman 3bcea108e3 Wait out the messages the application holds before shutting a session down 2026-09-01 13:58:28 +02:00
lilleman 2fe36fa4e8 Say once why the hold timer is not unref'd 2026-09-01 10:48:20 +02:00
lilleman 2c0dfd172f Keep the process alive while a send waits for a link 2026-09-01 10:38:16 +02:00
lilleman 35dd1e7678 Count every request that went out unanswered, and gate a send on a bound link 2026-09-01 10:08:56 +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 b1c790b9a0 Retry a stream we cannot read, and cover the framer reset on attach
A framing or codec error tears the link down rather than the session, so
the loop retries it on a fresh socket with a fresh framer — which is what
a desynced stream needs. Removing that reset failed nothing before; the
reconnect test now leaves half a PDU on the dying link, and does.

`disconnected` counts failed links rather than outages, which the README
now says, and the transport wires its socket as it is built.
2026-08-31 22:27:16 +02:00
lilleman b2d121b4f3 Emit exactly one close, and read the socket through a PduTransport
Ending a session while the link was down emitted nothing: the retried
drop had already torn it down, so close() found nothing left to do. The
terminal event is now guarded by its own flag rather than by `closed`.

The socket, the framer and the parse move to PduTransport, which is what
takes session.ts back under its line cap; `session.sock` reads through a
getter. The backoff also refuses an inverted pair and an unknown key.
2026-08-31 21:53:11 +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 18177053bc Re-bind a dropped client link by default, and take false to turn it off
Surviving a drop is most of what the session layer is for, and behind an
opt-in an application that never read the options table got none of it.
`reconnect` still takes `{ minDelay, maxDelay }` to retune the backoff;
`false` is the one spelling for off.
2026-08-31 20:18:29 +02:00
lilleman 140464802b Put the submit_sm summary back on the function it describes and tidy the review nits 2026-08-30 23:12:22 +02:00
lilleman 706b61d6ca Leave an unknown notation alone and read a receipt's TLV as the id the submit answered 2026-08-30 23:01:39 +02:00
lilleman 696e018b77 Derive the notation names from one table and refuse an smsIdFormat that is not the pair 2026-08-30 22:45:02 +02:00
lilleman 8e5b9bb556 Read a peer's submit and receipt message ids into one notation before comparing them 2026-08-30 22:35:09 +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 fa720e244a Drop the line count that drifts and reuse the delay helper 2026-08-30 21:09:19 +02:00
lilleman 7e8af3c308 Tear down at once on a peer unbind and stop reporting a reconnect after close 2026-08-30 21:01:46 +02:00
lilleman 5c618e0061 Stop accepting before the drain and report the link that dropped during it 2026-08-30 20:27:38 +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 f4e55909a1 Keep the delivery-receipt merges across a reconnect instead of dropping them 2026-08-28 14:44:32 +02:00