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:
2026-09-06 21:03:14 +02:00
committed by GitHub
parent 061c1871bd
commit 18de565aad
12 changed files with 595 additions and 89 deletions
+12 -10
View File
@@ -1,4 +1,4 @@
import type { ConcatInfo } from './udh.ts';
import type { Concat } from './concat.ts';
import type { ParamValue } from './defs/types.ts';
import type { PduObject } from './pdu.ts';
import type { SmppLog } from './log.ts';
@@ -96,12 +96,14 @@ function octetsOf(pduObj: PduObject): number {
return octets;
}
function groupKey(pduObj: PduObject, reference: number): string {
// NUL: the one octet a C-Octet String address cannot hold, so no sender can forge another's key.
function groupKey(pduObj: PduObject, concat: Concat): string {
return [
paramText(pduObj.params.source_addr),
paramText(pduObj.params.destination_addr),
String(reference),
].join('_');
concat.spelling,
String(concat.reference),
].join('\u0000');
}
/** The text of a message, joining its segments in the order they were reassembled. */
@@ -152,10 +154,10 @@ export class Reassembler {
}
/** The group the segment joined, and always an answer for it: an unanswered one stalls a peer. */
collect(pduObj: PduObject, concat: ConcatInfo): Collected {
collect(pduObj: PduObject, concat: Concat): Collected {
this.sweep();
const key = groupKey(pduObj, concat.reference);
const key = groupKey(pduObj, concat);
const existing = this.groups.get(key);
if (!this.placeable(concat, existing)) return { kept: false, refusal: 'unplaceable' };
@@ -204,10 +206,10 @@ export class Reassembler {
}
}
/** Whether a segment's UDH can join a group at all: its own numbering, and the group's total. */
private placeable(concat: ConcatInfo, existing: Group | undefined): boolean {
/** Whether a segment can join a group at all: its own numbering, and the group's total. */
private placeable(concat: Concat, existing: Group | undefined): boolean {
if (concat.part < 1 || concat.total < 1 || concat.part > concat.total) {
this.log.warn('reassembler - dropping a segment the UDH numbers impossibly', {
this.log.warn('reassembler - dropping an impossibly numbered segment', {
part: concat.part,
total: concat.total,
});
@@ -217,7 +219,7 @@ export class Reassembler {
// Parts 1/2 and 2/3 would otherwise complete the stored two-part group as a truncated message.
if (existing && existing.total !== concat.total) {
this.log.warn('reassembler - dropping a segment with an inconsistent UDH total', {
this.log.warn('reassembler - dropping a segment with an inconsistent total', {
existingTotal: existing.total,
part: concat.part,
total: concat.total,