From 3fc1eaf0df0a27ca9f6d5015db0c2c9fef146783 Mon Sep 17 00:00:00 2001 From: Peter Kurfer Date: Mon, 15 Feb 2021 08:46:19 +0100 Subject: [PATCH] Setup project and implement first draft of audit file reader --- .editorconfig | 10 ++ .gitignore | 6 ++ INetMock.sln | 36 ++++++++ src/INetMock.Client/Audit/Details.cs | 89 ++++++++++++++++++ src/INetMock.Client/Audit/Event.cs | 91 +++++++++++++++++++ .../Audit/EventServerStreamReader.cs | 28 ++++++ src/INetMock.Client/Audit/GenericReader.cs | 45 +++++++++ src/INetMock.Client/Audit/IEventReader.cs | 22 +++++ .../Audit/INetMockHttpHeaders.cs | 18 ++++ .../Audit/IProtoEventReader.cs | 11 +++ src/INetMock.Client/Audit/ProtoReader.cs | 68 ++++++++++++++ src/INetMock.Client/Audit/TypedReader.cs | 47 ++++++++++ src/INetMock.Client/INetMock.Client.csproj | 26 ++++++ src/INetMock.Client/proto | 1 + .../Audit/GenericReaderTest.cs | 57 ++++++++++++ .../Audit/TypedReaderTest.cs | 45 +++++++++ test/INetMock.Client.Test/Hex/Converter.cs | 15 +++ .../INetMock.Client.Test.csproj | 24 +++++ 18 files changed, 639 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 INetMock.sln create mode 100644 src/INetMock.Client/Audit/Details.cs create mode 100644 src/INetMock.Client/Audit/Event.cs create mode 100644 src/INetMock.Client/Audit/EventServerStreamReader.cs create mode 100644 src/INetMock.Client/Audit/GenericReader.cs create mode 100644 src/INetMock.Client/Audit/IEventReader.cs create mode 100644 src/INetMock.Client/Audit/INetMockHttpHeaders.cs create mode 100644 src/INetMock.Client/Audit/IProtoEventReader.cs create mode 100644 src/INetMock.Client/Audit/ProtoReader.cs create mode 100644 src/INetMock.Client/Audit/TypedReader.cs create mode 100644 src/INetMock.Client/INetMock.Client.csproj create mode 120000 src/INetMock.Client/proto create mode 100644 test/INetMock.Client.Test/Audit/GenericReaderTest.cs create mode 100644 test/INetMock.Client.Test/Audit/TypedReaderTest.cs create mode 100644 test/INetMock.Client.Test/Hex/Converter.cs create mode 100644 test/INetMock.Client.Test/INetMock.Client.Test.csproj diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..13fb40e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 + +[*.cs] +indent_style = space +indent_size = 4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f82761d --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +bin/ +obj/ +.idea/ +.vscode/ + +INetMock.sln.DotSettings.user diff --git a/INetMock.sln b/INetMock.sln new file mode 100644 index 0000000..a548414 --- /dev/null +++ b/INetMock.sln @@ -0,0 +1,36 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "INetMock.Client", "src\INetMock.Client\INetMock.Client.csproj", "{4B1CFFB0-23B5-4238-BFCC-4155F459FF8E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{DF7A15B0-72D0-4BA1-9C9E-424343F6D530}" +ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + .gitignore = .gitignore +EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{07100561-E3C0-4B95-92E1-D2D3BA12C3A6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{E7144CE4-3E55-431B-8835-E804C664A694}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "INetMock.Client.Test", "test\INetMock.Client.Test\INetMock.Client.Test.csproj", "{BFC06300-C82F-4A24-A10E-5C7D66208280}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4B1CFFB0-23B5-4238-BFCC-4155F459FF8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4B1CFFB0-23B5-4238-BFCC-4155F459FF8E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4B1CFFB0-23B5-4238-BFCC-4155F459FF8E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4B1CFFB0-23B5-4238-BFCC-4155F459FF8E}.Release|Any CPU.Build.0 = Release|Any CPU + {BFC06300-C82F-4A24-A10E-5C7D66208280}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BFC06300-C82F-4A24-A10E-5C7D66208280}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BFC06300-C82F-4A24-A10E-5C7D66208280}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BFC06300-C82F-4A24-A10E-5C7D66208280}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {4B1CFFB0-23B5-4238-BFCC-4155F459FF8E} = {07100561-E3C0-4B95-92E1-D2D3BA12C3A6} + {BFC06300-C82F-4A24-A10E-5C7D66208280} = {E7144CE4-3E55-431B-8835-E804C664A694} + EndGlobalSection +EndGlobal diff --git a/src/INetMock.Client/Audit/Details.cs b/src/INetMock.Client/Audit/Details.cs new file mode 100644 index 0000000..3adf1a0 --- /dev/null +++ b/src/INetMock.Client/Audit/Details.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using Google.Protobuf.WellKnownTypes; +using INetMock.Client.Audit.Details; + +namespace INetMock.Client.Audit +{ + public abstract record EventDetails; + public record EmptyDetails : EventDetails; + + public record GenericDetails : EventDetails + { + private readonly Any? _detailsAny; + public GenericDetails() + { + _detailsAny = null; + } + + public GenericDetails(Any? any) + { + _detailsAny = any; + } + + public static implicit operator HttpDetails(GenericDetails gd) + { + if (gd._detailsAny == null || gd._detailsAny.Value == null) return new(); + //if(!gd._detailsAny.TypeUrl.EndsWith(HTTPDetailsEntity.Descriptor.FullName)) throw new InvalidOperationException(); + return new HttpDetails(gd._detailsAny); + } + + public static implicit operator DnsDetails(GenericDetails gd) + { + if (gd._detailsAny == null || gd._detailsAny.Value == null) return new(); + //if(!gd._detailsAny.TypeUrl.EndsWith(HTTPDetailsEntity.Descriptor.FullName)) throw new InvalidOperationException(); + return new DnsDetails(gd._detailsAny); + } + } + + public record HttpDetails : EventDetails + { + public HttpDetails() + { + + } + + public HttpDetails(Any? any) + { + if (any == null || any.Value == null) return; + + var detailsEntity = HTTPDetailsEntity.Parser.ParseFrom(any.Value); + + Method = new HttpMethod(detailsEntity.Method.ToString()); + Host = detailsEntity.Host; + Uri = detailsEntity.Uri; + Proto = detailsEntity.Proto; + Headers = new INetMockHttpHeaders(detailsEntity.Headers); + } + + public HttpMethod Method { get; init; } + public string Host { get; init; } + public string Uri { get; init; } + public string Proto { get; init; } + public HttpHeaders Headers { get; init; } + } + + public record DnsDetails : EventDetails + { + public DnsDetails() + { + + } + + public DNSOpCode OpCode { get; init; } + + public IReadOnlyList Questions { get; init; } + + public DnsDetails(Any? any) + { + if(any == null || any.Value == null) return; + + var entity = DNSDetailsEntity.Parser.ParseFrom(any.Value); + OpCode = entity.Opcode; + Questions = entity.Questions; + } + } +} diff --git a/src/INetMock.Client/Audit/Event.cs b/src/INetMock.Client/Audit/Event.cs new file mode 100644 index 0000000..df66728 --- /dev/null +++ b/src/INetMock.Client/Audit/Event.cs @@ -0,0 +1,91 @@ +using System; +using System.Net; + +namespace INetMock.Client.Audit +{ + public record Event where T : EventDetails, new() + { + public Event() + { + + } + + public Event(EventEntity entity) + { + if (entity == null) throw new ArgumentNullException(nameof(entity)); + + var details = (new T(), entity.Application) switch + { + (GenericDetails, _) => new GenericDetails(entity.ProtocolDetails) as T, + (_, AppProtocol.Dns) => new DnsDetails(entity.ProtocolDetails) as T, + (_, AppProtocol.Http or AppProtocol.HttpProxy) => new HttpDetails(entity.ProtocolDetails) as T, + (_, _) => new EmptyDetails() as T + }; + + Id = entity.Id; + Timestamp = entity.Timestamp.ToDateTimeOffset(); + Transport = entity.Transport; + SourceIp = new IPAddress(entity.SourceIP.Span); + SourcePort = Convert.ToUInt16(entity.SourcePort); + DestinationIp = new IPAddress(entity.DestinationIP.Span); + DestinationPort = Convert.ToUInt16(entity.DestinationPort); + TlsDetails = entity.Tls; + Application = entity.Application; + Details = details; + } + + public long Id { get; init; } + public DateTimeOffset Timestamp { get; init; } + public TransportProtocol Transport { get; init; } + public AppProtocol Application { get; init; } + public IPAddress SourceIp { get; init; } + public IPAddress DestinationIp { get; init; } + public ushort SourcePort { get; init; } + public ushort DestinationPort { get; init; } + public TLSDetailsEntity TlsDetails { get; init; } + public T? Details { get; init; } + + public bool CanConvert() where TTarget : EventDetails, new() + { + if (Details == null) return false; + + return (new TTarget(), Application) switch + { + (DnsDetails, AppProtocol.Dns) => true, + (HttpDetails, AppProtocol.Http or AppProtocol.HttpProxy) => true, + (_, _) => false + }; + } + + public static explicit operator Event?(Event ge) + { + T? details = null; + if (ge.Details != null) + { + details = ge.Application switch + { + AppProtocol.Dns => (DnsDetails) ge.Details as T, + AppProtocol.Http or AppProtocol.HttpProxy => (HttpDetails) ge.Details as T, + _ => new EmptyDetails() as T + }; + } + + + if (details == null) return null; + + return new() + { + Id = ge.Id, + Timestamp = ge.Timestamp, + Transport = ge.Transport, + SourceIp = ge.SourceIp, + SourcePort = ge.SourcePort, + DestinationIp = ge.DestinationIp, + DestinationPort = ge.DestinationPort, + TlsDetails = ge.TlsDetails, + Application = ge.Application, + Details = details + }; + } + } +} diff --git a/src/INetMock.Client/Audit/EventServerStreamReader.cs b/src/INetMock.Client/Audit/EventServerStreamReader.cs new file mode 100644 index 0000000..ce754a9 --- /dev/null +++ b/src/INetMock.Client/Audit/EventServerStreamReader.cs @@ -0,0 +1,28 @@ +using System.Threading; +using System.Threading.Tasks; +using Grpc.Core; + +namespace INetMock.Client.Audit +{ + public sealed class EventServerStreamReader : IProtoEventReader + { + private readonly IAsyncStreamReader _asyncEventStream; + + public EventServerStreamReader(IAsyncStreamReader asyncEventStream) + { + _asyncEventStream = asyncEventStream; + } + + public async Task ReadAsync(CancellationToken token = default) + { + if (!await _asyncEventStream.MoveNext(token)) return null; + return _asyncEventStream.Current; + } + + public void Dispose() + { + } + + public ValueTask DisposeAsync() => ValueTask.CompletedTask; + } +} diff --git a/src/INetMock.Client/Audit/GenericReader.cs b/src/INetMock.Client/Audit/GenericReader.cs new file mode 100644 index 0000000..2d8dcbf --- /dev/null +++ b/src/INetMock.Client/Audit/GenericReader.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace INetMock.Client.Audit +{ + public sealed class GenericReader : IEventReader + { + private readonly IProtoEventReader _reader; + + public GenericReader(IProtoEventReader reader) + { + _reader = reader; + } + + public async IAsyncEnumerable> ReadAllAsync([EnumeratorCancellation] CancellationToken token = default) + { + while (true) + { + var ev = await ReadAsync(token); + if (ev == null) + { + yield break; + } + + yield return ev; + } + } + + public async Task?> ReadAsync(CancellationToken token = default) + { + var entity = await _reader.ReadAsync(token); + if (entity == null) return null; + return new Event(entity); + } + + public ValueTask DisposeAsync() => _reader.DisposeAsync(); + + public void Dispose() + { + _reader.Dispose(); + } + } +} diff --git a/src/INetMock.Client/Audit/IEventReader.cs b/src/INetMock.Client/Audit/IEventReader.cs new file mode 100644 index 0000000..bcff725 --- /dev/null +++ b/src/INetMock.Client/Audit/IEventReader.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace INetMock.Client.Audit +{ + public interface IEventReader: IDisposable, IAsyncDisposable where T : EventDetails, new() + { + IAsyncEnumerable> ReadAllAsync(CancellationToken token = default); + + /// + /// + /// + /// + /// + /// An event as long as underlying stream has data. + /// When the end of the stream has been reached it will return null. + /// + Task?> ReadAsync(CancellationToken token = default); + } +} diff --git a/src/INetMock.Client/Audit/INetMockHttpHeaders.cs b/src/INetMock.Client/Audit/INetMockHttpHeaders.cs new file mode 100644 index 0000000..69e1fcf --- /dev/null +++ b/src/INetMock.Client/Audit/INetMockHttpHeaders.cs @@ -0,0 +1,18 @@ +using System.Net.Http.Headers; +using Google.Protobuf.Collections; +using INetMock.Client.Audit.Details; + +namespace INetMock.Client.Audit +{ + internal class INetMockHttpHeaders : HttpHeaders + { + internal INetMockHttpHeaders(MapField headers) + { + foreach (var (key, values) in headers) + { + if (string.IsNullOrEmpty(key) || values == null) continue; + Add(key, values.Values); + } + } + } +} diff --git a/src/INetMock.Client/Audit/IProtoEventReader.cs b/src/INetMock.Client/Audit/IProtoEventReader.cs new file mode 100644 index 0000000..01e434e --- /dev/null +++ b/src/INetMock.Client/Audit/IProtoEventReader.cs @@ -0,0 +1,11 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace INetMock.Client.Audit +{ + public interface IProtoEventReader : IDisposable, IAsyncDisposable + { + Task ReadAsync(CancellationToken token = default); + } +} diff --git a/src/INetMock.Client/Audit/ProtoReader.cs b/src/INetMock.Client/Audit/ProtoReader.cs new file mode 100644 index 0000000..b2534d9 --- /dev/null +++ b/src/INetMock.Client/Audit/ProtoReader.cs @@ -0,0 +1,68 @@ +using System.Buffers; +using System.Buffers.Binary; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Google.Protobuf; + +namespace INetMock.Client.Audit +{ + public sealed class ProtoReader : IProtoEventReader + { + private readonly MemoryPool _memoryPool; + private readonly Stream _sourceStream; + private readonly bool _keepStreamOpen; + + public ProtoReader(Stream sourceStream, bool keepStreamOpen = false) + { + _memoryPool = MemoryPool.Shared; + _sourceStream = sourceStream; + _keepStreamOpen = keepStreamOpen; + } + + public async Task ReadAsync(CancellationToken token = default) + { + using var rentedLengthMem = _memoryPool.Rent(4); + var lengthMem = rentedLengthMem.Memory.Slice(0, 4); + var read = await _sourceStream.ReadAsync(lengthMem, token); + if (read != 4) + { + return null; + } + + var messageLength = BinaryPrimitives.ReadInt32BigEndian(lengthMem.Span); + using var rentedMsgMem = _memoryPool.Rent(messageLength); + var msgMem = rentedMsgMem.Memory.Slice(0, messageLength); + read = await _sourceStream.ReadAsync(msgMem, token); + if (read != messageLength) + { + return null; + } + + var entity = new EventEntity(); + entity.MergeFrom(msgMem.ToArray()); + + return entity; + } + + public async ValueTask DisposeAsync() + { + if (!_keepStreamOpen) + { + await _sourceStream.DisposeAsync(); + } + + _memoryPool.Dispose(); + } + + public void Dispose() + { + if (!_keepStreamOpen) + { + _sourceStream?.Dispose(); + } + + _memoryPool?.Dispose(); + } + } +} diff --git a/src/INetMock.Client/Audit/TypedReader.cs b/src/INetMock.Client/Audit/TypedReader.cs new file mode 100644 index 0000000..50ac00e --- /dev/null +++ b/src/INetMock.Client/Audit/TypedReader.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace INetMock.Client.Audit +{ + public sealed class TypedReader : IEventReader where T : EventDetails, new() + { + private readonly IProtoEventReader _reader; + + public TypedReader(IProtoEventReader reader) + { + _reader = reader; + } + + public async IAsyncEnumerable> ReadAllAsync([EnumeratorCancellation] CancellationToken token = default) + { + while (true) + { + var ev = await ReadAsync(token); + if (ev == null) + { + yield break; + } + + yield return ev; + } + } + + public async Task?> ReadAsync(CancellationToken token = default) + { + do + { + var entity = await _reader.ReadAsync(token); + if (entity == null) return null; + var parsed = new Event(entity); + if (parsed.Details == null) continue; + return parsed; + } while (true); + } + + public void Dispose() => _reader.Dispose(); + + public ValueTask DisposeAsync() => _reader.DisposeAsync(); + } +} diff --git a/src/INetMock.Client/INetMock.Client.csproj b/src/INetMock.Client/INetMock.Client.csproj new file mode 100644 index 0000000..b33a870 --- /dev/null +++ b/src/INetMock.Client/INetMock.Client.csproj @@ -0,0 +1,26 @@ + + + + net5.0 + enable + latest + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/src/INetMock.Client/proto b/src/INetMock.Client/proto new file mode 120000 index 0000000..8463861 --- /dev/null +++ b/src/INetMock.Client/proto @@ -0,0 +1 @@ +../../api/proto/ \ No newline at end of file diff --git a/test/INetMock.Client.Test/Audit/GenericReaderTest.cs b/test/INetMock.Client.Test/Audit/GenericReaderTest.cs new file mode 100644 index 0000000..3a52377 --- /dev/null +++ b/test/INetMock.Client.Test/Audit/GenericReaderTest.cs @@ -0,0 +1,57 @@ +using System.IO; +using INetMock.Client.Audit; +using INetMock.Client.Test.Hex; +using Xunit; + +namespace INetMock.Client.Test.Audit +{ + public class GenericReaderTest + { + private const string HttpEventPayload = "000000dd120b088092b8c398feffffff01180120022a047f00000132047f00000138d8fc0140504a3308041224544c535f45434448455f45434453415f574954485f4145535f3235365f4342435f5348411a096c6f63616c686f73745282010a34747970652e676f6f676c65617069732e636f6d2f696e65746d6f636b2e61756469742e4854545044657461696c73456e74697479124a12096c6f63616c686f73741a15687474703a2f2f6c6f63616c686f73742f6173646622084854545020312e312a1c0a0641636365707412120a106170706c69636174696f6e2f6a736f6e"; + private const string DnsEventPayload = "0000003b120b088092b8c398feffffff01180120012a100000000000000000000000000000000132100000000000000000000000000000000138d8fc014050"; + + private readonly byte[] _httpEventPayloadBytes; + private readonly byte[] _dnsEventPayloadBytes; + + public GenericReaderTest() + { + _httpEventPayloadBytes = HttpEventPayload.HexToByteArray(); + _dnsEventPayloadBytes = DnsEventPayload.HexToByteArray(); + } + + [Fact] + public async void TestRead_HttpEvent_Success() + { + await using var protoReader = new ProtoReader(new MemoryStream(_httpEventPayloadBytes)); + await using var reader = new GenericReader(protoReader); + await foreach (var ev in reader.ReadAllAsync()) + { + Assert.NotNull(ev); + + Assert.True(ev.CanConvert()); + Assert.False(ev.CanConvert()); + + var httpEvent = (Event?) ev; + var dnsEvent = (Event?) ev; + + Assert.NotNull(httpEvent); + Assert.Null(dnsEvent); + } + } + + [Fact] + public async void TestRead_DnsEvent_Success() + { + await using var memStream = new MemoryStream(_dnsEventPayloadBytes); + await using var protoReader = new ProtoReader(new MemoryStream(_dnsEventPayloadBytes)); + await using var reader = new GenericReader(protoReader); + await foreach (var ev in reader.ReadAllAsync()) + { + Assert.NotNull(ev); + Assert.True(ev.CanConvert()); + var dnsEvent = (Event?) ev; + Assert.NotNull(dnsEvent); + } + } + } +} diff --git a/test/INetMock.Client.Test/Audit/TypedReaderTest.cs b/test/INetMock.Client.Test/Audit/TypedReaderTest.cs new file mode 100644 index 0000000..48efa31 --- /dev/null +++ b/test/INetMock.Client.Test/Audit/TypedReaderTest.cs @@ -0,0 +1,45 @@ +using System.IO; +using INetMock.Client.Audit; +using INetMock.Client.Test.Hex; +using Xunit; + +namespace INetMock.Client.Test.Audit +{ + public class TypedReaderTest + { + private const string HttpEventPayload = "000000dd120b088092b8c398feffffff01180120022a047f00000132047f00000138d8fc0140504a3308041224544c535f45434448455f45434453415f574954485f4145535f3235365f4342435f5348411a096c6f63616c686f73745282010a34747970652e676f6f676c65617069732e636f6d2f696e65746d6f636b2e61756469742e4854545044657461696c73456e74697479124a12096c6f63616c686f73741a15687474703a2f2f6c6f63616c686f73742f6173646622084854545020312e312a1c0a0641636365707412120a106170706c69636174696f6e2f6a736f6e"; + private const string DnsEventPayload = "0000003b120b088092b8c398feffffff01180120012a100000000000000000000000000000000132100000000000000000000000000000000138d8fc014050"; + + private readonly byte[] _httpEventPayloadBytes; + private readonly byte[] _dnsEventPayloadBytes; + + public TypedReaderTest() + { + _httpEventPayloadBytes = HttpEventPayload.HexToByteArray(); + _dnsEventPayloadBytes = DnsEventPayload.HexToByteArray(); + } + + [Fact] + public async void TestRead_HttpEvent_Success() + { + await using var protoReader = new ProtoReader(new MemoryStream(_httpEventPayloadBytes)); + await using var reader = new TypedReader(protoReader); + await foreach (var ev in reader.ReadAllAsync()) + { + Assert.NotNull(ev); + } + } + + [Fact] + public async void TestRead_DnsEvent_Success() + { + await using var memStream = new MemoryStream(_dnsEventPayloadBytes); + await using var protoReader = new ProtoReader(new MemoryStream(_dnsEventPayloadBytes)); + await using var reader = new TypedReader(protoReader); + await foreach (var ev in reader.ReadAllAsync()) + { + Assert.NotNull(ev); + } + } + } +} diff --git a/test/INetMock.Client.Test/Hex/Converter.cs b/test/INetMock.Client.Test/Hex/Converter.cs new file mode 100644 index 0000000..4d1abd5 --- /dev/null +++ b/test/INetMock.Client.Test/Hex/Converter.cs @@ -0,0 +1,15 @@ +using System; +using System.Linq; + +namespace INetMock.Client.Test.Hex +{ + public static class Converter + { + public static byte[] HexToByteArray(this string hex) { + return Enumerable.Range(0, hex.Length) + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) + .ToArray(); + } + } +} diff --git a/test/INetMock.Client.Test/INetMock.Client.Test.csproj b/test/INetMock.Client.Test/INetMock.Client.Test.csproj new file mode 100644 index 0000000..b0de0dc --- /dev/null +++ b/test/INetMock.Client.Test/INetMock.Client.Test.csproj @@ -0,0 +1,24 @@ + + + + net5.0 + + false + + latest + + enable + + + + + + + + + + + + + +