This phase focuses on integrating the Transaction Capabilities Application Part (TCAP) layer on top of the SCCP implementation from Phase 2. It includes the introduction of a new enumeration for TCAP operation codes, and initial encode/decode support for the two primary component types we need in early end‑to‑end tests: Invoke and ReturnResult. The goal is to produce a byte‑array representation that can be carried inside the SCCP UDT message’s UserData
field and subsequently transported by M3UA.
We intentionally use a simplified encoding scheme to bootstrap testing quickly; real TCAP uses ASN.1 BER encoding which is far more complex. The simplified scheme outlined here packs the minimal fields required for our test scenarios in a fixed order.
Operation codes identify the service or action requested via an Invoke component. In MAP‑SMS, common operation codes include sending and delivering short messages. Below is an initial enumeration (TcapOperationCode
) containing a few representative values. These can be extended to match the full MAP specification later.
namespace SigtranStack.Layers.TCAP
{
/// <summary>
/// Represents high‑level TCAP/MAP operation codes. In a real implementation these
/// map to ASN.1 Object Identifiers defined in 3GPP TS 29.002. We use
/// small integers for early test cases.
/// </summary>
public enum TcapOperationCode : byte
{
/// <summary>Unknown or undefined operation.</summary>
None = 0,
/// <summary>MAP operation to send a mobile‑originated short message (MO‑ForwardSM).</summary>
MoForwardShortMessage = 1,
/// <summary>MAP operation to send a mobile‑terminated short message (MT‑ForwardSM).</summary>
MtForwardShortMessage = 2,
/// <summary>MAP operation to alert the Service Centre that a mobile is reachable.</summary>
AlertServiceCentre = 3,
/// <summary>MAP operation to report short message delivery status.</summary>
ReportSmDeliveryStatus = 4
}
}
All TCAP components share a few common fields: an Invoke ID to correlate requests and responses, an operation code (for Invoke and ReturnResult), and a byte array of parameters (which in our simplified scheme are left as raw payloads). The abstract base class TcapComponent
defines these fields and declares Encode()
and Decode()
methods that subclasses must implement.
namespace SigtranStack.Layers.TCAP
{
using System;
/// <summary>
/// Base class for TCAP components. Provides common properties and enforces
/// encoding/decoding semantics via abstract methods.
/// </summary>
public abstract class TcapComponent
{
/// <summary>
/// Gets the invoke identifier associated with this component. For Invoke
/// this is generated by the sender; for ReturnResult it refers back to
/// the original Invoke.
/// </summary>
public byte InvokeId { get; protected set; }
/// <summary>
/// Gets the operation code associated with this component.
/// </summary>
public TcapOperationCode OperationCode { get; protected set; }
/// <summary>
/// Gets the parameters for this component. In real TCAP these are ASN.1
/// encoded structures; here we treat them as opaque bytes.
/// </summary>
public ReadOnlyMemory<byte> Parameters { get; protected set; }
/// <summary>
/// Encodes the component into a byte array according to the simplified format.
/// </summary>
/// <returns>Encoded bytes representing the component.</returns>
public abstract byte[] Encode();
/// <summary>
/// Parses a component from encoded bytes. Subclasses should verify the
/// component type marker and populate properties accordingly.
/// </summary>
/// <param name="data">Byte span containing the component.</param>
/// <returns>True on success; false otherwise.</returns>
public abstract bool TryDecode(ReadOnlySpan<byte> data);
}
}
Invoke components request a remote operation. Our simplified encoding uses the following layout:
Offset | Length | Purpose |
---|---|---|
0 | 1 | Type = 0xA1 for Invoke |
1 | 1 | Invoke ID |
2 | 1 | Operation Code |
3 | 1 | Length of Parameters (N) |
4.. | N | Parameter bytes |
Encode()
constructs a byte array accordingly. TryDecode()
validates the type marker, checks bounds, and populates fields.
namespace SigtranStack.Layers.TCAP
{
using System;
/// <summary>
/// Represents a TCAP Invoke component for simplified testing. Uses a
/// fixed layout and 8‑bit invoke IDs and operation codes.
/// </summary>
public sealed class TcapInvokeComponent : TcapComponent
{
/// <summary>
/// Constructs a new Invoke component with given fields.
/// </summary>
public TcapInvokeComponent(byte invokeId, TcapOperationCode opCode, ReadOnlyMemory<byte> parameters)
{
InvokeId = invokeId;
OperationCode = opCode;
Parameters = parameters;
}
/// <inheritdoc />
public override byte[] Encode()
{
int len = Parameters.Length;
byte[] buffer = new byte[4 + len];
buffer[0] = 0xA1; // Invoke type marker
buffer[1] = InvokeId;
buffer[2] = (byte)OperationCode;
buffer[3] = (byte)len;
if (len > 0)
{
Parameters.Span.CopyTo(buffer.AsSpan(4));
}
return buffer;
}
/// <inheritdoc />
public override bool TryDecode(ReadOnlySpan<byte> data)
{
if (data.Length < 4) return false;
if (data[0] != 0xA1) return false;
byte id = data[1];
TcapOperationCode op = (TcapOperationCode)data[2];
int plen = data[3];
if (data.Length < 4 + plen) return false;
InvokeId = id;
OperationCode = op;
Parameters = data.Slice(4, plen).ToArray();
return true;
}
}
}
A ReturnResult component conveys the successful outcome of a previously issued Invoke. Our simplified format mirrors the Invoke structure but uses a different type marker (0xA2
). The operation code is repeated for completeness.
namespace SigtranStack.Layers.TCAP
{
using System;
/// <summary>
/// Represents a TCAP ReturnResult component for simplified testing.
/// </summary>
public sealed class TcapReturnResultComponent : TcapComponent
{
/// <summary>
/// Constructs a new ReturnResult component.
/// </summary>
public TcapReturnResultComponent(byte invokeId, TcapOperationCode opCode, ReadOnlyMemory<byte> parameters)
{
InvokeId = invokeId;
OperationCode = opCode;
Parameters = parameters;
}
/// <inheritdoc />
public override byte[] Encode()
{
int len = Parameters.Length;
byte[] buffer = new byte[4 + len];
buffer[0] = 0xA2; // ReturnResult type marker
buffer[1] = InvokeId;
buffer[2] = (byte)OperationCode;
buffer[3] = (byte)len;
if (len > 0)
{
Parameters.Span.CopyTo(buffer.AsSpan(4));
}
return buffer;
}
/// <inheritdoc />
public override bool TryDecode(ReadOnlySpan<byte> data)
{
if (data.Length < 4) return false;
if (data[0] != 0xA2) return false;
byte id = data[1];
TcapOperationCode op = (TcapOperationCode)data[2];
int plen = data[3];
if (data.Length < 4 + plen) return false;
InvokeId = id;
OperationCode = op;
Parameters = data.Slice(4, plen).ToArray();
return true;
}
}
}
The Encode()
methods produce raw byte arrays that can be passed as the UserData
in an SccpMessage
. For example:
// Create a TCAP Invoke asking to forward a mobile‑originated short message
var invoke = new TcapInvokeComponent(1, TcapOperationCode.MoForwardShortMessage,
parameters: new byte[] { 0x01, 0x02, 0x03 });
byte[] tcapBytes = invoke.Encode();
// Build a UDT with these bytes and send down via SCCP/M3UA
var udt = new SccpMessage
{
ProtocolClass = 0,
CalledParty = new SccpAddress { Subsystem = SubsystemNumber.MAP },
CallingParty = new SccpAddress { Subsystem = SubsystemNumber.MAP },
UserData = tcapBytes
};
byte[] sccpPayload = udt.EncodeUdt();
On the receiving side, the bytes can be parsed back into a TcapInvokeComponent
or TcapReturnResultComponent
by examining the first byte (0xA1 or 0xA2). The TryDecode()
method will populate the Invoke ID, Operation Code and Parameters accordingly. More component types (e.g., ReturnError, Reject) can be added later following the same pattern.
- Integrate TCAP into the SCCP layer: modify the
SccpLayer
to produce and consumeTcapComponent
objects rather than rawbyte[]
for clarity. - Implement TCAP Dialogue management: connect
TcapDialogue
withTcapComponent
encode/decode so that it can build full messages (with multiple components) and track transaction state. - Support additional component types: ReturnError and Reject should be added with appropriate simplified encoding.
- Transition to ASN.1 BER: once the simplified path is proven, replace the custom encoding with proper BER encoding using an ASN.1 library.