Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/target
bin
obj
11 changes: 11 additions & 0 deletions csharp/DirectIpNet.Native/DirectIpNet.Native.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
<PublishAot>true</PublishAot>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../DirectIpNet/SBDDirectIP.csproj" />
</ItemGroup>
</Project>
56 changes: 56 additions & 0 deletions csharp/DirectIpNet.Native/Exports.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using SBDDirectIP;

namespace SBDDirectIP.Native;

public static unsafe class Exports
{
[StructLayout(LayoutKind.Sequential)]
public struct ConfirmationInfo
{
public uint ClientMsgId;
public uint IdReference;
public MessageStatus Status;
}

[UnmanagedCallersOnly(EntryPoint = "directip_send_mt")]
public static int SendMt(byte* host, int port, uint clientMsgId, byte* imei, byte* payload, int payloadLen, ConfirmationInfo* confirmation)
{
try
{
var hostStr = Marshal.PtrToStringUTF8((IntPtr)host)!;
var imeiStr = Marshal.PtrToStringUTF8((IntPtr)imei)!;
var data = new byte[payloadLen];
if (payload != null && payloadLen > 0)
{
Marshal.Copy((IntPtr)payload, data, 0, payloadLen);
}

var client = new DirectIpClient(hostStr, port);
var msg = MTMessage.Builder()
.ClientMsgId(clientMsgId)
.Imei(imeiStr)
.Payload(data)
.Build();

Task<Confirmation> task = client.SendAsync(msg);
task.Wait();
Confirmation conf = task.Result;

if (confirmation != null)
{
confirmation->ClientMsgId = conf.ClientMsgId;
confirmation->IdReference = conf.IdReference;
confirmation->Status = conf.Status;
}

return 0;
}
catch
{
return -1;
}
}
}
35 changes: 35 additions & 0 deletions csharp/DirectIpNet.Native/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# DirectIpNet.Native

This project exposes the `SBDDirectIP` API as a native C library. It uses
`UnmanagedCallersOnly` so the resulting binary can be consumed from any language
that can call C functions.

## Building

Use `dotnet publish` with NativeAOT enabled to produce a shared library:

```shell
cd csharp/DirectIpNet.Native
dotnet publish -c Release -r linux-x64
```

The compiled library will be placed under `bin/Release/net8.0/linux-x64/native`.

## Usage

Include `directip.h` in your C or C++ project and link against the produced
library. Example:

```c
#include "directip.h"

int main() {
ConfirmationInfo conf;
const char payload[] = "Hello";
int rc = directip_send_mt("127.0.0.1", 10800, 1,
"012345678901234",
(const uint8_t*)payload, sizeof(payload)-1,
&conf);
return rc;
}
```
28 changes: 28 additions & 0 deletions csharp/DirectIpNet.Native/directip.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#ifndef DIRECTIP_H
#define DIRECTIP_H

#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

typedef struct {
uint32_t ClientMsgId;
uint32_t IdReference;
int32_t Status;
} ConfirmationInfo;

int directip_send_mt(const char* host,
int port,
uint32_t client_msg_id,
const char* imei,
const uint8_t* payload,
int payload_len,
ConfirmationInfo* confirmation);

#ifdef __cplusplus
}
#endif

#endif // DIRECTIP_H
38 changes: 38 additions & 0 deletions csharp/DirectIpNet/Confirmation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.Buffers.Binary;

namespace SBDDirectIP;

public class Confirmation
{
public uint ClientMsgId { get; }
public string Imei { get; }
public uint IdReference { get; }
public MessageStatus Status { get; }

public Confirmation(uint clientMsgId, string imei, uint idReference, MessageStatus status)
{
ClientMsgId = clientMsgId;
Imei = imei;
IdReference = idReference;
Status = status;
}

public static Confirmation FromArray(ReadOnlySpan<byte> data)
{
int offset = 0;
if (data[offset++] != 0x44) throw new ArgumentException("Invalid confirmation element");
ushort len = BinaryPrimitives.ReadUInt16BigEndian(data.Slice(offset,2));
offset += 2;
if (len != 25) throw new ArgumentException("Unexpected confirmation length");
uint clientId = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(offset,4));
offset += 4;
string imei = System.Text.Encoding.ASCII.GetString(data.Slice(offset,15));
offset += 15;
uint refId = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(offset,4));
offset += 4;
short statusCode = BinaryPrimitives.ReadInt16BigEndian(data.Slice(offset,2));
var status = (MessageStatus)statusCode;
return new Confirmation(clientId, imei, refId, status);
}
}
29 changes: 29 additions & 0 deletions csharp/DirectIpNet/DirectIpClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace SBDDirectIP;

public class DirectIpClient
{
private readonly string _host;
private readonly int _port;

public DirectIpClient(string host, int port)
{
_host = host;
_port = port;
}

public async Task<Confirmation> SendAsync(MTMessage message)
{
using var client = new TcpClient();
await client.ConnectAsync(_host, _port);
using var stream = client.GetStream();
var data = message.ToArray();
await stream.WriteAsync(data, 0, data.Length);
var buffer = new byte[56];
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
return Confirmation.FromArray(buffer.AsSpan(3));
}
}
59 changes: 59 additions & 0 deletions csharp/DirectIpNet/MOMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;
using System.Buffers.Binary;

namespace SBDDirectIP;

/// <summary>
/// Represents a Mobile Originated (MO) message received from a remote device.
/// Only the header fields required to locate the payload are parsed.
/// </summary>
public class MOMessage
{
public string Imei { get; }
public byte[] Payload { get; }

private MOMessage(string imei, byte[] payload)
{
Imei = imei;
Payload = payload;
}

/// <summary>
/// Parse a raw DirectIP MO message.
/// </summary>
public static MOMessage FromArray(ReadOnlySpan<byte> data)
{
if (data.Length < 3)
throw new ArgumentException("Invalid data length", nameof(data));
if (data[0] != 1)
throw new ArgumentException("Unsupported protocol version", nameof(data));

ushort length = BinaryPrimitives.ReadUInt16BigEndian(data.Slice(1, 2));
if (data.Length - 3 < length)
throw new ArgumentException("Incomplete message", nameof(data));

int offset = 3;
if (data[offset++] != 0x01)
throw new ArgumentException("Missing MO-Header", nameof(data));
ushort hlen = BinaryPrimitives.ReadUInt16BigEndian(data.Slice(offset, 2));
offset += 2;
if (hlen != 28)
throw new ArgumentException("Unexpected header length", nameof(data));

offset += 4; // CDR reference
string imei = System.Text.Encoding.ASCII.GetString(data.Slice(offset, 15));
offset += 15;
offset += 1; // session status
offset += 2; // MOMSN
offset += 2; // MTMSN
offset += 4; // time of session

if (data[offset++] != 0x02)
throw new ArgumentException("Missing MO-Payload", nameof(data));
ushort plen = BinaryPrimitives.ReadUInt16BigEndian(data.Slice(offset, 2));
offset += 2;
byte[] payload = data.Slice(offset, plen).ToArray();

return new MOMessage(imei, payload);
}
}
36 changes: 36 additions & 0 deletions csharp/DirectIpNet/MTHeader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Buffers.Binary;
using System.IO;
using System.Text;

namespace SBDDirectIP;

internal class MTHeader
{
public uint ClientMsgId { get; }
public string Imei { get; }
public ushort DispositionFlags { get; }

public MTHeader(uint clientMsgId, string imei, ushort dispositionFlags)
{
if (imei.Length != 15) throw new ArgumentException("IMEI must be 15 digits", nameof(imei));
ClientMsgId = clientMsgId;
Imei = imei;
DispositionFlags = dispositionFlags;
}

public byte[] ToArray()
{
var buffer = new byte[24];
int offset = 0;
buffer[offset++] = 0x41;
BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(offset), 21);
offset += 2;
BinaryPrimitives.WriteUInt32BigEndian(buffer.AsSpan(offset), ClientMsgId);
offset += 4;
Encoding.ASCII.GetBytes(Imei).CopyTo(buffer.AsSpan(offset));
offset += 15;
BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(offset), DispositionFlags);
return buffer;
}
}
85 changes: 85 additions & 0 deletions csharp/DirectIpNet/MTMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System.Buffers.Binary;

namespace SBDDirectIP;

public class MTMessage
{
private readonly MTHeader _header;
private readonly MTPayload _payload;

private MTMessage(MTHeader header, MTPayload payload)
{
_header = header;
_payload = payload;
}

public static MTMessageBuilder Builder() => new MTMessageBuilder();

public byte[] ToArray()
{
var headerBytes = _header.ToArray();
var payloadBytes = _payload.ToArray();
var length = (ushort)(headerBytes.Length + payloadBytes.Length);

var buffer = new byte[3 + length];
int offset = 0;
buffer[offset++] = 1; // protocol version
BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(offset), length);
offset += 2;
headerBytes.CopyTo(buffer.AsSpan(offset));
offset += headerBytes.Length;
payloadBytes.CopyTo(buffer.AsSpan(offset));
return buffer;
}

public static MTMessage FromArray(ReadOnlySpan<byte> data)
{
if (data.Length < 3) throw new ArgumentException("Invalid message");
if (data[0] != 1) throw new ArgumentException("Unsupported protocol version");
ushort len = BinaryPrimitives.ReadUInt16BigEndian(data.Slice(1, 2));
if (data.Length - 3 < len) throw new ArgumentException("Incomplete message");
int offset = 3;
// parse header
if (data[offset] != 0x41) throw new ArgumentException("Missing header");
offset += 1;
ushort hLen = BinaryPrimitives.ReadUInt16BigEndian(data.Slice(offset,2));
offset += 2;
uint clientMsgId = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(offset,4));
offset += 4;
string imei = System.Text.Encoding.ASCII.GetString(data.Slice(offset,15));
offset += 15;
ushort flags = BinaryPrimitives.ReadUInt16BigEndian(data.Slice(offset,2));
offset += 2;
var header = new MTHeader(clientMsgId, imei, flags);
// parse payload
if (data[offset] != 0x42) throw new ArgumentException("Missing payload");
offset += 1;
ushort pLen = BinaryPrimitives.ReadUInt16BigEndian(data.Slice(offset,2));
offset += 2;
var payload = new byte[pLen];
data.Slice(offset, pLen).CopyTo(payload);
var pl = new MTPayload(payload);
return new MTMessage(header, pl);
}

public class MTMessageBuilder
{
private uint _clientMsgId;
private string? _imei;
private byte[] _payload = Array.Empty<byte>();
private ushort _flags = 0;

public MTMessageBuilder ClientMsgId(uint id) { _clientMsgId = id; return this; }
public MTMessageBuilder Imei(string imei) { _imei = imei; return this; }
public MTMessageBuilder Payload(byte[] data) { _payload = data ?? Array.Empty<byte>(); return this; }
public MTMessageBuilder DispositionFlags(ushort flags) { _flags = flags; return this; }

public MTMessage Build()
{
if (_imei == null) throw new InvalidOperationException("IMEI missing");
var header = new MTHeader(_clientMsgId, _imei, _flags);
var payload = new MTPayload(_payload);
return new MTMessage(header, payload);
}
}
}
Loading