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
This commit is contained in:
@@ -115,6 +115,11 @@ sent SAR MO unprompted), it confirms only that pushing SAR/UDH-tagged `deliver_s
|
||||
directly over the connector session, reassembles correctly on the way out through `smpps` - Jasmin
|
||||
does not re-segment or otherwise disturb an already-short single PDU in transit.
|
||||
|
||||
**Fixed** in [#91](https://github.com/larvit/larvitsmpp/pull/91), where a second peer did reproduce
|
||||
it (`findings/05-java-clients.md`): reassembly now reads the `sar_*` TLVs as well as the UDH. C8's
|
||||
SAR scenario no longer accepts fragments as an outcome - it asserts one whole `sms` and no segment
|
||||
arriving on its own - and a rerun is 19/19 with no malformed frame and no expert error.
|
||||
|
||||
### `data_sm` is refused (target 4) - confirmed against a real peer
|
||||
|
||||
**What happened.** With `jasmin-datasm`'s `[dlr-thrower] dlr_pdu = data_sm`, a receipt requested via
|
||||
|
||||
@@ -75,7 +75,7 @@ an independent dissector agreeing is the expected outcome, not a surprise.
|
||||
| S2 UDH 8-bit (targets 2, 5) | pass | `jsmpp.test.ts` "UDH, 8-bit reference": one reassembled `sms`, segments answered `<base>-1`/`<base>-2` |
|
||||
| S2 UDH 16-bit (target 5) | pass | `jsmpp.test.ts` "UDH, 16-bit reference": also reassembled - confirms both widths are read |
|
||||
| S2 `message_payload` (target 2) | pass | `jsmpp.test.ts` "message_payload: one sms, the full text" |
|
||||
| S2 `sar_*` (target 3) | defect confirmed, second peer | `jsmpp.test.ts` "sar_*: defect (target 3)": two independent `sms` events, not one |
|
||||
| S2 `sar_*` (target 3) | defect confirmed, second peer; fixed in [#91](https://github.com/larvit/larvitsmpp/pull/91) | `jsmpp.test.ts` "sar_* (target 3)": one reassembled `sms`, segments answered `<base>-1`/`<base>-2` |
|
||||
| S3 known-but-unhandled (targets 1, 6) | pass | `jsmpp.test.ts` "query_sm, cancel_sm, replace_sm": `ESME_RINVCMDID`, link survives, jsmpp raises `NegativeResponseException` and keeps going |
|
||||
| S3 unknown command id (target 1) | pass | `jsmpp.test.ts` "an unknown command id gets generic_nack..." |
|
||||
| S3 truncated TLV stream (target 1) | pass | `jsmpp.test.ts` "a deliver_sm with a truncated TLV stream..." |
|
||||
@@ -100,11 +100,19 @@ Spec: SMPP 3.4 5.3.2.16-5.3.2.18 defines `sar_msg_ref_num`/`sar_total_segments`/
|
||||
as an alternative to the UDH for carrying concatenation; nothing in the spec says a receiver may
|
||||
ignore it.
|
||||
|
||||
Reproducer: `jsmpp.test.ts`, "sar_\*: defect (target 3)" - two `submit_sm`s to the same
|
||||
Reproducer: `jsmpp.test.ts`, "sar_\* (target 3)" - two `submit_sm`s to the same
|
||||
`source_addr`/`destination_addr`, `esm_class` 0x00, one `sar_msg_ref_num` (0x77) across both, `1/2`
|
||||
then `2/2` in `sar_total_segments`/`sar_segment_seqnum`. Severity: as already scoped in PLAN.md
|
||||
target 3 / phase 10 - a known, tracked limitation, not new.
|
||||
|
||||
**Fixed** in [#91](https://github.com/larvit/larvitsmpp/pull/91): `concatOf()` reads the
|
||||
concatenation from the UDH, or from the `sar_*` TLVs where the PDU declares none, and each spelling
|
||||
groups in a reference space of its own. The reproducer now asserts what a rerun shows - one `sms`
|
||||
carrying the whole 200-char text, its two `submit_sm`s answered `<base>-1` and `<base>-2`, and
|
||||
neither half ever reaching the application on its own. The rest of the suite is unchanged: 12/12,
|
||||
frames 37, `submit_sm` 8/8, malformed 2 and expert errors 2 - the two deliberately malformed PDUs
|
||||
the S3 scenarios send.
|
||||
|
||||
### A 4-octet truncated TLV tail is silently accepted rather than refused (target 1)
|
||||
|
||||
What happened: a `deliver_sm` whose mandatory fields are complete, followed by exactly one bare TLV
|
||||
|
||||
@@ -514,15 +514,13 @@ describe('C8 (target 3) - long MO from an upstream SMSC, SAR vs UDH segmentation
|
||||
|
||||
await sendSarMo(upstreamSession, { from: TO, message: text, to: FROM });
|
||||
|
||||
// Recorded either way, per the task: one whole `sms` (Jasmin reassembled the SAR segments
|
||||
// before forwarding) or several fragments (it relayed them, and our SAR-blind reassembler -
|
||||
// keyed on UDH only - never groups them; see findings for which happened and the reproducer).
|
||||
const whole = await waitFor(() => sms.find(s => s.message === text), 10_000);
|
||||
const fragments = sms.filter(s => text.includes(s.message) && s.message !== '');
|
||||
const fragments = sms.filter(s => s.message !== text && text.includes(s.message) && s.message !== '');
|
||||
|
||||
await session.close({ signal: AbortSignal.abort() });
|
||||
|
||||
assert.ok(whole ?? fragments.length > 0, 'expected either a reassembled sms or SAR fragments to arrive');
|
||||
assert.ok(whole, 'expected the SAR segments to reassemble into one whole sms');
|
||||
assert.deepEqual(fragments.map(s => s.message), [], 'no segment reaches the application on its own');
|
||||
});
|
||||
|
||||
test('UDH-segmented deliver_sm from the fake upstream', async () => {
|
||||
|
||||
@@ -173,7 +173,7 @@ describe('S2 - long messages in every spelling (targets 2, 3, 5)', () => {
|
||||
assert.equal(sms.answeredOnArrival, false);
|
||||
});
|
||||
|
||||
test('sar_*: defect (target 3) - each segment reaches the application as its own sms, not one', async () => {
|
||||
test('sar_* (target 3): one reassembled sms, each segment answered <base>-<n>', async () => {
|
||||
await waitForSessions(1);
|
||||
|
||||
const text = 'sar-'.padEnd(200, 'c');
|
||||
@@ -184,17 +184,13 @@ describe('S2 - long messages in every spelling (targets 2, 3, 5)', () => {
|
||||
|
||||
assert.equal(segments.length, 2);
|
||||
|
||||
// Neither segment ever arrives as the whole 200-char text: each is its own ordinary sms
|
||||
// carrying only its own ~130-char slice, with its own unrelated (non-<base>-<n>) message id.
|
||||
const first = await waitForSms(text.slice(0, 130), 15_000);
|
||||
const second = await waitForSms(text.slice(130), 15_000);
|
||||
const sms = await waitForSms(text, 15_000);
|
||||
|
||||
assert.equal(first.answeredOnArrival, false);
|
||||
assert.equal(second.answeredOnArrival, false);
|
||||
assert.notEqual(first.smsId, second.smsId);
|
||||
assert.equal(allSms.some(entry => entry.sms.message === text), false);
|
||||
void first;
|
||||
void second;
|
||||
assert.equal(sms.answeredOnArrival, true);
|
||||
assert.equal(segments[0]?.messageId, `${sms.smsId}-1`);
|
||||
assert.equal(segments[1]?.messageId, `${sms.smsId}-2`);
|
||||
// Neither ~130-char slice ever reached the application on its own.
|
||||
assert.equal(allSms.filter(entry => text.includes(entry.sms.message)).length, 1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user