Skip to content

Commit

Permalink
DataEnqueue Refactor (#33)
Browse files Browse the repository at this point in the history
* Refactored DataEnqueue to use pointer arithmetic
  • Loading branch information
mullerj authored Jun 14, 2024
1 parent 0662326 commit 662e1fa
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 64 deletions.
1 change: 0 additions & 1 deletion src/dotnet/design/Mil/Navy/Nrl/Norm/NormApi.puml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ class NormApi
+ {static} NormSetTxRobustFactor(sessionHandle:long, robustFactor:int) : void
+ {static} NormFileEnqueue(sessionHandle:long, fileName:string, infoPtr:string, infoLen:int): long
+ {static} NormDataEnqueue(sessionHandle:long, dataPtr:nint, dataLen:int, infoPtr:nint, infoLen:int) : long
+ {static} NormDataEnqueue(sessionHandle:long, data:byte[], dataLen:int, info:byte[], infoLen:int) : long
+ {static} NormRequeueObject(sessionHandle:long, objectHandle:long) : bool
+ {static} NormStreamOpen(sessionHandle:long, bufferSize:long, infoPtr:string, infoLen:int) : long
+ {static} NormStreamClose(streamHandle:long, graceful:bool) : void
Expand Down
35 changes: 0 additions & 35 deletions src/dotnet/src/Mil.Navy.Nrl.Norm/NormApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -544,41 +544,6 @@ public struct NormEvent
[DllImport(NORM_LIBRARY)]
public static extern long NormDataEnqueue(long sessionHandle, nint dataPtr, int dataLen, nint infoPtr, int infoLen);

/// <summary>
/// This function enqueues a segment of application memory space for transmission within the specified NORM sessionHandle.
/// </summary>
/// <param name="sessionHandle">Used to identify application in the NormSession.</param>
/// <param name="data">The data parameter must be a managed buffer to be transmitted.</param>
/// <param name="dataLen">The dataLen parameter indicates the quantity of data to transmit.</param>
/// <param name="info">The optional info and infoLen parameters
/// are used to associate NORM_INFO content with the sent transport object. The maximum allowed infoLen
/// corresponds to the segmentSize used in the prior call to NormStartSender(). The use and interpretation of the
/// NORM_INFO content is left to the application's discretion.</param>
/// <param name="infoLen">The optional info and infoLen parameters
/// are used to associate NORM_INFO content with the sent transport object. The maximum allowed infoLen
/// corresponds to the segmentSize used in the prior call to NormStartSender(). The use and interpretation of the
/// NORM_INFO content is left to the application's discretion</param>
/// <returns>A NormObjectHandle is returned which the application may use in other NORM API calls as needed.</returns>
public static long NormDataEnqueue(long sessionHandle, byte[] data, int dataLen, byte[]? info, int infoLen)
{
long objectHandle;
var dataHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
var infoHandle = GCHandle.Alloc(info, GCHandleType.Pinned);

try
{
var dataPtr = dataHandle.AddrOfPinnedObject();
var infoPtr = infoHandle.AddrOfPinnedObject();
objectHandle = NormDataEnqueue(sessionHandle, dataPtr, dataLen, infoPtr, infoLen);
}
finally
{
dataHandle.Free();
infoHandle.Free();
}
return objectHandle;
}

/// <summary>
/// This function allows the application to resend (or reset transmission of) a NORM_OBJECT_FILE or NORM_OBJECT_DATA
/// transmit object that was previously enqueued for the indicated sessionHandle.
Expand Down
48 changes: 36 additions & 12 deletions src/dotnet/src/Mil.Navy.Nrl.Norm/NormSession.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text;
using System.Runtime.InteropServices;
using System.Text;

namespace Mil.Navy.Nrl.Norm
{
Expand Down Expand Up @@ -497,6 +498,7 @@ public NormFile FileEnqueue(string filename, byte[] info, int infoOffset, int in
/// <param name="dataLength">Size of the message.</param>
/// <returns>A NormData is returned which the application may use in other NORM API calls as needed.</returns>
/// <exception cref="IOException">Thrown when NormDataEnqueue() returns NORM_OBJECT_INVALID, indicating the failure to enqueue data.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the data offset or data length are outside of the data buffer.</exception>
public NormData DataEnqueue(byte[] dataBuffer, int dataOffset, int dataLength)
{
return DataEnqueue(dataBuffer, dataOffset, dataLength, null, 0, 0);
Expand All @@ -514,24 +516,46 @@ public NormData DataEnqueue(byte[] dataBuffer, int dataOffset, int dataLength)
/// <param name="infoLength">The optional info and infoLength parameters are used to associate NORM_INFO content with the sent transport object.</param>
/// <returns>A NormData is returned which the application may use in other NORM API calls as needed.</returns>
/// <exception cref="IOException">Thrown when NormDataEnqueue() returns NORM_OBJECT_INVALID, indicating the failure to enqueue data.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the data offset, data length, info offset or info length are outside of the associated buffer.</exception>
public NormData DataEnqueue(byte[] dataBuffer, int dataOffset, int dataLength, byte[]? info, int infoOffset, int infoLength)
{
var dataBytes = dataBuffer.Skip(dataOffset).Take(dataLength).ToArray();
byte[]? infoBytes;
if (info != null)
if (dataOffset < 0 || dataOffset >= dataBuffer.Length)
{
infoBytes = info.Skip(infoOffset).Take(infoLength).ToArray();
}
else
throw new ArgumentOutOfRangeException(nameof(dataOffset), "The data offset is out of range");
}
if (dataOffset + dataLength > dataBuffer.Length)
{
infoBytes = null;
infoLength = 0;
throw new ArgumentOutOfRangeException(nameof(dataLength), "The data length is out of range");
}
var objectHandle = NormDataEnqueue(_handle, dataBytes, dataLength, infoBytes, infoLength);
if (objectHandle == NormObject.NORM_OBJECT_INVALID)
if (infoOffset < 0 || infoOffset >= info?.Length)
{
throw new IOException("Failed to enqueue data");
throw new ArgumentOutOfRangeException(nameof(infoOffset), "The info offset is out of range");
}
if (infoOffset + infoLength > info?.Length)
{
throw new ArgumentOutOfRangeException(nameof(infoLength), "The info length is out of range");
}

long objectHandle;
var dataHandle = GCHandle.Alloc(dataBuffer, GCHandleType.Pinned);
var infoHandle = GCHandle.Alloc(info, GCHandleType.Pinned);

try
{
var dataPtr = dataHandle.AddrOfPinnedObject() + dataOffset;
var infoPtr = infoHandle.AddrOfPinnedObject() + infoOffset;
objectHandle = NormDataEnqueue(_handle, dataPtr, dataLength, infoPtr, infoLength);
if (objectHandle == NormObject.NORM_OBJECT_INVALID)
{
throw new IOException("Failed to enqueue data");
}
}
finally
{
dataHandle.Free();
infoHandle.Free();
}

return new NormData(objectHandle);
}

Expand Down
188 changes: 172 additions & 16 deletions src/dotnet/tests/Mil.Navy.Nrl.Norm.IntegrationTests/NormSessionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,12 +186,22 @@ public void StopsReceiver()
/// Generates text content
/// </summary>
/// <returns>The generated text content</returns>
private string GenerateTextContent()
private static string GenerateTextContent()
{
var faker = new Faker();
return faker.Lorem.Paragraph();
}

/// <summary>
/// Generates info content
/// </summary>
/// <returns>The generated info content</returns>
private static string GenerateInfoContent()
{
var faker = new Faker();
return faker.Lorem.Sentence();
}

private IEnumerable<NormEvent> GetEvents(TimeSpan delayTime)
{
var normEvents = new List<NormEvent>();
Expand Down Expand Up @@ -398,24 +408,85 @@ public void ReceivesFileWithRename()
}
}

[SkippableFact(typeof(IOException))]
public void EnqueuesData()
public static IEnumerable<object[]> GenerateData()
{
var data = new List<object[]>();

var dataContent = GenerateTextContent();
var expectedDataContent = dataContent;
var dataOffset = 0;
var dataLength = dataContent.Length;
data.Add(new object[] { dataContent, expectedDataContent, dataOffset, dataLength });

var infoContent = GenerateInfoContent();
var expectedInfoContent = infoContent;
var infoOffset = 0;
var infoLength = infoContent.Length;
data.Add(new object[] { dataContent, expectedDataContent, dataOffset, dataLength, infoContent, expectedInfoContent, infoOffset, infoLength });

var faker = new Faker();
infoLength = faker.Random.Int(infoContent.Length / 2, infoContent.Length - 1);
expectedInfoContent = infoContent.Substring(infoOffset, infoLength);
data.Add(new object[] { dataContent, expectedDataContent, dataOffset, dataLength, infoContent, expectedInfoContent, infoOffset, infoLength });

infoOffset = faker.Random.Int(1, infoContent.Length - 1 / 2);
infoLength = infoContent.Length - infoOffset;
expectedInfoContent = infoContent.Substring(infoOffset, infoLength);
data.Add(new object[] { dataContent, expectedDataContent, dataOffset, dataLength, infoContent, expectedInfoContent, infoOffset, infoLength });

infoOffset = faker.Random.Int(1, infoContent.Length - 1 / 2);
infoLength = faker.Random.Int(1, infoContent.Length - infoOffset);
expectedInfoContent = infoContent.Substring(infoOffset, infoLength);
data.Add(new object[] { dataContent, expectedDataContent, dataOffset, dataLength, infoContent, expectedInfoContent, infoOffset, infoLength });

dataLength = faker.Random.Int(dataContent.Length / 2, dataContent.Length - 1);
expectedDataContent = dataContent.Substring(dataOffset, dataLength);
data.Add(new object[] { dataContent, expectedDataContent, dataOffset, dataLength });

dataOffset = faker.Random.Int(1, dataContent.Length - 1 / 2);
dataLength = dataContent.Length - dataOffset;
expectedDataContent = dataContent.Substring(dataOffset, dataLength);
data.Add(new object[] { dataContent, expectedDataContent, dataOffset, dataLength });

dataOffset = faker.Random.Int(1, dataContent.Length - 1 / 2);
dataLength = faker.Random.Int(1, dataContent.Length - dataOffset);
expectedDataContent = dataContent.Substring(dataOffset, dataLength);
data.Add(new object[] { dataContent, expectedDataContent, dataOffset, dataLength });

return data;
}

[SkippableTheory(typeof(IOException))]
[MemberData(nameof(GenerateData))]
public void EnqueuesData(string dataContent, string expectedDataContent, int dataOffset, int dataLength, string? infoContent = null, string expectedInfoContent = "", int? infoOffset = null, int? infoLength = null)
{
StartSender();
//Create data to write to the stream
var expectedContent = GenerateTextContent();
byte[] expectedData = Encoding.ASCII.GetBytes(expectedContent);
//Create data to write to enqueue
var data = Encoding.ASCII.GetBytes(dataContent);
var expectedData = Encoding.ASCII.GetBytes(expectedDataContent);
//Create info to enqueue
var info = infoContent != null ? Encoding.ASCII.GetBytes(infoContent) : null;
var expectedInfo = Encoding.ASCII.GetBytes(expectedInfoContent);

try
{
var normData = _normSession.DataEnqueue(expectedData, 0, expectedData.Length);
var normData = infoOffset != null && infoLength != null ?
_normSession.DataEnqueue(data, dataOffset, dataLength, info, infoOffset.Value, infoLength.Value) :
_normSession.DataEnqueue(data, dataOffset, dataLength);
var expectedEventTypes = new List<NormEventType> { NormEventType.NORM_TX_OBJECT_SENT, NormEventType.NORM_TX_QUEUE_EMPTY };
var actualEventTypes = GetEvents().Select(e => e.Type).ToList();
Assert.Equal(expectedEventTypes, actualEventTypes);
var actualData = normData.GetData();
Assert.Equal(expectedData, actualData);
var actualContent = Encoding.ASCII.GetString(actualData);
Assert.Equal(expectedContent, actualContent);
var actualDataContent = Encoding.ASCII.GetString(actualData);
Assert.Equal(expectedDataContent, actualDataContent);
var actualInfo = normData.Info;
Assert.Equal(expectedInfo, actualInfo);
if (actualInfo != null)
{
var actualInfoContent = Encoding.ASCII.GetString(actualInfo);
Assert.Equal(expectedInfoContent, actualInfoContent);
}
}
catch (Exception)
{
Expand All @@ -427,8 +498,81 @@ public void EnqueuesData()
}
}

[SkippableFact(typeof(IOException))]
public void ReceivesData()
public static IEnumerable<object[]> GenerateOutOfRangeData()
{
var data = new List<object[]>();

var dataContent = GenerateTextContent();
var faker = new Faker();
var dataOffset = faker.Random.Int(-dataContent.Length, -1);
var dataLength = dataContent.Length;
data.Add(new object[] { dataContent, dataOffset, dataLength });

dataOffset = faker.Random.Int(dataContent.Length, dataContent.Length * 2);
dataLength = dataContent.Length;
data.Add(new object[] { dataContent, dataOffset, dataLength });

dataOffset = 0;
dataLength = faker.Random.Int(dataContent.Length + 1, dataContent.Length * 2);
data.Add(new object[] { dataContent, dataOffset, dataLength });

dataOffset = dataContent.Length - 1;
dataLength = dataContent.Length;
data.Add(new object[] { dataContent, dataOffset, dataLength });

dataOffset = 0;
dataLength = dataContent.Length;

var infoContent = GenerateInfoContent();
var infoOffset = faker.Random.Int(-infoContent.Length, -1);
var infoLength = infoContent.Length;
data.Add(new object[] { dataContent, dataOffset, dataLength, infoContent, infoOffset, infoLength });

infoOffset = faker.Random.Int(infoContent.Length, infoContent.Length * 2);
infoLength = infoContent.Length;
data.Add(new object[] { dataContent, dataOffset, dataLength, infoContent, infoOffset, infoLength });

infoOffset = 0;
infoLength = faker.Random.Int(infoContent.Length + 1, infoContent.Length * 2);
data.Add(new object[] { dataContent, dataOffset, dataLength, infoContent, infoOffset, infoLength });

infoOffset = infoContent.Length - 1;
infoLength = infoContent.Length;
data.Add(new object[] { dataContent, dataOffset, dataLength, infoContent, infoOffset, infoLength });

return data;
}

[SkippableTheory(typeof(IOException))]
[MemberData(nameof(GenerateOutOfRangeData))]
public void EnqueuesDataThrowsExceptionWhenOutOfRange(string dataContent, int dataOffset, int dataLength, string? infoContent = null, int? infoOffset = null, int? infoLength = null)
{
StartSender();
//Create data to enqueue
var data = Encoding.ASCII.GetBytes(dataContent);
//Create info to enqueue
var info = infoContent != null ? Encoding.ASCII.GetBytes(infoContent) : null;

try
{
Assert.Throws<ArgumentOutOfRangeException>(() =>
infoOffset != null && infoLength != null ?
_normSession.DataEnqueue(data, dataOffset, dataLength, info, infoOffset.Value, infoLength.Value) :
_normSession.DataEnqueue(data, dataOffset, dataLength));
}
catch (Exception)
{
throw;
}
finally
{
StopSender();
}
}

[SkippableTheory(typeof(IOException))]
[MemberData(nameof(GenerateData))]
public void ReceivesData(string content, string expectedDataContent, int dataOffset, int dataLength, string? infoContent = null, string expectedInfoContent = "", int? infoOffset = null, int? infoLength = null)
{
_normSession.SetLoopback(true);
StartSender();
Expand All @@ -441,12 +585,17 @@ public void ReceivesData()
_normInstance.SetCacheDirectory(cachePath);

//Create data to be sent
var expectedContent = GenerateTextContent();
var expectedData = Encoding.ASCII.GetBytes(expectedContent);
var data = Encoding.ASCII.GetBytes(content);
var expectedData = Encoding.ASCII.GetBytes(expectedDataContent);
//Create info to be sent
var info = infoContent != null ? Encoding.ASCII.GetBytes(infoContent) : null;
var expectedInfo = Encoding.ASCII.GetBytes(expectedInfoContent);

try
{
var normData = _normSession.DataEnqueue(expectedData, 0, expectedData.Length);
var normData = infoOffset != null && infoLength != null ?
_normSession.DataEnqueue(data, dataOffset, dataLength, info, infoOffset.Value, infoLength.Value) :
_normSession.DataEnqueue(data, dataOffset, dataLength);
var expectedEventTypes = new List<NormEventType>
{
NormEventType.NORM_REMOTE_SENDER_NEW,
Expand All @@ -468,8 +617,15 @@ public void ReceivesData()

var actualData = actualNormData.GetData();
Assert.Equal(expectedData, actualData);
var actualContent = Encoding.ASCII.GetString(actualData);
Assert.Equal(expectedContent, actualContent);
var actualDataContent = Encoding.ASCII.GetString(actualData);
Assert.Equal(expectedDataContent, actualDataContent);
var actualInfo = normData.Info;
Assert.Equal(expectedInfo, actualInfo);
if (actualInfo != null)
{
var actualInfoContent = Encoding.ASCII.GetString(actualInfo);
Assert.Equal(expectedInfoContent, actualInfoContent);
}
}
catch (Exception)
{
Expand Down

0 comments on commit 662e1fa

Please sign in to comment.