Skip to content

Commit

Permalink
Throw when native functions are missing.
Browse files Browse the repository at this point in the history
  • Loading branch information
mgnsm committed May 10, 2022
1 parent 1b40788 commit 13fffc3
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 54 deletions.
2 changes: 2 additions & 0 deletions Source/Millistream.Streaming/Message.ByteOverloads.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public bool AddString(uint tag, ReadOnlySpan<byte> value, int length)
ThrowIfDisposed();
if (value != null && length < 0)
return false;
ThrowIfNativeFunctionIsMissing(_nativeImplementation.mdf_message_add_string2, nameof(_nativeImplementation.mdf_message_add_string2));
fixed (byte* bytes = value)
return _nativeImplementation.mdf_message_add_string2(Handle, tag, (IntPtr)bytes, length) == 1;
}
Expand Down Expand Up @@ -186,6 +187,7 @@ public bool AddList(Field tag, ReadOnlySpan<byte> value) =>
public bool Deserialize(ReadOnlySpan<byte> data)
{
ThrowIfDisposed();
ThrowIfNativeFunctionIsMissing(_nativeImplementation.mdf_message_deserialize, nameof(_nativeImplementation.mdf_message_deserialize));
fixed (byte* bytes = data)
return _nativeImplementation.mdf_message_deserialize(Handle, (IntPtr)bytes) == 1;
}
Expand Down
28 changes: 28 additions & 0 deletions Source/Millistream.Streaming/Message.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ public Message()
public Message(string nativeLibraryPath)
: this(nativeLibraryPath, true) { }

internal Message(NativeImplementation nativeImplementation)
{
_nativeImplementation = nativeImplementation ?? throw new ArgumentNullException(nameof(nativeImplementation));
Handle = _nativeImplementation.mdf_message_create();
}

private Message(string nativeLibraryPath, bool validateArgument)
{
_nativeImplementation = string.IsNullOrEmpty(nativeLibraryPath) ?
Expand All @@ -48,6 +54,7 @@ private Message(string nativeLibraryPath, bool validateArgument)
/// The zlib compression level used for the <see cref="AddString(uint, string)"/> and <see cref="AddString(uint, string, int)"/> methods.
/// </summary>
/// <exception cref="InvalidOperationException">The installed version of the native library doesn't support setting the zlib compression level.</exception>
/// <exception cref="InvalidOperationException">The installed version of the native library doesn't include the mdf_message_set_property function.</exception>
/// <exception cref="ObjectDisposedException">The <see cref="Message"/> instance has been disposed.</exception>
public CompressionLevel CompressionLevel
{
Expand All @@ -59,6 +66,7 @@ public CompressionLevel CompressionLevel
set
{
ThrowIfDisposed();
ThrowIfNativeFunctionIsMissing(_nativeImplementation.mdf_message_set_property, nameof(_nativeImplementation.mdf_message_set_property));
if (_nativeImplementation.mdf_message_set_property(Handle, MDF_MSG_OPTION.MDF_MSG_OPT_COMPRESSION, (int)value) == 1)
_compressionLevel = value;
}
Expand Down Expand Up @@ -93,6 +101,7 @@ public int ActiveCount
/// <summary>
/// Enables or disables the UTF-8 validation performed in <see cref="AddString(uint, string)"/> and <see cref="AddString(uint, string, int)"/>. It's enabled by default.
/// </summary>
/// <exception cref="InvalidOperationException">The installed version of the native library doesn't include the mdf_message_set_property function.</exception>
/// <exception cref="ObjectDisposedException">The <see cref="Message"/> instance has been disposed.</exception>
public bool Utf8Validation
{
Expand All @@ -104,6 +113,7 @@ public bool Utf8Validation
set
{
ThrowIfDisposed();
ThrowIfNativeFunctionIsMissing(_nativeImplementation.mdf_message_set_property, nameof(_nativeImplementation.mdf_message_set_property));
if (_nativeImplementation.mdf_message_set_property(Handle, MDF_MSG_OPTION.MDF_MSG_OPT_UTF8, value ? 1 : 0) == 1)
_utf8Validation = value;
}
Expand Down Expand Up @@ -183,6 +193,7 @@ public bool AddInt64(uint tag, long value, int decimals)
if (decimals < 0 || decimals > 19)
throw new ArgumentException($"{nameof(decimals)} cannot be smaller than 0 or greater than 19.", nameof(decimals));
ThrowIfDisposed();
ThrowIfNativeFunctionIsMissing(_nativeImplementation.mdf_message_add_int, nameof(_nativeImplementation.mdf_message_add_int));
return _nativeImplementation.mdf_message_add_int(Handle, tag, value, decimals) == 1;
}

Expand Down Expand Up @@ -216,6 +227,7 @@ public bool AddUInt64(uint tag, ulong value, int decimals)
if (decimals < 0 || decimals > 19)
throw new ArgumentException($"{nameof(decimals)} cannot be smaller than 0 or greater than 19.", nameof(decimals));
ThrowIfDisposed();
ThrowIfNativeFunctionIsMissing(_nativeImplementation.mdf_message_add_uint, nameof(_nativeImplementation.mdf_message_add_uint));
return _nativeImplementation.mdf_message_add_uint(Handle, tag, value, decimals) == 1;
}

Expand Down Expand Up @@ -270,6 +282,7 @@ public bool AddString(uint tag, string value)
public bool AddString(uint tag, string value, int length)
{
ThrowIfDisposed();
ThrowIfNativeFunctionIsMissing(_nativeImplementation.mdf_message_add_string2, nameof(_nativeImplementation.mdf_message_add_string2));
if (value == null)
return _nativeImplementation.mdf_message_add_string2(Handle, tag, IntPtr.Zero, length) == 1;

Expand Down Expand Up @@ -354,6 +367,7 @@ public bool AddDate(Field tag, string value) =>
public bool AddDate(uint tag, int year, int month, int day)
{
ThrowIfDisposed();
ThrowIfNativeFunctionIsMissing(_nativeImplementation.mdf_message_add_date2, nameof(_nativeImplementation.mdf_message_add_date2));
return _nativeImplementation.mdf_message_add_date2(Handle, tag, year, month, day) == 1;
}

Expand Down Expand Up @@ -417,6 +431,7 @@ public bool AddTime(Field tag, string value) =>
public bool AddTime2(uint tag, int hour, int minute, int second, int millisecond)
{
ThrowIfDisposed();
ThrowIfNativeFunctionIsMissing(_nativeImplementation.mdf_message_add_time2, nameof(_nativeImplementation.mdf_message_add_time2));
return _nativeImplementation.mdf_message_add_time2(Handle, tag, hour, minute, second, millisecond) == 1;
}

Expand Down Expand Up @@ -450,6 +465,7 @@ public bool AddTime2(Field tag, int hour, int minute, int second, int millisecon
public bool AddTime3(uint tag, int hour, int minute, int second, int nanosecond)
{
ThrowIfDisposed();
ThrowIfNativeFunctionIsMissing(_nativeImplementation.mdf_message_add_time3, nameof(_nativeImplementation.mdf_message_add_time3));
return _nativeImplementation.mdf_message_add_time3(Handle, tag, hour, minute, second, nanosecond) == 1;
}

Expand Down Expand Up @@ -546,6 +562,8 @@ public static bool Move(Message source, Message destination, ulong sourceInsref,
if (source == null)
throw new ArgumentNullException(nameof(source));

ThrowIfNativeFunctionIsMissing(source._nativeImplementation.mdf_message_move, nameof(source._nativeImplementation.mdf_message_move));

return source._nativeImplementation.mdf_message_move(source.Handle, destination?.Handle ?? IntPtr.Zero, sourceInsref, destinationInsRef) == 1;
}

Expand All @@ -560,6 +578,7 @@ public static bool Move(Message source, Message destination, ulong sourceInsref,
public bool Serialize(out IntPtr result)
{
ThrowIfDisposed();
ThrowIfNativeFunctionIsMissing(_nativeImplementation.mdf_message_serialize, nameof(_nativeImplementation.mdf_message_serialize));
result = IntPtr.Zero;
return _nativeImplementation.mdf_message_serialize(Handle, ref result) == 1;
}
Expand All @@ -579,6 +598,7 @@ public bool Deserialize(string data)
throw new ArgumentNullException(nameof(data));

ThrowIfDisposed();
ThrowIfNativeFunctionIsMissing(_nativeImplementation.mdf_message_deserialize, nameof(_nativeImplementation.mdf_message_deserialize));
byte* bytes = stackalloc byte[data.Length + 1];
if (!TryGetAsciiBytes(data, bytes))
return false;
Expand All @@ -596,6 +616,7 @@ public bool Deserialize(string data)
public bool Deserialize(IntPtr data)
{
ThrowIfDisposed();
ThrowIfNativeFunctionIsMissing(_nativeImplementation.mdf_message_deserialize, nameof(_nativeImplementation.mdf_message_deserialize));
return _nativeImplementation.mdf_message_deserialize(Handle, data) == 1;
}

Expand All @@ -619,6 +640,13 @@ private void ThrowIfDisposed()
throw new ObjectDisposedException(typeof(Message).FullName);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void ThrowIfNativeFunctionIsMissing(void* function, string name)
{
if (function == default)
throw new InvalidOperationException($"The installed version of the native library doesn't include the {name} function.");
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryGetAsciiBytes(string value, byte* bytes)
{
Expand Down
75 changes: 64 additions & 11 deletions Tests/Millistream.Streaming.UnitTests/MessageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ public void DisposeTest()

[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void CreateMessageWithNoNativeLibraryPathTest () => new Message(default);
public void CreateMessageWithNoNativeLibraryPathTest () => new Message(default(string));

[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
Expand Down Expand Up @@ -491,6 +491,69 @@ public void CannotCallMethodsAfterDisposeTest()
CatchObjectDisposedException(() => disposedMessage.Deserialize("ABC"));
CatchObjectDisposedException(() => disposedMessage.Deserialize(new IntPtr(123)));
CatchObjectDisposedException(() => disposedMessage.Deserialize(Encoding.ASCII.GetBytes("ABC")));

static void CatchObjectDisposedException(Action action)
{
try
{
action();
Assert.Fail($"No expected {nameof(ObjectDisposedException)} was thrown.");
}
catch (ObjectDisposedException) { }
}
}

[TestMethod]
public unsafe void CannotSetPropertyWhenNativeFunctionIsMissingTest()
{
NativeImplementation nativeImplementation = new(default);
nativeImplementation.mdf_message_set_property = default;
nativeImplementation.mdf_message_add_int = default;
nativeImplementation.mdf_message_add_uint = default;
nativeImplementation.mdf_message_add_string2 = default;
nativeImplementation.mdf_message_add_date2 = default;
nativeImplementation.mdf_message_add_time2 = default;
nativeImplementation.mdf_message_add_time3 = default;
nativeImplementation.mdf_message_move = default;
nativeImplementation.mdf_message_serialize = default;
nativeImplementation.mdf_message_deserialize = default;

using Message message = new(nativeImplementation);
CatchInvalidOperationException(() => message.CompressionLevel = CompressionLevel.Z_BEST_COMPRESSION, nameof(nativeImplementation.mdf_message_set_property));
CatchInvalidOperationException(() => message.Utf8Validation = false, nameof(nativeImplementation.mdf_message_set_property));
CatchInvalidOperationException(() => message.AddInt64(default(uint), default, default), nameof(nativeImplementation.mdf_message_add_int));
CatchInvalidOperationException(() => message.AddInt64(default(Field), default, default), nameof(nativeImplementation.mdf_message_add_int));
CatchInvalidOperationException(() => message.AddUInt64(default(uint), default, default), nameof(nativeImplementation.mdf_message_add_uint));
CatchInvalidOperationException(() => message.AddUInt64(default(Field), default, default), nameof(nativeImplementation.mdf_message_add_uint));
CatchInvalidOperationException(() => message.AddString(default(uint), default(string), default), nameof(nativeImplementation.mdf_message_add_string2));
CatchInvalidOperationException(() => message.AddString(default(Field), default(string), default), nameof(nativeImplementation.mdf_message_add_string2));
CatchInvalidOperationException(() => message.AddString(default(uint), default(ReadOnlySpan<byte>), default), nameof(nativeImplementation.mdf_message_add_string2));
CatchInvalidOperationException(() => message.AddString(default(Field), default(ReadOnlySpan<byte>), default), nameof(nativeImplementation.mdf_message_add_string2));
CatchInvalidOperationException(() => message.AddDate(default(uint), default, default, default), nameof(nativeImplementation.mdf_message_add_date2));
CatchInvalidOperationException(() => message.AddDate(default(Field), default, default, default), nameof(nativeImplementation.mdf_message_add_date2));
CatchInvalidOperationException(() => message.AddTime2(default(uint), default, default, default, default), nameof(nativeImplementation.mdf_message_add_time2));
CatchInvalidOperationException(() => message.AddTime2(default(Field), default, default, default, default), nameof(nativeImplementation.mdf_message_add_time2));
CatchInvalidOperationException(() => message.AddTime3(default(uint), default, default, default, default), nameof(nativeImplementation.mdf_message_add_time3));
CatchInvalidOperationException(() => message.AddTime3(default(Field), default, default, default, default), nameof(nativeImplementation.mdf_message_add_time3));
CatchInvalidOperationException(() => Message.Move(message, default, default, default), nameof(nativeImplementation.mdf_message_move));
CatchInvalidOperationException(() => message.Serialize(out _), nameof(nativeImplementation.mdf_message_serialize));
CatchInvalidOperationException(() => message.Deserialize("..."), nameof(nativeImplementation.mdf_message_deserialize));
CatchInvalidOperationException(() => message.Deserialize(default(ReadOnlySpan<byte>)), nameof(nativeImplementation.mdf_message_deserialize));



static void CatchInvalidOperationException(Action action, string missingFunctionName)
{
try
{
action();
Assert.Fail($"No expected {nameof(InvalidOperationException)} was thrown.");
}
catch (InvalidOperationException ex)
{
Assert.AreEqual($"The installed version of the native library doesn't include the {missingFunctionName} function.", ex.Message);
}
}
}

private static void Compare(string expectedValue, IntPtr actualValue)
Expand All @@ -506,15 +569,5 @@ private static Message GetDisposedMessage()
message.Dispose();
return message;
}

private static void CatchObjectDisposedException(Action action)
{
try
{
action();
Assert.Fail();
}
catch (ObjectDisposedException) { }
}
}
}
Loading

0 comments on commit 13fffc3

Please sign in to comment.