diff --git a/src/dotnet/design/Mil/Navy/Nrl/Norm/NormApi.puml b/src/dotnet/design/Mil/Navy/Nrl/Norm/NormApi.puml index c23811a..027ac46 100644 --- a/src/dotnet/design/Mil/Navy/Nrl/Norm/NormApi.puml +++ b/src/dotnet/design/Mil/Navy/Nrl/Norm/NormApi.puml @@ -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 diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/NormApi.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/NormApi.cs index 5e969e9..e5f56b8 100644 --- a/src/dotnet/src/Mil.Navy.Nrl.Norm/NormApi.cs +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/NormApi.cs @@ -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); - /// - /// This function enqueues a segment of application memory space for transmission within the specified NORM sessionHandle. - /// - /// Used to identify application in the NormSession. - /// The data parameter must be a managed buffer to be transmitted. - /// The dataLen parameter indicates the quantity of data to transmit. - /// 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. - /// 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 - /// A NormObjectHandle is returned which the application may use in other NORM API calls as needed. - 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; - } - /// /// 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. diff --git a/src/dotnet/src/Mil.Navy.Nrl.Norm/NormSession.cs b/src/dotnet/src/Mil.Navy.Nrl.Norm/NormSession.cs index f4b9177..b7a318b 100644 --- a/src/dotnet/src/Mil.Navy.Nrl.Norm/NormSession.cs +++ b/src/dotnet/src/Mil.Navy.Nrl.Norm/NormSession.cs @@ -1,4 +1,5 @@ -using System.Text; +using System.Runtime.InteropServices; +using System.Text; namespace Mil.Navy.Nrl.Norm { @@ -497,6 +498,7 @@ public NormFile FileEnqueue(string filename, byte[] info, int infoOffset, int in /// Size of the message. /// A NormData is returned which the application may use in other NORM API calls as needed. /// Thrown when NormDataEnqueue() returns NORM_OBJECT_INVALID, indicating the failure to enqueue data. + /// Thrown when the data offset or data length are outside of the data buffer. public NormData DataEnqueue(byte[] dataBuffer, int dataOffset, int dataLength) { return DataEnqueue(dataBuffer, dataOffset, dataLength, null, 0, 0); @@ -514,24 +516,46 @@ public NormData DataEnqueue(byte[] dataBuffer, int dataOffset, int dataLength) /// The optional info and infoLength parameters are used to associate NORM_INFO content with the sent transport object. /// A NormData is returned which the application may use in other NORM API calls as needed. /// Thrown when NormDataEnqueue() returns NORM_OBJECT_INVALID, indicating the failure to enqueue data. + /// Thrown when the data offset, data length, info offset or info length are outside of the associated buffer. 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); } diff --git a/src/dotnet/tests/Mil.Navy.Nrl.Norm.IntegrationTests/NormSessionTests.cs b/src/dotnet/tests/Mil.Navy.Nrl.Norm.IntegrationTests/NormSessionTests.cs index 1470c07..a478299 100644 --- a/src/dotnet/tests/Mil.Navy.Nrl.Norm.IntegrationTests/NormSessionTests.cs +++ b/src/dotnet/tests/Mil.Navy.Nrl.Norm.IntegrationTests/NormSessionTests.cs @@ -186,12 +186,22 @@ public void StopsReceiver() /// Generates text content /// /// The generated text content - private string GenerateTextContent() + private static string GenerateTextContent() { var faker = new Faker(); return faker.Lorem.Paragraph(); } + /// + /// Generates info content + /// + /// The generated info content + private static string GenerateInfoContent() + { + var faker = new Faker(); + return faker.Lorem.Sentence(); + } + private IEnumerable GetEvents(TimeSpan delayTime) { var normEvents = new List(); @@ -398,24 +408,85 @@ public void ReceivesFileWithRename() } } - [SkippableFact(typeof(IOException))] - public void EnqueuesData() + public static IEnumerable GenerateData() + { + var data = new List(); + + 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.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) { @@ -427,8 +498,81 @@ public void EnqueuesData() } } - [SkippableFact(typeof(IOException))] - public void ReceivesData() + public static IEnumerable GenerateOutOfRangeData() + { + var data = new List(); + + 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(() => + 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(); @@ -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.NORM_REMOTE_SENDER_NEW, @@ -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) {