From bd66c9a9e25911dd3482c2aa466f5b409e498bb4 Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Wed, 21 Feb 2024 09:04:44 +0300 Subject: [PATCH 01/14] Improve yamux; add test-plans interop app --- .dockerignore | 35 + .../Libp2p.Core.Tests/ReaderWriterTests.cs | 125 +- .../Libp2p.Core.TestsBase/TestChannel.cs | 10 + src/libp2p/Libp2p.Core/Channel.cs | 138 +- src/libp2p/Libp2p.Core/IReader.cs | 17 +- src/libp2p/Libp2p.Core/IWriter.cs | 1 + .../IdentifyProtocol.cs | 2 +- .../Libp2p.Protocols.Ping/PingProtocol.cs | 2 +- .../Libp2p.Protocols.Yamux.Tests.csproj | 29 + .../Libp2p.Protocols.Yamux.Tests/Usings.cs | 4 + .../YamuxProtocolTests.cs | 94 ++ .../SessionTerminationCode.cs | 11 + .../Libp2p.Protocols.Yamux/YamuxProtocol.cs | 351 +++-- src/libp2p/Libp2p.sln | 16 +- src/samples/transport-interop/Dockerfile | 21 + src/samples/transport-interop/Program.cs | 151 ++ .../Properties/launchSettings.json | 24 + .../transport-interop/TransportInterop.csproj | 30 + .../transport-interop/packages.lock.json | 1267 +++++++++++++++++ 19 files changed, 2130 insertions(+), 198 deletions(-) create mode 100644 .dockerignore create mode 100644 src/libp2p/Libp2p.Protocols.Yamux.Tests/Libp2p.Protocols.Yamux.Tests.csproj create mode 100644 src/libp2p/Libp2p.Protocols.Yamux.Tests/Usings.cs create mode 100644 src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs create mode 100644 src/libp2p/Libp2p.Protocols.Yamux/SessionTerminationCode.cs create mode 100644 src/samples/transport-interop/Dockerfile create mode 100644 src/samples/transport-interop/Program.cs create mode 100644 src/samples/transport-interop/Properties/launchSettings.json create mode 100644 src/samples/transport-interop/TransportInterop.csproj create mode 100644 src/samples/transport-interop/packages.lock.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..dc4adebc --- /dev/null +++ b/.dockerignore @@ -0,0 +1,35 @@ +.git +launchSettings.json + +*DS_Store +_ReSharper.* +*.csproj.user +*[Rr]e[Ss]harper.user +_ReSharper.*/ +.vs/ + +**/Obj/ +**/obj/ +**/bin/ +**/Bin/ + +*.xap +*.user +/TestResults +*.vspscc +*.vssscc +*.suo +*.cache +packages/* +artifacts/* +msbuild.log +PublishProfiles/ +*.psess +*.vsp +*.pidb +*.userprefs +*.ncrunchsolution +*.log +*.vspx +/.symbols +*.sln.ide \ No newline at end of file diff --git a/src/libp2p/Libp2p.Core.Tests/ReaderWriterTests.cs b/src/libp2p/Libp2p.Core.Tests/ReaderWriterTests.cs index a02106a4..aab1758a 100644 --- a/src/libp2p/Libp2p.Core.Tests/ReaderWriterTests.cs +++ b/src/libp2p/Libp2p.Core.Tests/ReaderWriterTests.cs @@ -14,17 +14,23 @@ public async Task Test_ChannelWrites_WhenReadIsRequested() bool isWritten = false; Task wrote = Task.Run(async () => { - await readerWriter.WriteAsync(new ReadOnlySequence(new byte[] { 1, 2, 3, 4 })); + await readerWriter.WriteAsync(new ReadOnlySequence([1, 2, 3, 4])); isWritten = true; }); await Task.Delay(100); Assert.That(isWritten, Is.False); ReadOnlySequence chunk1 = await readerWriter.ReadAsync(1); - Assert.That(chunk1.ToArray(), Is.EquivalentTo(new byte[] { 1 })); - Assert.That(isWritten, Is.False); + Assert.Multiple(() => + { + Assert.That(chunk1.ToArray(), Is.EquivalentTo(new byte[] { 1 })); + Assert.That(isWritten, Is.False); + }); ReadOnlySequence chunk2 = await readerWriter.ReadAsync(2); - Assert.That(chunk2.ToArray(), Is.EquivalentTo(new byte[] { 2, 3 })); - Assert.That(isWritten, Is.False); + Assert.Multiple(() => + { + Assert.That(chunk2.ToArray(), Is.EquivalentTo(new byte[] { 2, 3 })); + Assert.That(isWritten, Is.False); + }); ReadOnlySequence chunk3 = await readerWriter.ReadAsync(1); Assert.That(chunk3.ToArray(), Is.EquivalentTo(new byte[] { 4 })); await wrote; @@ -37,8 +43,8 @@ public async Task Test_ChannelReads_MultipleWrites() Channel.ReaderWriter readerWriter = new(); _ = Task.Run(async () => { - await readerWriter.WriteAsync(new ReadOnlySequence(new byte[] { 1 })); - await readerWriter.WriteAsync(new ReadOnlySequence(new byte[] { 2 })); + await readerWriter.WriteAsync(new ReadOnlySequence([1])); + await readerWriter.WriteAsync(new ReadOnlySequence([2])); }); ReadOnlySequence allTheData = await readerWriter.ReadAsync(2); Assert.That(allTheData.ToArray(), Is.EquivalentTo(new byte[] { 1, 2 })); @@ -50,10 +56,10 @@ public async Task Test_ChannelReads_SequentialChunks() Channel.ReaderWriter readerWriter = new(); ValueTask> t1 = readerWriter.ReadAsync(2); ValueTask> t2 = readerWriter.ReadAsync(2); - await readerWriter.WriteAsync(new ReadOnlySequence(new byte[] { 1 })); - await readerWriter.WriteAsync(new ReadOnlySequence(new byte[] { 2 })); - await readerWriter.WriteAsync(new ReadOnlySequence(new byte[] { 3 })); - await readerWriter.WriteAsync(new ReadOnlySequence(new byte[] { 4 })); + await readerWriter.WriteAsync(new ReadOnlySequence([1])); + await readerWriter.WriteAsync(new ReadOnlySequence([2])); + await readerWriter.WriteAsync(new ReadOnlySequence([3])); + await readerWriter.WriteAsync(new ReadOnlySequence([4])); ReadOnlySequence chunk1 = await t1; ReadOnlySequence chunk2 = await t2; Assert.That(chunk1.ToArray(), Is.EquivalentTo(new byte[] { 1, 2 })); @@ -64,7 +70,7 @@ public async Task Test_ChannelReads_SequentialChunks() public async Task Test_ChannelWrites_WhenReadIsRequested2() { Channel.ReaderWriter readerWriter = new(); - _ = Task.Run(async () => await readerWriter.WriteAsync(new ReadOnlySequence(new byte[] { 1, 2 }))); + _ = Task.Run(async () => await readerWriter.WriteAsync(new ReadOnlySequence([1, 2]))); ReadOnlySequence res1 = await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAny); Assert.That(res1.ToArray().Length, Is.EqualTo(2)); } @@ -88,4 +94,99 @@ public async Task Test_ChannelWrites_WhenReadIsRequested3() ReadOnlySequence res1 = await readerWriter.ReadAsync(3, ReadBlockingMode.DontWait); Assert.That(res1.ToArray().Length, Is.EqualTo(0)); } + + [Test] + public async Task Test_ChannelWrites_Eof() + { + Channel.ReaderWriter readerWriter = new(); + + _ = Task.Run(async () => + { + await readerWriter.WriteAsync(new ReadOnlySequence([1, 2, 3])); + await readerWriter.WriteEofAsync(); + }); + + Assert.That(await readerWriter.CanReadAsync(), Is.True); + ReadOnlySequence res1 = await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAll); + + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.DontWait)); + Assert.That(await readerWriter.CanReadAsync(), Is.False); + + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAny)); + Assert.That(await readerWriter.CanReadAsync(), Is.False); + + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAll)); + Assert.That(await readerWriter.CanReadAsync(), Is.False); + + } + + [TestCase(new byte[0])] + [TestCase(new byte[] { 1, 2, 3 })] + public async Task Test_ChannelWrites_CannotWriteAfterEof(byte[] toWrite) + { + Channel.ReaderWriter readerWriter = new(); + + await readerWriter.WriteEofAsync(); + Assert.That(await readerWriter.CanReadAsync(), Is.False); + + Assert.ThrowsAsync(async () => await readerWriter.WriteAsync(new ReadOnlySequence(toWrite))); + Assert.That(await readerWriter.CanReadAsync(), Is.False); + } + + [Test] + public async Task Test_ChannelWrites_CanReadAny() + { + Channel.ReaderWriter readerWriter = new(); + + _ = Task.Run(async () => + { + await readerWriter.WriteAsync(new ReadOnlySequence([1, 2, 3])); + await readerWriter.WriteEofAsync(); + }); + + ReadOnlySequence res1 = await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAll); + + Assert.That(res1, Has.Length.EqualTo(3)); + + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.DontWait)); + Assert.That(await readerWriter.CanReadAsync(), Is.False); + + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAny)); + Assert.That(await readerWriter.CanReadAsync(), Is.False); + + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAll)); + Assert.That(await readerWriter.CanReadAsync(), Is.False); + } + + [Test] + public async Task Test_ChannelWrites_CannotReadAll_OnePacket() + { + Channel.ReaderWriter readerWriter = new(); + + _ = Task.Run(async () => + { + await readerWriter.WriteAsync(new ReadOnlySequence([1, 2, 3])); + await readerWriter.WriteEofAsync(); + }); + + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(5, ReadBlockingMode.WaitAll)); + Assert.That(await readerWriter.CanReadAsync(), Is.False); + } + + [Test] + public async Task Test_ChannelWrites_CannotReadAll_Fragmented() + { + Channel.ReaderWriter readerWriter = new(); + + _ = Task.Run(async () => + { + await readerWriter.WriteAsync(new ReadOnlySequence([1])); + await readerWriter.WriteAsync(new ReadOnlySequence([2, 3])); + await readerWriter.WriteAsync(new ReadOnlySequence([4])); + await readerWriter.WriteEofAsync(); + }); + + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(5, ReadBlockingMode.WaitAll)); + Assert.That(await readerWriter.CanReadAsync(), Is.False); + } } diff --git a/src/libp2p/Libp2p.Core.TestsBase/TestChannel.cs b/src/libp2p/Libp2p.Core.TestsBase/TestChannel.cs index 18f04b67..0a345686 100644 --- a/src/libp2p/Libp2p.Core.TestsBase/TestChannel.cs +++ b/src/libp2p/Libp2p.Core.TestsBase/TestChannel.cs @@ -48,4 +48,14 @@ public ValueTask WriteAsync(ReadOnlySequence bytes) { return _channel.WriteAsync(bytes); } + + public ValueTask WriteEofAsync() + { + return _channel.WriteEofAsync(); + } + + public ValueTask CanReadAsync(CancellationToken token = default) + { + return _channel.CanReadAsync(token); + } } diff --git a/src/libp2p/Libp2p.Core/Channel.cs b/src/libp2p/Libp2p.Core/Channel.cs index 45407426..0d31e627 100644 --- a/src/libp2p/Libp2p.Core/Channel.cs +++ b/src/libp2p/Libp2p.Core/Channel.cs @@ -51,7 +51,7 @@ public IChannel Reverse Channel x = new((ReaderWriter)Writer, (ReaderWriter)Reader) { - _logger = this._logger, + _logger = _logger, _reversedChannel = this, State = State, Id = Id + "-rev" @@ -119,70 +119,95 @@ public ReaderWriter() private readonly SemaphoreSlim _read = new(0, 1); private readonly SemaphoreSlim _canRead = new(0, 1); private readonly SemaphoreSlim _readLock = new(1, 1); + private bool _eof = false; public async ValueTask> ReadAsync(int length, ReadBlockingMode blockingMode = ReadBlockingMode.WaitAll, CancellationToken token = default) { await _readLock.WaitAsync(token); - try + + if (_eof) + { + _readLock.Release(); + throw new Exception("Can't read after EOF"); + } + + if (blockingMode == ReadBlockingMode.DontWait && _bytes.Length == 0) + { + _readLock.Release(); + return new ReadOnlySequence(); + } + + await _canRead.WaitAsync(token); + + if (_eof) + { + _canRead.Release(); + _readLock.Release(); + _read.Release(); + _canWrite.Release(); + throw new Exception("Can't read after EOF"); + } + + bool lockAgain = false; + long bytesToRead = length != 0 + ? (blockingMode == ReadBlockingMode.WaitAll ? length : Math.Min(length, _bytes.Length)) + : _bytes.Length; + + ReadOnlySequence chunk = default; + do { - if (blockingMode == ReadBlockingMode.DontWait && _bytes.Length == 0) + if (lockAgain) await _canRead.WaitAsync(token); + + if (_eof) { + _canRead.Release(); _readLock.Release(); - return new ReadOnlySequence(); + _read.Release(); + throw new Exception("Can't read after EOF"); } - await _canRead.WaitAsync(token); + ReadOnlySequence anotherChunk = default; - bool lockAgain = false; - long bytesToRead = length != 0 - ? (blockingMode == ReadBlockingMode.WaitAll ? length : Math.Min(length, _bytes.Length)) - : _bytes.Length; - - ReadOnlySequence chunk = default; - do + if (_bytes.Length <= bytesToRead) + { + anotherChunk = _bytes; + bytesToRead -= _bytes.Length; + _logger?.ReadChunk(_bytes.Length); + _bytes = default; + _read.Release(); + _canWrite.Release(); + } + else if (_bytes.Length > bytesToRead) { - if (lockAgain) await _canRead.WaitAsync(token); - - ReadOnlySequence anotherChunk = default; - - if (_bytes.Length <= bytesToRead) - { - anotherChunk = _bytes; - bytesToRead -= _bytes.Length; - _logger?.ReadChunk(_bytes.Length); - _bytes = default; - _read.Release(); - _canWrite.Release(); - } - else if (_bytes.Length > bytesToRead) - { - anotherChunk = _bytes.Slice(0, bytesToRead); - _bytes = _bytes.Slice(bytesToRead, _bytes.End); - _logger?.ReadEnough(_bytes.Length); - bytesToRead = 0; - _canRead.Release(); - } - - chunk = chunk.Length == 0 ? anotherChunk : chunk.Append(anotherChunk.First); - lockAgain = true; - } while (bytesToRead != 0); + anotherChunk = _bytes.Slice(0, bytesToRead); + _bytes = _bytes.Slice(bytesToRead, _bytes.End); + _logger?.ReadEnough(_bytes.Length); + bytesToRead = 0; + _canRead.Release(); + } - _readLock.Release(); - return chunk; - } - catch - { - throw; - } + chunk = chunk.Length == 0 ? anotherChunk : chunk.Append(anotherChunk.First); + lockAgain = true; + } while (bytesToRead != 0); + + _readLock.Release(); + return chunk; } public async ValueTask WriteAsync(ReadOnlySequence bytes) { await _canWrite.WaitAsync(); + + if (_eof) + { + _canWrite.Release(); + throw new Exception("Can't write after EOF"); + } + if (_bytes.Length != 0) { - throw new InvalidProgramException(); + throw new Exception("Channel is not properly locked"); } _logger?.WriteBytes(bytes.Length); @@ -197,6 +222,25 @@ public async ValueTask WriteAsync(ReadOnlySequence bytes) _canRead.Release(); await _read.WaitAsync(); } + + public async ValueTask WriteEofAsync() + { + await _canWrite.WaitAsync(); + _eof = true; + _canRead.Release(); + _canWrite.Release(); + } + + public async ValueTask CanReadAsync(CancellationToken token = default) + { + if (_eof) + { + return false; + } + await _readLock.WaitAsync(token); + _readLock.Release(); + return !_eof; + } } public ValueTask> ReadAsync(int length, ReadBlockingMode blockingMode = ReadBlockingMode.WaitAll, @@ -209,4 +253,8 @@ public ValueTask WriteAsync(ReadOnlySequence bytes) { return Writer.WriteAsync(bytes); } + + public ValueTask WriteEofAsync() => Writer.WriteEofAsync(); + + public ValueTask CanReadAsync(CancellationToken token = default) => Reader.CanReadAsync(token); } diff --git a/src/libp2p/Libp2p.Core/IReader.cs b/src/libp2p/Libp2p.Core/IReader.cs index 690648b9..aab59f20 100644 --- a/src/libp2p/Libp2p.Core/IReader.cs +++ b/src/libp2p/Libp2p.Core/IReader.cs @@ -10,6 +10,17 @@ namespace Nethermind.Libp2p.Core; public interface IReader { + /// + /// Waits for any data or EOF marker, returns false if EOF was reached, true without reading data otherwise. + /// + /// + ValueTask CanReadAsync(CancellationToken token = default); + + ValueTask> ReadAsync(int length, ReadBlockingMode blockingMode = ReadBlockingMode.WaitAll, + CancellationToken token = default); + + + #region Read helpers async Task ReadLineAsync() { int size = await ReadVarintAsync(); @@ -33,17 +44,17 @@ async ValueTask ReadPrefixedProtobufAsync(MessageParser parser, Cancell return parser.ParseFrom(serializedMessage); } + async IAsyncEnumerable> ReadAllAsync( [EnumeratorCancellation] CancellationToken token = default) { - while (!token.IsCancellationRequested) + while (!token.IsCancellationRequested && await CanReadAsync()) { yield return await ReadAsync(0, ReadBlockingMode.WaitAny, token); } } - ValueTask> ReadAsync(int length, ReadBlockingMode blockingMode = ReadBlockingMode.WaitAll, - CancellationToken token = default); + #endregion } public enum ReadBlockingMode diff --git a/src/libp2p/Libp2p.Core/IWriter.cs b/src/libp2p/Libp2p.Core/IWriter.cs index 345c72dd..ffda19bc 100644 --- a/src/libp2p/Libp2p.Core/IWriter.cs +++ b/src/libp2p/Libp2p.Core/IWriter.cs @@ -54,4 +54,5 @@ async ValueTask WritePrefixedProtobufAsync(T grpcMessage) where T : IMessage< } ValueTask WriteAsync(ReadOnlySequence bytes); + ValueTask WriteEofAsync(); } diff --git a/src/libp2p/Libp2p.Protocols.Identify/IdentifyProtocol.cs b/src/libp2p/Libp2p.Protocols.Identify/IdentifyProtocol.cs index 6d321c91..16f24498 100644 --- a/src/libp2p/Libp2p.Protocols.Identify/IdentifyProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Identify/IdentifyProtocol.cs @@ -19,7 +19,7 @@ public class IdentifyProtocol : IProtocol private readonly ILogger? _logger; private readonly IPeerFactoryBuilder _peerFactoryBuilder; - public IdentifyProtocol(IPeerFactoryBuilder peerFactoryBuilder, IdentifyProtocolSettings? settings, ILoggerFactory? loggerFactory = null) + public IdentifyProtocol(IPeerFactoryBuilder peerFactoryBuilder, IdentifyProtocolSettings? settings = null, ILoggerFactory? loggerFactory = null) { _logger = loggerFactory?.CreateLogger(); _peerFactoryBuilder = peerFactoryBuilder; diff --git a/src/libp2p/Libp2p.Protocols.Ping/PingProtocol.cs b/src/libp2p/Libp2p.Protocols.Ping/PingProtocol.cs index 3598f64a..7d5e1927 100644 --- a/src/libp2p/Libp2p.Protocols.Ping/PingProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Ping/PingProtocol.cs @@ -52,7 +52,7 @@ public async Task ListenAsync(IChannel channel, IChannelFactory? channelFactory, { _logger?.PingListenStarted(context.RemotePeer.Address); - while (!channel.IsClosed) + while (await channel.CanReadAsync()) { _logger?.ReadingPing(context.RemotePeer.Address); ReadOnlySequence request = await channel.ReadAsync(PayloadLength, ReadBlockingMode.WaitAll); diff --git a/src/libp2p/Libp2p.Protocols.Yamux.Tests/Libp2p.Protocols.Yamux.Tests.csproj b/src/libp2p/Libp2p.Protocols.Yamux.Tests/Libp2p.Protocols.Yamux.Tests.csproj new file mode 100644 index 00000000..6c0a21cc --- /dev/null +++ b/src/libp2p/Libp2p.Protocols.Yamux.Tests/Libp2p.Protocols.Yamux.Tests.csproj @@ -0,0 +1,29 @@ + + + + enable + enable + Nethermind.$(MSBuildProjectName.Replace(" ", "_")) + false + Nethermind.$(MSBuildProjectName) + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/src/libp2p/Libp2p.Protocols.Yamux.Tests/Usings.cs b/src/libp2p/Libp2p.Protocols.Yamux.Tests/Usings.cs new file mode 100644 index 00000000..e160e2a1 --- /dev/null +++ b/src/libp2p/Libp2p.Protocols.Yamux.Tests/Usings.cs @@ -0,0 +1,4 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: MIT + +global using NUnit.Framework; diff --git a/src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs b/src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs new file mode 100644 index 00000000..199fc4a0 --- /dev/null +++ b/src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: MIT + +using Nethermind.Libp2p.Core; +using Nethermind.Libp2p.Core.TestsBase; +using NSubstitute; +using System.Buffers; + +namespace Nethermind.Libp2p.Protocols.Noise.Tests; + +[TestFixture] +public class YamuxProtocolTests +{ + // implement the following testcases: + // Establish connection, expect 0 stream + // Close connection, expect goaway + // Try speak a protocol + // Exchange data + // Expect error and react to it + + [Test] + public async Task Test_Connection_Ackowledged() + { + IChannel downChannel = new TestChannel(); + IChannel downChannelFromProtocolPov = ((TestChannel)downChannel).Reverse(); + IChannelFactory channelFactory = Substitute.For(); + IPeerContext peerContext = Substitute.For(); + + IProtocol? proto1 = Substitute.For(); + proto1.Id.Returns("proto1"); + channelFactory.SubProtocols.Returns(new[] { proto1 }); + IChannel upChannel = new TestChannel(); + channelFactory.SubDialAndBind(Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(upChannel); + + YamuxProtocol proto = new(); + _ = proto.DialAsync(downChannelFromProtocolPov, channelFactory, peerContext); + await downChannel.WriteLineAsync(proto.Id); + await downChannel.WriteLineAsync("proto1"); + + Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo(proto.Id)); + Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo("proto1")); + channelFactory.Received().SubDialAndBind(downChannelFromProtocolPov, peerContext, proto1); + await downChannel.CloseAsync(); + } + + [Test] + public async Task Test_ConnectionClosed_ForBrokenHandshake() + { + // IChannel downChannel = new TestChannel(); + // IChannel downChannelFromProtocolPov = ((TestChannel)downChannel).Reverse(); + // IChannelFactory channelFactory = Substitute.For(); + // IPeerContext peerContext = Substitute.For(); + // + // IProtocol? proto1 = Substitute.For(); + // proto1.Id.Returns("proto1"); + // channelFactory.SubProtocols.Returns(new[] { proto1 }); + // IChannel upChannel = new TestChannel(); + // channelFactory.SubDialAndBind(Arg.Any(), Arg.Any(), Arg.Any()) + // .Returns(upChannel); + // + // NoiseProtocol proto = new(); + // _ = proto.DialAsync(downChannelFromProtocolPov, channelFactory, peerContext); + // await downChannel.WriteLineAsync(proto.Id); + // await downChannel.WriteLineAsync("proto2"); + // + // Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo(proto.Id)); + // Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo("proto1")); + // channelFactory.DidNotReceive().SubDialAndBind(downChannelFromProtocolPov, peerContext, proto1); + // await upChannel.CloseAsync(); + } + + class PingPongProtocol : IProtocol + { + public string Id => throw new NotImplementedException(); + + public async Task DialAsync(IChannel downChannel, IChannelFactory? upChannelFactory, IPeerContext context) + { + const string line = "hello"; + await downChannel.WriteLineAsync(line); + await downChannel.WriteEofAsync(); + string received = await downChannel.ReadLineAsync(); + Assert.That(received, Is.EqualTo(line)); + } + + public async Task ListenAsync(IChannel downChannel, IChannelFactory? upChannelFactory, IPeerContext context) + { + string line = await downChannel.ReadLineAsync(); + ReadOnlySequence? readAfter = await downChannel.ReadAsync(0, ReadBlockingMode.WaitAny); + Assert.That(readAfter, Is.Null); + await downChannel.WriteLineAsync(line); + } + } +} diff --git a/src/libp2p/Libp2p.Protocols.Yamux/SessionTerminationCode.cs b/src/libp2p/Libp2p.Protocols.Yamux/SessionTerminationCode.cs new file mode 100644 index 00000000..90d69333 --- /dev/null +++ b/src/libp2p/Libp2p.Protocols.Yamux/SessionTerminationCode.cs @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: MIT + +namespace Nethermind.Libp2p.Protocols; + +internal enum SessionTerminationCode +{ + Ok = 0x0, + ProtocolError = 0x1, + InternalError = 0x2, +} diff --git a/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs b/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs index 187d2533..a0bb3039 100644 --- a/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs @@ -23,178 +23,247 @@ public YamuxProtocol(ILoggerFactory? loggerFactory = null) protected override async Task ConnectAsync(IChannel channel, IChannelFactory? channelFactory, IPeerContext context, bool isListener) { - if (channelFactory is null) + try { - throw new ArgumentException("ChannelFactory should be available for a muxer", nameof(channelFactory)); - } - - _logger?.LogInformation("Yamux as {role}", isListener ? "listener" : "dialer"); - int streamIdCounter = isListener ? 2 : 1; - - Dictionary channels = new(); + if (channelFactory is null) + { + throw new ArgumentException("ChannelFactory should be available for a muxer", nameof(channelFactory)); + } - if (!isListener) - { - await WriteHeaderAsync(channel, - new YamuxHeader { Flags = YamuxHeaderFlags.Syn, Type = YamuxHeaderType.Ping, StreamID = 0 }); - } + _logger?.LogInformation("Yamux as {role}", isListener ? "listener" : "dialer"); + int streamIdCounter = isListener ? 2 : 1; - context.Connected(context.RemotePeer); + Dictionary channels = new(); - _ = Task.Run(async () => - { - foreach (IChannelRequest request in context.SubDialRequests.GetConsumingEnumerable()) + if (!isListener) { - int streamId = streamIdCounter; - streamIdCounter += 2; - _logger?.LogDebug("Trying to dial with protocol {proto} via stream-{streamId}", request.SubProtocol?.Id, streamId); - channels[streamId] = new ChannelState { Request = request }; await WriteHeaderAsync(channel, - new YamuxHeader - { - Flags = YamuxHeaderFlags.Syn, - Type = YamuxHeaderType.Data, - StreamID = streamId, - }); - ActivateUpchannel(streamId, request); + new YamuxHeader { Flags = YamuxHeaderFlags.Syn, Type = YamuxHeaderType.Ping, StreamID = 0 }); } - }); - while (!channel.IsClosed) - { - YamuxHeader header = await ReadHeaderAsync(channel, token: channel.Token); - ReadOnlySequence data = default; + context.Connected(context.RemotePeer); - if (header.StreamID is 0) + _ = Task.Run(async () => { - if ((header.Flags & YamuxHeaderFlags.Syn) == YamuxHeaderFlags.Syn) + foreach (IChannelRequest request in context.SubDialRequests) { - _logger?.LogDebug("Confirming session stream"); - _ = WriteHeaderAsync(channel, + int streamId = streamIdCounter; + Interlocked.Add(ref streamIdCounter, 2); + + _logger?.LogDebug("Trying to dial with protocol {proto} via stream-{streamId}", request.SubProtocol?.Id, streamId); + channels[streamId] = new ChannelState { Request = request }; + await WriteHeaderAsync(channel, new YamuxHeader { - Flags = YamuxHeaderFlags.Ack, + Flags = YamuxHeaderFlags.Syn, Type = YamuxHeaderType.Data, - StreamID = header.StreamID + StreamID = streamId, }); + _ = ActivateUpchannel(streamId, request); } + }); + + bool f = true; + channel.OnClose(() => + { + f = false; + return Task.CompletedTask; + }); + while (f) + { + YamuxHeader header = await ReadHeaderAsync(channel); + ReadOnlySequence data = default; - if ((header.Flags & YamuxHeaderFlags.Rst) == YamuxHeaderFlags.Rst || (header.Flags & YamuxHeaderFlags.Fin) == YamuxHeaderFlags.Fin) + if (header.StreamID is 0) { - _logger?.LogDebug("Closing all streams"); + if ((header.Flags & YamuxHeaderFlags.Syn) == YamuxHeaderFlags.Syn) + { + _logger?.LogDebug("Confirming session stream"); + _ = WriteHeaderAsync(channel, + new YamuxHeader + { + Flags = YamuxHeaderFlags.Ack, + Type = YamuxHeaderType.Data, + StreamID = header.StreamID + }); + } - foreach (ChannelState channelState in channels.Values) + if (header.Type == YamuxHeaderType.GoAway) { - await channelState.Channel?.CloseAsync(); + _logger?.LogDebug("Closing all streams"); + + foreach (ChannelState channelState in channels.Values) + { + if(channelState.Channel is not null) + { + await channelState.Channel.WriteEofAsync(); + } + } + + break; } - await channel.CloseAsync(); - return; + continue; } - continue; - } - - if (header is { Type: YamuxHeaderType.Data, Length: not 0 }) - { - data = new ReadOnlySequence((await channel.ReadAsync(header.Length)).ToArray()); - _logger?.LogDebug("Recv data, stream-{0}, len={1}, data: {data}", - header.StreamID, data.Length, - Encoding.ASCII.GetString(data.ToArray().Select(c => c == 0x1b || c == 0x07 ? (byte)0x2e : c).ToArray())); - } + if (channels.TryAdd(header.StreamID, new()) || (header.Flags & YamuxHeaderFlags.Syn) == YamuxHeaderFlags.Syn) + { + _logger?.LogDebug("Request for a stream"); + _ = WriteHeaderAsync(channel, + new YamuxHeader + { + Flags = YamuxHeaderFlags.Ack, + Type = YamuxHeaderType.Data, + StreamID = header.StreamID + }); + } - if (channels.TryAdd(header.StreamID, new()) || (header.Flags & YamuxHeaderFlags.Syn) == YamuxHeaderFlags.Syn) - { - _logger?.LogDebug("Request for a stream"); - _ = WriteHeaderAsync(channel, - new YamuxHeader + if (header is { Type: YamuxHeaderType.Data, Length: not 0 }) + { + if (header.Length > channels[header.StreamID].WindowSize) { - Flags = YamuxHeaderFlags.Ack, - Type = YamuxHeaderType.Data, - StreamID = header.StreamID - }); - } + await WriteGoAwayAsync(channel, SessionTerminationCode.ProtocolError); + return; + } + data = new ReadOnlySequence((await channel.ReadAsync(header.Length)).ToArray()); + _logger?.LogDebug("Recv data, stream-{0}, len={1}, data: {data}", + header.StreamID, data.Length, + Encoding.ASCII.GetString(data.ToArray().Select(c => c == 0x1b || c == 0x07 ? (byte)0x2e : c).ToArray())); + } - if (channels[header.StreamID].Channel is null) - { - ActivateUpchannel(header.StreamID, null); - _logger?.LogDebug("Channel activated for stream-{streamId}", header.StreamID); - } + if (channels[header.StreamID].Channel is null) + { + _ = ActivateUpchannel(header.StreamID, null); + _logger?.LogDebug("Channel activated for stream-{streamId}", header.StreamID); + } - if (header.Type == YamuxHeaderType.Data) - { - _logger?.LogDebug("Write data to upchannel, stream-{0}, len={1}", header.StreamID, data.Length); - await channels[header.StreamID].Channel!.WriteAsync(data); - } + if (header.Type == YamuxHeaderType.Data) + { + _logger?.LogDebug("Write data to upchannel, stream-{0}, len={1}", header.StreamID, data.Length); + await channels[header.StreamID].Channel!.WriteAsync(data); + } - if ((header.Flags & YamuxHeaderFlags.Fin) == YamuxHeaderFlags.Fin) - { - _ = channels[header.StreamID].Channel?.CloseAsync(); - _logger?.LogDebug("Fin, stream-{0}", header.StreamID); - } + if (header.Type == YamuxHeaderType.WindowUpdate) + { + _logger?.LogDebug("Write data to upchannel, stream-{0}, len={1}", header.StreamID, data.Length); + channels[header.StreamID] = channels[header.StreamID] with { WindowSize = header.Length }; + } - if ((header.Flags & YamuxHeaderFlags.Rst) == YamuxHeaderFlags.Rst) - { - _ = channels[header.StreamID].Channel?.CloseAsync(); - _logger?.LogDebug("Rst, stream-{0}", header.StreamID); + if ((header.Flags & YamuxHeaderFlags.Fin) == YamuxHeaderFlags.Fin) + { + _ = channels[header.StreamID].Channel?.WriteEofAsync(); + channels[header.StreamID] = channels[header.StreamID] with { State = channels[header.StreamID].State | State.DownClosed }; + _logger?.LogDebug("Fin, stream-{0}", header.StreamID); + if (channels[header.StreamID].State == State.Closed) + { + channels.Remove(header.StreamID); + } + } + + if ((header.Flags & YamuxHeaderFlags.Rst) == YamuxHeaderFlags.Rst) + { + _ = channels[header.StreamID].Channel?.CloseAsync(); + channels.Remove(header.StreamID); + _logger?.LogDebug("Rst, stream-{0}", header.StreamID); + } } - } - void ActivateUpchannel(int streamId, IChannelRequest? channelRequest) - { - if (channels[streamId].Channel is not null) + async Task ActivateUpchannel(int streamId, IChannelRequest? channelRequest) { - return; - } + if (channels[streamId].Channel is not null) + { + return; + } - bool isListenerChannel = isListener ^ (streamId % 2 == 0); + bool isListenerChannel = isListener ^ (streamId % 2 == 0); - _logger?.LogDebug("Create chan for stream-{0} isListener = {1}", streamId, isListenerChannel); - IChannel upChannel; + _logger?.LogDebug("Create chan for stream-{0} isListener = {1}", streamId, isListenerChannel); + IChannel upChannel; - if (isListenerChannel) - { - upChannel = channelFactory.SubListen(context); - } - else - { - IPeerContext dialContext = context.Fork(); - dialContext.SpecificProtocolRequest = channels[streamId].Request; - upChannel = channelFactory.SubDial(dialContext); - } + if (isListenerChannel) + { + upChannel = channelFactory.SubListen(context); + } + else + { + IPeerContext dialContext = context.Fork(); + dialContext.SpecificProtocolRequest = channels[streamId].Request; + upChannel = channelFactory.SubDial(dialContext); + } - channels[streamId] = new(upChannel, channelRequest); + channels[streamId] = new(upChannel, channelRequest); - upChannel.OnClose(async () => - { - await WriteHeaderAsync(channel, - new YamuxHeader + try + { + await foreach (var data in upChannel.ReadAllAsync()) { - Flags = YamuxHeaderFlags.Fin, - Type = YamuxHeaderType.Data, - StreamID = streamId - }); - channels[streamId].Request?.CompletionSource?.SetResult(); - _logger?.LogDebug("Close, stream-{0}", streamId); - }); + ReadOnlySequence upData = + await channels[streamId].Channel!.ReadAsync(channels[streamId].WindowSize, ReadBlockingMode.WaitAny, channel.Token); + _logger?.LogDebug("Read data from upchannel, stream-{0}, len={1}", streamId, upData.Length); + + for(int i = 0; i < upData.Length;) + { + int sendingSize = Math.Min((int)upData.Length - i, channels[streamId].WindowSize); + await WriteHeaderAsync(channel, + new YamuxHeader + { + Type = YamuxHeaderType.Data, + Length = (int)upData.Length, + StreamID = streamId + }, upData.Slice(i, sendingSize)); + i += sendingSize; + } + } - _ = Task.Run(async () => - { - while (!channel.IsClosed) - { - ReadOnlySequence upData = - await channels[streamId].Channel!.ReadAsync(0, ReadBlockingMode.WaitAny, - channel.Token); - _logger?.LogDebug("Read data from upchannel, stream-{0}, len={1}", streamId, upData.Length); await WriteHeaderAsync(channel, new YamuxHeader { - Type = YamuxHeaderType.Data, - Length = (int)upData.Length, + Flags = YamuxHeaderFlags.Fin, + Type = YamuxHeaderType.WindowUpdate, StreamID = streamId - }, upData); + }); + channels[streamId] = channels[streamId] with { State = channels[streamId].State | State.UpClosed }; + _logger?.LogDebug("Close, stream-{id}", streamId); } - }); + catch { + await WriteHeaderAsync(channel, + new YamuxHeader + { + Flags = YamuxHeaderFlags.Rst, + Type = YamuxHeaderType.Data, + StreamID = streamId + }); + _ = upChannel.CloseAsync(); + channels.Remove(streamId); + + _logger?.LogDebug("Unexpected close, stream-{id}", streamId); + + } + + upChannel.OnClose(() => + { + channels.Remove(streamId); + if ((channels[streamId].State | State.UpClosed) == 0) + { + _ = WriteHeaderAsync(channel, + new YamuxHeader + { + Flags = YamuxHeaderFlags.Fin, + Type = YamuxHeaderType.Data, + StreamID = streamId + }); + } + _logger?.LogDebug("Close, stream-{id}", streamId); + return Task.CompletedTask; + }); + } + + await WriteGoAwayAsync(channel, SessionTerminationCode.Ok); + } + catch (Exception ex) + { + await WriteGoAwayAsync(channel, SessionTerminationCode.InternalError); + _logger?.LogDebug("Multiplexer closed with exception {exception}", ex.Message); } } @@ -219,15 +288,27 @@ await writer.WriteAsync( data.Length == 0 ? new ReadOnlySequence(headerBuffer) : data.Prepend(headerBuffer)); } - private struct ChannelState - { - public ChannelState(IChannel? channel, IChannelRequest? request = default) + private Task WriteGoAwayAsync(IWriter channel, SessionTerminationCode code) => + WriteHeaderAsync(channel, new YamuxHeader { - Channel = channel; - Request = request; - } + Type = YamuxHeaderType.GoAway, + Length = (int)code, + StreamID = 0, + }); - public IChannel? Channel { get; set; } - public IChannelRequest? Request { get; set; } + private struct ChannelState(IChannel? channel, IChannelRequest? request = default) + { + public IChannel? Channel { get; set; } = channel; + public IChannelRequest? Request { get; set; } = request; + public int WindowSize { get; set; } = 256_000; + public State State { get; set; } + } + + private enum State + { + Open = 0, + UpClosed = 1, + DownClosed = 2, + Closed = UpClosed | DownClosed, } } diff --git a/src/libp2p/Libp2p.sln b/src/libp2p/Libp2p.sln index 644904ff..6c2b40c3 100644 --- a/src/libp2p/Libp2p.sln +++ b/src/libp2p/Libp2p.sln @@ -65,7 +65,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Multiformats.Address", "..\cs-multiaddress\src\Multiformats.Address\Multiformats.Address.csproj", "{1C7EF4B1-E314-46C7-99EC-16C7B20A1618}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Multiformats.Address.Tests", "..\cs-multiaddress\test\Multiformats.Address.Tests\Multiformats.Address.Tests.csproj", "{0582D2CC-05C7-47BF-B42A-65EDF3A02FAB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Multiformats.Address.Tests", "..\cs-multiaddress\test\Multiformats.Address.Tests\Multiformats.Address.Tests.csproj", "{0582D2CC-05C7-47BF-B42A-65EDF3A02FAB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libp2p.Protocols.Yamux.Tests", "Libp2p.Protocols.Yamux.Tests\Libp2p.Protocols.Yamux.Tests.csproj", "{D9003366-1562-49CA-B32D-087BBE3973ED}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TransportInterop", "..\samples\transport-interop\TransportInterop.csproj", "{EC505F21-FC69-4432-88A8-3CD5F7899B08}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -181,6 +185,14 @@ Global {0582D2CC-05C7-47BF-B42A-65EDF3A02FAB}.Debug|Any CPU.Build.0 = Debug|Any CPU {0582D2CC-05C7-47BF-B42A-65EDF3A02FAB}.Release|Any CPU.ActiveCfg = Release|Any CPU {0582D2CC-05C7-47BF-B42A-65EDF3A02FAB}.Release|Any CPU.Build.0 = Release|Any CPU + {D9003366-1562-49CA-B32D-087BBE3973ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D9003366-1562-49CA-B32D-087BBE3973ED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D9003366-1562-49CA-B32D-087BBE3973ED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D9003366-1562-49CA-B32D-087BBE3973ED}.Release|Any CPU.Build.0 = Release|Any CPU + {EC505F21-FC69-4432-88A8-3CD5F7899B08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EC505F21-FC69-4432-88A8-3CD5F7899B08}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EC505F21-FC69-4432-88A8-3CD5F7899B08}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EC505F21-FC69-4432-88A8-3CD5F7899B08}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -203,6 +215,8 @@ Global {E4103D59-03EB-488A-8392-0D2FBE3FBCC3} = {6F3D9AA9-C92D-4998-BC4E-D5EA068E8D0D} {FC0E9BCE-2848-45DC-AE20-FB7E862A199E} = {6F3D9AA9-C92D-4998-BC4E-D5EA068E8D0D} {EEECB761-A3C3-4598-AD03-EFABBF6CAA77} = {6F3D9AA9-C92D-4998-BC4E-D5EA068E8D0D} + {D9003366-1562-49CA-B32D-087BBE3973ED} = {6F3D9AA9-C92D-4998-BC4E-D5EA068E8D0D} + {EC505F21-FC69-4432-88A8-3CD5F7899B08} = {0DC1C6A1-0A5B-43BA-9605-621C21A16716} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E337E37C-3DB8-42FA-9A83-AC4E3B2557B4} diff --git a/src/samples/transport-interop/Dockerfile b/src/samples/transport-interop/Dockerfile new file mode 100644 index 00000000..d946e323 --- /dev/null +++ b/src/samples/transport-interop/Dockerfile @@ -0,0 +1,21 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env +WORKDIR /app + +COPY . ./ +RUN cd ./src/samples/transport-interop && dotnet publish -c Release -o ../../../out + +FROM mcr.microsoft.com/dotnet/runtime:8.0-jammy +WORKDIR /app + +RUN apt update -y && \ + apt install curl -y && \ + curl -sSL -O https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb && \ + dpkg -i packages-microsoft-prod.deb && \ + apt update -y && \ + apt install libmsquic=2.3.4 -y && \ + ln -s /usr/lib/x86_64-linux-gnu/libmsquic.so.2 /bin + +COPY --from=build-env /app/out . +ENTRYPOINT ["dotnet", "TransportInterop.dll"] + + diff --git a/src/samples/transport-interop/Program.cs b/src/samples/transport-interop/Program.cs new file mode 100644 index 00000000..2a497de6 --- /dev/null +++ b/src/samples/transport-interop/Program.cs @@ -0,0 +1,151 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: MIT + +using Microsoft.Extensions.DependencyInjection; +using Nethermind.Libp2p.Core; +using Nethermind.Libp2p.Protocols; +using StackExchange.Redis; +using System.Diagnostics; +using System.Net.NetworkInformation; +using System.Net.Sockets; + + +try +{ + string transport = Environment.GetEnvironmentVariable("transport")!; + string muxer = Environment.GetEnvironmentVariable("muxer")!; + string security = Environment.GetEnvironmentVariable("security")!; + + bool isDialer = bool.Parse(Environment.GetEnvironmentVariable("is_dialer")!); + string ip = Environment.GetEnvironmentVariable("ip") ?? "0.0.0.0"; + + string redisAddr = Environment.GetEnvironmentVariable("redis_addr") ?? "redis:6379"; + + int testTimeoutSeconds = int.Parse(Environment.GetEnvironmentVariable("test_timeout_seconds") ?? "180"); + + TestPlansPeerFactoryBuilder builder = new TestPlansPeerFactoryBuilder(transport, muxer, security); + IPeerFactory peerFactory = builder.Build(); + + Log($"Connecting to redis at {redisAddr}..."); + ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(redisAddr); + IDatabase db = redis.GetDatabase(); + + if (isDialer) + { + ILocalPeer localPeer = peerFactory.Create(localAddr: builder.MakeAddress()); + + Log($"Picking an address to dial..."); + + CancellationTokenSource cts = new(TimeSpan.FromSeconds(10)); + string? listenerAddr = null; + while ((listenerAddr = await db.ListRightPopAsync("listenerAddr")) is null) + { + await Task.Delay(10, cts.Token); + } + + Log($"Dialing {listenerAddr}..."); + Stopwatch handshakeStartInstant = Stopwatch.StartNew(); + IRemotePeer remotePeer = await localPeer.DialAsync(listenerAddr); + + Stopwatch pingIstant = Stopwatch.StartNew(); + await remotePeer.DialAsync(); + long pingRTT = pingIstant.ElapsedMilliseconds; + + long handshakePlusOneRTT = handshakeStartInstant.ElapsedMilliseconds; + + PrintResult($"{{\"handshakePlusOneRTTMillis\": {handshakePlusOneRTT}, \"pingRTTMilllis\": {pingRTT}}}"); + Log("Done"); + return 0; + } + else + { + if (ip == "0.0.0.0") + { + IEnumerable addresses = NetworkInterface.GetAllNetworkInterfaces()! + .FirstOrDefault(i => i.Name == "eth0")! + .GetIPProperties() + .UnicastAddresses + .Where(a => a.Address.AddressFamily == AddressFamily.InterNetwork); + + Log("Available addresses detected, picking the first: " + string.Join(",", addresses.Select(a => a.Address))); + ip = addresses.First().Address.ToString()!; + } + Log("Starting to listen..."); + ILocalPeer localPeer = peerFactory.Create(localAddr: builder.MakeAddress(ip)); + IListener listener = await localPeer.ListenAsync(localPeer.Address); + listener.OnConnection += (peer) => { Log($"Connected {peer.Address}"); return Task.CompletedTask; }; + Log($"Listening on {listener.Address}"); + db.ListRightPush(new RedisKey("listenerAddr"), new RedisValue(listener.Address.ToString())); + await Task.Delay(testTimeoutSeconds * 1000); + await listener.DisconnectAsync(); + return -1; + } +} +catch (Exception ex) +{ + Log(ex.Message); + return -1; +} + +static void Log(string info) => Console.Error.WriteLine(info); +static void PrintResult(string info) => Console.WriteLine(info); + +class TestPlansPeerFactoryBuilder : PeerFactoryBuilderBase +{ + private readonly string transport; + private readonly string? muxer; + private readonly string? security; + private static IPeerFactoryBuilder? defaultPeerFactoryBuilder; + + public TestPlansPeerFactoryBuilder(string transport, string? muxer, string? security) + : base(new ServiceCollection() + .AddScoped(_ => defaultPeerFactoryBuilder!) + .BuildServiceProvider()) + { + defaultPeerFactoryBuilder = this; + this.transport = transport; + this.muxer = muxer; + this.security = security; + } + + private static readonly string[] stacklessProtocols = ["quic", "quic-v1", "webtransport"]; + + protected override ProtocolStack BuildStack() + { + ProtocolStack stack = transport switch + { + "tcp" => Over(), + // TODO: Improve QUIC imnteroperability + "quic-v1" => Over(), + _ => throw new NotImplementedException(), + }; + + stack = stack.Over(); + + if (!stacklessProtocols.Contains(transport)) + { + stack = security switch + { + "noise" => stack.Over(), + _ => throw new NotImplementedException(), + }; + stack = stack.Over(); + stack = muxer switch + { + "yamux" => stack.Over(), + _ => throw new NotImplementedException(), + }; + stack = stack.Over(); + } + + return stack.AddAppLayerProtocol() + .AddAppLayerProtocol(); + } + + public string MakeAddress(string ip = "0.0.0.0", string port = "0") => transport switch + { + "tcp" => $"/ip4/{ip}/tcp/{port}", + "quic-v1" => $"/ip4/{ip}/udp/{port}/quic-v1", + _ => throw new NotImplementedException(), + }; +} diff --git a/src/samples/transport-interop/Properties/launchSettings.json b/src/samples/transport-interop/Properties/launchSettings.json new file mode 100644 index 00000000..b1b72441 --- /dev/null +++ b/src/samples/transport-interop/Properties/launchSettings.json @@ -0,0 +1,24 @@ +{ + "profiles": { + "TransportInterop - Client": { + "commandName": "Project", + "environmentVariables": { + "is_dialer": "true", + "transport": "tcp", + "muxer": "yamux", + "security": "noise", + "redis_addr": "127.0.0.1:6379" + } + }, + "TransportInterop - Server": { + "commandName": "Project", + "environmentVariables": { + "is_dialer": "false", + "transport": "tcp", + "muxer": "yamux", + "security": "noise", + "redis_addr": "127.0.0.1:6379" + } + } + } +} diff --git a/src/samples/transport-interop/TransportInterop.csproj b/src/samples/transport-interop/TransportInterop.csproj new file mode 100644 index 00000000..42309408 --- /dev/null +++ b/src/samples/transport-interop/TransportInterop.csproj @@ -0,0 +1,30 @@ + + + + Exe + net8.0 + transport_interop + enable + enable + true + true + true + + + + + + + + + + + + + + + + + + + diff --git a/src/samples/transport-interop/packages.lock.json b/src/samples/transport-interop/packages.lock.json new file mode 100644 index 00000000..cd92ee1e --- /dev/null +++ b/src/samples/transport-interop/packages.lock.json @@ -0,0 +1,1267 @@ +{ + "version": 1, + "dependencies": { + "net8.0": { + "Microsoft.Extensions.Logging": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "tvRkov9tAJ3xP51LCv3FJ2zINmv1P8Hi8lhhtcKGqM+ImiTCC84uOPEI4z8Cdq2C3o9e+Aa0Gw0rmrsJD77W+w==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.Logging.Console": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "e+48o7DztoYog+PY430lPxrM4mm3PbA6qucvQtUDDwVo4MO+ejMw7YGc/o2rnxbxj4isPxdfKFzTxvXMwAz83A==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Logging": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.Extensions.Logging.Configuration": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0", + "System.Text.Json": "8.0.0" + } + }, + "NRedisStack": { + "type": "Direct", + "requested": "[0.11.0, )", + "resolved": "0.11.0", + "contentHash": "v27MRxtH2pxQBPU1qleqpIi0VWDB0BR6IU+ozpHuNdjchGahZcK/XzQSpHK6AMCN4amQ+mIsH7Dh/Y+PxrZ/Qw==", + "dependencies": { + "NetTopologySuite": "2.5.0", + "StackExchange.Redis": "2.6.122" + } + }, + "BinaryEncoding": { + "type": "Transitive", + "resolved": "1.4.0", + "contentHash": "1cnkP90c+zNcRyabjKSA3VYJvpYfkGEpXeekfF8KdTFo3VyUUFOioAsANbG8nsMyedGcmUOqHWd1d3fOXke4VA==", + "dependencies": { + "NETStandard.Library": "1.6.1", + "System.Buffers": "4.4.0" + } + }, + "BouncyCastle.Cryptography": { + "type": "Transitive", + "resolved": "2.2.1", + "contentHash": "A6Zr52zVqJKt18ZBsTnX0qhG0kwIQftVAjLmszmkiR/trSp8H+xj1gUOzk7XHwaKgyREMSV1v9XaKrBUeIOdvQ==" + }, + "Google.Protobuf": { + "type": "Transitive", + "resolved": "3.25.1", + "contentHash": "Sw9bq4hOD+AaS3RrnmP5IT25cyZ/T1qpM0e8+G+23Nojhv7+ScJFPEAQo1m4EFQWhXoI4FRZDrK+wjHCPw9yxg==" + }, + "libsodium": { + "type": "Transitive", + "resolved": "1.0.16", + "contentHash": "rdqn+/u7cwwjMwEAiPEDfCCv8+rOo8MFSb4ImxbC1toyP5dOVwQVTkWt6gDPj8S6SypoE/9rRgBz6I/qjJvzqw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1" + } + }, + "Microsoft.Extensions.Configuration": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "0J/9YNXTMWSZP2p2+nvl8p71zpSwokZXZuJW+VjdErkegAnFdO1XlqtA62SJtgVYHdKu3uPxJHcMR/r35HwFBA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "mBMoXLsr5s1y2zOHWmKsE9veDcx8h1x/c3rz4baEdQKTeDcmQAPNbB54Pi/lhFO3K431eEq6PFbMgLaa6PHFfA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg==" + }, + "Microsoft.Extensions.Logging.Configuration": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "ixXXV0G/12g6MXK65TLngYN9V5hQQRuV+fZi882WIoVJT7h5JvoYoxTEwCgdqwLjSneqh1O+66gM8sMr9z/rsQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "8.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Configuration.Binder": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Logging": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0", + "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0" + } + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "0f4DMRqEd50zQh+UyJc+/HiBsZ3vhAQALgdkcQEalSH1L2isdC7Yj54M3cyo5e+BeO5fcBQ7Dxly8XiBBcvRgw==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Configuration.Binder": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==" + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" + }, + "Microsoft.NETCore.Targets": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" + }, + "Microsoft.Win32.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "Multiformats.Base": { + "type": "Transitive", + "resolved": "2.0.1", + "contentHash": "JherI2cl97crsQHN5pwwNIlz004D64szvvXRRq8XVXQR2ZOFTaW5UEs8sJmt80bhW3cHH7XP4ooCqGYr/WBNRw==", + "dependencies": { + "NETStandard.Library": "1.6.1", + "System.Runtime.Numerics": "4.3.0" + } + }, + "murmurhash": { + "type": "Transitive", + "resolved": "1.0.2", + "contentHash": "Yw9+sYL3qdTEXDKAEeiXsVwsP2K2nyWOxgvbDD1w5j+yu0CYk5edLvGmmJHqqFxuBFrVsgb7iF2XGprRlt+SEA==" + }, + "NETStandard.Library": { + "type": "Transitive", + "resolved": "1.6.1", + "contentHash": "WcSp3+vP+yHNgS8EV5J7pZ9IRpeDuARBPN28by8zqff1wJQXm26PVU8L3/fYLBJVU7BtDyqNVWq2KlCVvSSR4A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.Win32.Primitives": "4.3.0", + "System.AppContext": "4.3.0", + "System.Collections": "4.3.0", + "System.Collections.Concurrent": "4.3.0", + "System.Console": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.Tools": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Calendars": "4.3.0", + "System.IO": "4.3.0", + "System.IO.Compression": "4.3.0", + "System.IO.Compression.ZipFile": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Linq": "4.3.0", + "System.Linq.Expressions": "4.3.0", + "System.Net.Http": "4.3.0", + "System.Net.Primitives": "4.3.0", + "System.Net.Sockets": "4.3.0", + "System.ObjectModel": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Extensions": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Security.Cryptography.X509Certificates": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Text.Encoding.Extensions": "4.3.0", + "System.Text.RegularExpressions": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "System.Threading.Timer": "4.3.0", + "System.Xml.ReaderWriter": "4.3.0", + "System.Xml.XDocument": "4.3.0" + } + }, + "NetTopologySuite": { + "type": "Transitive", + "resolved": "2.5.0", + "contentHash": "5/+2O2ADomEdUn09mlSigACdqvAf0m/pVPGtIPEPQWnyrVykYY0NlfXLIdkMgi41kvH9kNrPqYaFBTZtHYH7Xw==", + "dependencies": { + "System.Memory": "4.5.4" + } + }, + "Noise.NET": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "fYnHQ8yZcj9W0fPGbzMkZUnE14aGGTFS8WE0Ow2hXiGhJ61Tv71cTi1yuugHxPCLyb87JpWMkq4lix8Rf06vtA==", + "dependencies": { + "NETStandard.Library": "1.6.1", + "System.Buffers": "4.5.0", + "System.Memory": "4.5.0", + "System.ValueTuple": "4.4.0", + "libsodium": "1.0.16" + } + }, + "Pipelines.Sockets.Unofficial": { + "type": "Transitive", + "resolved": "2.2.8", + "contentHash": "zG2FApP5zxSx6OcdJQLbZDk2AVlN2BNQD6MorwIfV6gVj0RRxWPEp2LXAxqDGZqeNV1Zp0BNPcNaey/GXmTdvQ==", + "dependencies": { + "System.IO.Pipelines": "5.0.1" + } + }, + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q==" + }, + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA==" + }, + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw==" + }, + "runtime.native.System": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "runtime.native.System.IO.Compression": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "INBPonS5QPEgn7naufQFXJEp3zX6L4bwHgJ/ZH78aBTpeNfQMtf7C6VrAFhlq2xxWBveIOWyFzQjJ8XzHMhdOQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "runtime.native.System.Net.Http": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "runtime.native.System.Security.Cryptography.Apple": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", + "dependencies": { + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" + } + }, + "runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==", + "dependencies": { + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A==" + }, + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ==" + }, + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==" + }, + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g==" + }, + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg==" + }, + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ==" + }, + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A==" + }, + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg==" + }, + "SimpleBase": { + "type": "Transitive", + "resolved": "4.0.0", + "contentHash": "X9VdQGnMwRZ7ve1eGgzoRxV/srWCQfMWRaFzK8KsnA9P2N0LKUcELAdSW8noAY0JPKkDXNDtpH65CeVQwDDf+w==", + "dependencies": { + "System.Memory": "4.5.5" + } + }, + "StackExchange.Redis": { + "type": "Transitive", + "resolved": "2.6.122", + "contentHash": "wp7mvGpFXaevfZ07/SDeh/6YHUJEgwJIGyjbDWKBYbPwKMJQYFz9zFEmBptqtVzqvSgft5nlewwutoaMaG0LPA==", + "dependencies": { + "Pipelines.Sockets.Unofficial": "2.2.8" + } + }, + "System.AppContext": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "fKC+rmaLfeIzUhagxY17Q9siv/sPrjjKcfNg1Ic8IlQkZLipo8ljcaZQu4VtI4Jqbzjc2VTjzGLF6WmsRXAEgA==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "pL2ChpaRRWI/p4LXyy4RgeWlYF2sgfj/pnVMvBqwNFr5cXg7CXNnWZWxrOONLg8VGdFB8oB+EG2Qw4MLgTOe+A==" + }, + "System.Collections": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Collections.Concurrent": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Composition": { + "type": "Transitive", + "resolved": "1.2.0", + "contentHash": "nTgIj77StlLM7CW3uFM3B/0Yen5udzaeSQcdSCVV3wIlRGYsXYLjZWTYa9m8IBjQiyZKsukKYaogqhOa6QUlDA==", + "dependencies": { + "System.Composition.AttributedModel": "1.2.0", + "System.Composition.Convention": "1.2.0", + "System.Composition.Hosting": "1.2.0", + "System.Composition.Runtime": "1.2.0", + "System.Composition.TypedParts": "1.2.0" + } + }, + "System.Composition.AttributedModel": { + "type": "Transitive", + "resolved": "1.2.0", + "contentHash": "IQ2bn1BR/Q7gapjnXR/HGh0BMtjYVU0t0uPZ3LXE4yfwjM7x/HcImJxwwhUtnL+YWU5/pTOhzZnqsjwKJpWaug==" + }, + "System.Composition.Convention": { + "type": "Transitive", + "resolved": "1.2.0", + "contentHash": "g9PSAdL/0dT3GZbdwt5r238RLHfnn+ujRVhoOGvVNjbbhlgZeKcDA+zsje4Y81csMywAPsDXkeXrBigtjINurg==", + "dependencies": { + "System.Composition.AttributedModel": "1.2.0" + } + }, + "System.Composition.Hosting": { + "type": "Transitive", + "resolved": "1.2.0", + "contentHash": "NQa4OanHFuWVpMuj3+0RnoAq2v+5KQNA3+EYuhmuDbOfR06o7rYjzs9FHP0XWJWN85vqnM76dgAgj46OYsDV8A==", + "dependencies": { + "System.Composition.Runtime": "1.2.0" + } + }, + "System.Composition.Runtime": { + "type": "Transitive", + "resolved": "1.2.0", + "contentHash": "F8Ef3y9/JKbK4lEqJScFnfhT8/CwboGS890a/Js9E11wb1N6rl63pU8wxRPmy2MUUUHSafxrF3ooIh94pNEF0g==" + }, + "System.Composition.TypedParts": { + "type": "Transitive", + "resolved": "1.2.0", + "contentHash": "cLjoUGnaLRkJSwL6FLEx3aJanDgwEtyoEqf9cE6Z5ipbjNXAlk7W11uwNfHaECxdPa/QfGbvaRd4i24gxc5ygg==", + "dependencies": { + "System.Composition.AttributedModel": "1.2.0", + "System.Composition.Hosting": "1.2.0", + "System.Composition.Runtime": "1.2.0" + } + }, + "System.Console": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "DHDrIxiqk1h03m6khKWV2X8p/uvN79rgSqpilL6uzpmSfxfU5ng8VcPtW4qsDsQDHiTv6IPV9TmD5M/vElPNLg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0" + } + }, + "System.Diagnostics.Debug": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Diagnostics.Tools": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Diagnostics.Tracing": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Globalization": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Globalization.Calendars": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Globalization": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Globalization.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.InteropServices": "4.3.0" + } + }, + "System.IO": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.IO.Compression": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Buffers": "4.3.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "runtime.native.System": "4.3.0", + "runtime.native.System.IO.Compression": "4.3.0" + } + }, + "System.IO.Compression.ZipFile": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "G4HwjEsgIwy3JFBduZ9quBkAu+eUwjIdJleuNSgmUojbH6O3mlvEIme+GHx/cLlTAPcrnnL7GqvB9pTlWRfhOg==", + "dependencies": { + "System.Buffers": "4.3.0", + "System.IO": "4.3.0", + "System.IO.Compression": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Text.Encoding": "4.3.0" + } + }, + "System.IO.FileSystem": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.IO.FileSystem.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, + "System.IO.Pipelines": { + "type": "Transitive", + "resolved": "5.0.1", + "contentHash": "qEePWsaq9LoEEIqhbGe6D5J8c9IqQOUuTzzV6wn1POlfdLkJliZY3OlB0j0f17uMWlqZYjH7txj+2YbyrIA8Yg==" + }, + "System.Linq": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0" + } + }, + "System.Linq.Expressions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.Linq": "4.3.0", + "System.ObjectModel": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Emit": "4.3.0", + "System.Reflection.Emit.ILGeneration": "4.3.0", + "System.Reflection.Emit.Lightweight": "4.3.0", + "System.Reflection.Extensions": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Reflection.TypeExtensions": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.5", + "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" + }, + "System.Net.Http": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.DiagnosticSource": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Extensions": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.Net.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.OpenSsl": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Security.Cryptography.X509Certificates": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "runtime.native.System": "4.3.0", + "runtime.native.System.Net.Http": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Net.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0" + } + }, + "System.Net.Sockets": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Net.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.ObjectModel": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Reflection": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Emit": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", + "dependencies": { + "System.IO": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Emit.ILGeneration": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Emit.ILGeneration": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", + "dependencies": { + "System.Reflection": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Emit.Lightweight": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==", + "dependencies": { + "System.Reflection": "4.3.0", + "System.Reflection.Emit.ILGeneration": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.TypeExtensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==", + "dependencies": { + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Resources.ResourceManager": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Globalization": "4.3.0", + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "System.Runtime.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime.Handles": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime.InteropServices": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Reflection": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0" + } + }, + "System.Runtime.InteropServices.RuntimeInformation": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==", + "dependencies": { + "System.Reflection": "4.3.0", + "System.Reflection.Extensions": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Threading": "4.3.0", + "runtime.native.System": "4.3.0" + } + }, + "System.Runtime.Numerics": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", + "dependencies": { + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0" + } + }, + "System.Security.Cryptography.Algorithms": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.Apple": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.Cng": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0" + } + }, + "System.Security.Cryptography.Csp": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Security.Cryptography.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Collections.Concurrent": "4.3.0", + "System.Linq": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", + "dependencies": { + "System.Collections": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", + "dependencies": { + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Security.Cryptography.X509Certificates": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Calendars": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Cng": "4.3.0", + "System.Security.Cryptography.Csp": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.OpenSsl": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "runtime.native.System": "4.3.0", + "runtime.native.System.Net.Http": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Text.Encoding.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0" + } + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==" + }, + "System.Text.Json": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "OdrZO2WjkiEG6ajEFRABTRCi/wuXQPxeV6g8xvUJqdxMvvuCCEk86zPla8UiIQJz3durtUEbNyY/3lIhS0yZvQ==", + "dependencies": { + "System.Text.Encodings.Web": "8.0.0" + } + }, + "System.Text.RegularExpressions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, + "System.Threading": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", + "dependencies": { + "System.Runtime": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Threading.Tasks.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Threading.Timer": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.ValueTuple": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "BahUww/+mdP4ARCAh2RQhQTg13wYLVrBb9SYVgW8ZlrwjraGCXHGjo0oIiUfZ34LUZkMMR+RAzR7dEY4S1HeQQ==" + }, + "System.Xml.ReaderWriter": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Text.Encoding.Extensions": "4.3.0", + "System.Text.RegularExpressions": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "System.Threading.Tasks.Extensions": "4.3.0" + } + }, + "System.Xml.XDocument": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.Tools": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "System.Xml.ReaderWriter": "4.3.0" + } + }, + "multiformats.address": { + "type": "Project", + "dependencies": { + "BinaryEncoding": "[1.3.4, )", + "Multiformats.Base": "[2.0.1, )", + "Multiformats.Hash": "[1.3.0, )" + } + }, + "multiformats.hash": { + "type": "Project", + "dependencies": { + "BinaryEncoding": "[1.4.0, )", + "BouncyCastle.Cryptography": "[2.2.1, )", + "Multiformats.Base": "[2.0.1, )", + "System.Composition": "[1.2.0, )", + "murmurhash": "[1.0.2, )" + } + }, + "Nethermind.Libp2p.Core": { + "type": "Project", + "dependencies": { + "BouncyCastle.Cryptography": "[2.2.1, )", + "Google.Protobuf": "[3.25.1, )", + "Microsoft.Extensions.DependencyInjection": "[8.0.0, )", + "Microsoft.Extensions.DependencyInjection.Abstractions": "[8.0.0, )", + "Microsoft.Extensions.Logging.Abstractions": "[8.0.0, )", + "Multiformats.Address": "[1.1.1, )", + "Multiformats.Hash": "[1.5.0, )", + "SimpleBase": "[4.0.0, )" + } + }, + "Nethermind.Libp2p.Protocols.Identify": { + "type": "Project", + "dependencies": { + "Google.Protobuf": "[3.25.1, )", + "Microsoft.Extensions.Logging.Abstractions": "[8.0.0, )", + "Nethermind.Libp2p.Core": "[1.0.0, )", + "Nethermind.Libp2p.Protocols.IpTcp": "[1.0.0, )" + } + }, + "Nethermind.Libp2p.Protocols.IpTcp": { + "type": "Project", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "[8.0.0, )", + "Nethermind.Libp2p.Core": "[1.0.0, )" + } + }, + "Nethermind.Libp2p.Protocols.Multistream": { + "type": "Project", + "dependencies": { + "Nethermind.Libp2p.Core": "[1.0.0, )" + } + }, + "Nethermind.Libp2p.Protocols.Noise": { + "type": "Project", + "dependencies": { + "BouncyCastle.Cryptography": "[2.2.1, )", + "Google.Protobuf": "[3.25.1, )", + "Multiformats.Hash": "[1.5.0, )", + "Nethermind.Libp2p.Core": "[1.0.0, )", + "Noise.NET": "[1.0.0, )" + } + }, + "Nethermind.Libp2p.Protocols.Ping": { + "type": "Project", + "dependencies": { + "Nethermind.Libp2p.Core": "[1.0.0, )" + } + }, + "Nethermind.Libp2p.Protocols.Quic": { + "type": "Project", + "dependencies": { + "BouncyCastle.Cryptography": "[2.2.1, )", + "Microsoft.Extensions.Logging.Abstractions": "[8.0.0, )", + "Multiformats.Hash": "[1.5.0, )", + "Nethermind.Libp2p.Core": "[1.0.0, )" + } + }, + "Nethermind.Libp2p.Protocols.Yamux": { + "type": "Project", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "[8.0.0, )", + "Nethermind.Libp2p.Core": "[1.0.0, )" + } + } + } + } +} \ No newline at end of file From a2ce6e1cde834e547c795d22a0b1b66139735748 Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Thu, 11 Apr 2024 16:33:55 +0300 Subject: [PATCH 02/14] Before channel update --- .../Libp2p.Core.TestsBase/TestChannel.cs | 7 +- src/libp2p/Libp2p.Core/Channel.cs | 7 +- src/libp2p/Libp2p.Core/PeerFactory.cs | 3 +- .../Libp2p.Protocols.IpTcp/IpTcpProtocol.cs | 15 +- .../YamuxProtocolTests.cs | 196 ++++++++++++++---- .../Libp2p.Protocols.Yamux/YamuxProtocol.cs | 24 +-- src/samples/transport-interop/Program.cs | 21 +- 7 files changed, 204 insertions(+), 69 deletions(-) diff --git a/src/libp2p/Libp2p.Core.TestsBase/TestChannel.cs b/src/libp2p/Libp2p.Core.TestsBase/TestChannel.cs index 0a345686..366a8485 100644 --- a/src/libp2p/Libp2p.Core.TestsBase/TestChannel.cs +++ b/src/libp2p/Libp2p.Core.TestsBase/TestChannel.cs @@ -18,7 +18,7 @@ public TestChannel() public bool IsClosed => _channel.IsClosed; public CancellationToken Token => _channel.Token; - public Task CloseAsync(bool graceful = true) + public Task CloseAsync() { return _channel.CloseAsync(); } @@ -58,4 +58,9 @@ public ValueTask CanReadAsync(CancellationToken token = default) { return _channel.CanReadAsync(token); } + + public Task CloseAsync(bool graceful = true) + { + return Task.CompletedTask; + } } diff --git a/src/libp2p/Libp2p.Core/Channel.cs b/src/libp2p/Libp2p.Core/Channel.cs index 0d31e627..059b30a8 100644 --- a/src/libp2p/Libp2p.Core/Channel.cs +++ b/src/libp2p/Libp2p.Core/Channel.cs @@ -68,10 +68,11 @@ public IChannel Reverse public CancellationToken Token => State.Token; - public Task CloseAsync(bool graceful = true) + public async Task CloseAsync() { + await Writer.WriteEofAsync(); + State.Cancel(); - return Task.CompletedTask; } public void OnClose(Func action) @@ -257,4 +258,6 @@ public ValueTask WriteAsync(ReadOnlySequence bytes) public ValueTask WriteEofAsync() => Writer.WriteEofAsync(); public ValueTask CanReadAsync(CancellationToken token = default) => Reader.CanReadAsync(token); + + public Task CloseAsync(bool graceful = true) => WriteEofAsync().AsTask(); } diff --git a/src/libp2p/Libp2p.Core/PeerFactory.cs b/src/libp2p/Libp2p.Core/PeerFactory.cs index b4b530a7..7a9635dc 100644 --- a/src/libp2p/Libp2p.Core/PeerFactory.cs +++ b/src/libp2p/Libp2p.Core/PeerFactory.cs @@ -22,7 +22,8 @@ public PeerFactory(IServiceProvider serviceProvider) public virtual ILocalPeer Create(Identity? identity = default, Multiaddress? localAddr = default) { - return new LocalPeer(this) { Identity = identity, Address = localAddr ?? $"/ip4/0.0.0.0/tcp/0/" }; + identity ??= new Identity(); + return new LocalPeer(this) { Identity = identity ?? new Identity(), Address = localAddr ?? $"/ip4/0.0.0.0/tcp/0/p2p/{identity.PeerId}" }; } /// diff --git a/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs b/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs index fece64b8..b2fb72f8 100644 --- a/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs @@ -25,8 +25,6 @@ public IpTcpProtocol(ILoggerFactory? loggerFactory = null) public async Task ListenAsync(IChannel channel, IChannelFactory? channelFactory, IPeerContext context) { - _logger?.LogInformation("ListenAsync({contextId})", context.Id); - Multiaddress addr = context.LocalPeer.Address; bool isIP4 = addr.Has(); MultiaddressProtocol ipProtocol = isIP4 ? addr.Get() : addr.Get(); @@ -93,8 +91,9 @@ await Task.Run(async () => } } } - catch (SocketException) + catch (SocketException e) { + await chan.CloseAsync(false); } }, chan.Token); @@ -117,8 +116,13 @@ await Task.Run(async () => }); } - public async Task DialAsync(IChannel channel, IChannelFactory channelFactory, IPeerContext context) + public async Task DialAsync(IChannel channel, IChannelFactory? channelFactory, IPeerContext context) { + if(channelFactory is null) + { + throw new ProtocolViolationException(); + } + _logger?.LogInformation("DialAsync({contextId})", context.Id); TaskCompletionSource waitForStop = new(TaskCreationOptions.RunContinuationsAsynchronously); @@ -158,7 +162,8 @@ public async Task DialAsync(IChannel channel, IChannelFactory channelFactory, IP context.LocalPeer.Address = context.LocalEndpoint.Add(context.LocalPeer.Identity.PeerId.ToString()); IChannel upChannel = channelFactory.SubDial(context); - channel.Token.Register(() => upChannel.CloseAsync()); + channel.GetAwaiter().OnCompleted(() => upChannel.CloseAsync()); + //upChannel.OnClosing += (graceful) => upChannel.CloseAsync(graceful); Task receiveTask = Task.Run(async () => diff --git a/src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs b/src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs index 199fc4a0..fed43741 100644 --- a/src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs +++ b/src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs @@ -1,10 +1,16 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: MIT +using Microsoft.Extensions.Logging; using Nethermind.Libp2p.Core; using Nethermind.Libp2p.Core.TestsBase; using NSubstitute; +using NUnit.Framework.Internal; +using Org.BouncyCastle.Crypto.Agreement.Srp; using System.Buffers; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Threading.Channels; namespace Nethermind.Libp2p.Protocols.Noise.Tests; @@ -18,58 +24,115 @@ public class YamuxProtocolTests // Exchange data // Expect error and react to it + //[Test] + //public async Task Test_Connection_Ackowledged() + //{ + // IChannel downChannel = new TestChannel(); + // IChannel downChannelFromProtocolPov = ((TestChannel)downChannel).Reverse(); + // IChannelFactory channelFactory = Substitute.For(); + // IPeerContext peerContext = Substitute.For(); + + // IProtocol? proto1 = Substitute.For(); + // proto1.Id.Returns("proto1"); + // channelFactory.SubProtocols.Returns(new[] { proto1 }); + // IChannel upChannel = new TestChannel(); + // channelFactory.SubDialAndBind(Arg.Any(), Arg.Any(), Arg.Any()) + // .Returns(upChannel); + + // YamuxProtocol proto = new(); + // _ = proto.DialAsync(downChannelFromProtocolPov, channelFactory, peerContext); + // await downChannel.WriteLineAsync(proto.Id); + // await downChannel.WriteLineAsync("proto1"); + + // Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo(proto.Id)); + // Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo("proto1")); + // channelFactory.Received().SubDialAndBind(downChannelFromProtocolPov, peerContext, proto1); + // await downChannel.CloseAsync(); + //} + + //[Test] + //public async Task Test_ConnectionClosed_ForBrokenHandshake() + //{ + // // IChannel downChannel = new TestChannel(); + // // IChannel downChannelFromProtocolPov = ((TestChannel)downChannel).Reverse(); + // // IChannelFactory channelFactory = Substitute.For(); + // // IPeerContext peerContext = Substitute.For(); + // // + // // IProtocol? proto1 = Substitute.For(); + // // proto1.Id.Returns("proto1"); + // // channelFactory.SubProtocols.Returns(new[] { proto1 }); + // // IChannel upChannel = new TestChannel(); + // // channelFactory.SubDialAndBind(Arg.Any(), Arg.Any(), Arg.Any()) + // // .Returns(upChannel); + // // + // // NoiseProtocol proto = new(); + // // _ = proto.DialAsync(downChannelFromProtocolPov, channelFactory, peerContext); + // // await downChannel.WriteLineAsync(proto.Id); + // // await downChannel.WriteLineAsync("proto2"); + // // + // // Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo(proto.Id)); + // // Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo("proto1")); + // // channelFactory.DidNotReceive().SubDialAndBind(downChannelFromProtocolPov, peerContext, proto1); + // // await upChannel.CloseAsync(); + //} + [Test] public async Task Test_Connection_Ackowledged() { - IChannel downChannel = new TestChannel(); - IChannel downChannelFromProtocolPov = ((TestChannel)downChannel).Reverse(); - IChannelFactory channelFactory = Substitute.For(); - IPeerContext peerContext = Substitute.For(); - IProtocol? proto1 = Substitute.For(); proto1.Id.Returns("proto1"); - channelFactory.SubProtocols.Returns(new[] { proto1 }); - IChannel upChannel = new TestChannel(); - channelFactory.SubDialAndBind(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(upChannel); - - YamuxProtocol proto = new(); - _ = proto.DialAsync(downChannelFromProtocolPov, channelFactory, peerContext); - await downChannel.WriteLineAsync(proto.Id); - await downChannel.WriteLineAsync("proto1"); - - Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo(proto.Id)); - Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo("proto1")); - channelFactory.Received().SubDialAndBind(downChannelFromProtocolPov, peerContext, proto1); - await downChannel.CloseAsync(); - } + IPeerContext dialerPeerContext = Substitute.For(); + var dialerRequests = new BlockingCollection() { new ChannelRequest() { SubProtocol = proto1 } }; + dialerPeerContext.SubDialRequests.Returns(dialerRequests); + + TestChannel dialerDownChannel = new TestChannel(); + IChannelFactory dialerUpchannelFactory = Substitute.For(); + dialerUpchannelFactory.SubProtocols.Returns(new[] { proto1 }); + TestChannel dialerUpChannel = new TestChannel(); + dialerUpchannelFactory.SubDial(Arg.Any(), Arg.Any()) + .Returns(dialerUpChannel); + + + _ = dialerUpChannel.Reverse().WriteLineAsync("hello").AsTask().ContinueWith((e) => dialerUpChannel.CloseAsync()); + + IPeerContext listenerPeerContext = Substitute.For(); + IChannel listenerDownChannel = dialerDownChannel.Reverse(); + IChannelFactory listenerUpchannelFactory = Substitute.For(); + var listenerRequests = new BlockingCollection(); + listenerPeerContext.SubDialRequests.Returns(listenerRequests); + listenerUpchannelFactory.SubProtocols.Returns(new[] { proto1 }); + TestChannel listenerUpChannel = new TestChannel(); + listenerUpchannelFactory.SubListen(Arg.Any(), Arg.Any()) + .Returns(listenerUpChannel); + + YamuxProtocol proto = new(new DebugLoggerFactory()); + + _ = proto.ListenAsync(listenerDownChannel, listenerUpchannelFactory, listenerPeerContext); + + _ = proto.DialAsync(dialerDownChannel, dialerUpchannelFactory, dialerPeerContext); + + + var res = await listenerUpChannel.Reverse().ReadLineAsync(); + //await listenerUpChannel.CloseAsync(); + + Assert.That(res, Is.EqualTo("hello")); + + await Task.Delay(1000); + //IChannel listenerUpChannel = new TestChannel(); + - [Test] - public async Task Test_ConnectionClosed_ForBrokenHandshake() - { - // IChannel downChannel = new TestChannel(); - // IChannel downChannelFromProtocolPov = ((TestChannel)downChannel).Reverse(); - // IChannelFactory channelFactory = Substitute.For(); - // IPeerContext peerContext = Substitute.For(); - // - // IProtocol? proto1 = Substitute.For(); - // proto1.Id.Returns("proto1"); - // channelFactory.SubProtocols.Returns(new[] { proto1 }); - // IChannel upChannel = new TestChannel(); - // channelFactory.SubDialAndBind(Arg.Any(), Arg.Any(), Arg.Any()) - // .Returns(upChannel); - // - // NoiseProtocol proto = new(); - // _ = proto.DialAsync(downChannelFromProtocolPov, channelFactory, peerContext); - // await downChannel.WriteLineAsync(proto.Id); - // await downChannel.WriteLineAsync("proto2"); // - // Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo(proto.Id)); - // Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo("proto1")); - // channelFactory.DidNotReceive().SubDialAndBind(downChannelFromProtocolPov, peerContext, proto1); - // await upChannel.CloseAsync(); + //_ = proto.DialAsync(downChannelFromProtocolPov, channelFactory, peerContext); + //await downChannel.WriteLineAsync(proto.Id); + //await downChannel.WriteLineAsync("proto1"); + + //Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo(proto.Id)); + //Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo("proto1")); + //channelFactory.Received().SubDialAndBind(downChannelFromProtocolPov, peerContext, proto1); + //await downChannel.CloseAsync(); } + class PingPongProtocol : IProtocol { public string Id => throw new NotImplementedException(); @@ -92,3 +155,50 @@ public async Task ListenAsync(IChannel downChannel, IChannelFactory? upChannelFa } } } + +internal class DebugLoggerFactory : Microsoft.Extensions.Logging.ILoggerFactory +{ + class DebugLogger : Microsoft.Extensions.Logging.ILogger, IDisposable + { + private string categoryName; + + public DebugLogger(string categoryName) + { + this.categoryName = categoryName; + } + + public IDisposable? BeginScope(TState state) where TState : notnull + { + return this; + } + + public void Dispose() + { + } + + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + TestContext.Out.WriteLine($"{logLevel} {categoryName}:{eventId}: {(exception is null ? state?.ToString() : formatter(state, exception))}"); + } + } + + public void AddProvider(ILoggerProvider provider) + { + + } + + public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) + { + return new DebugLogger(categoryName); + } + + public void Dispose() + { + + } +} diff --git a/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs b/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs index a0bb3039..862c8ff3 100644 --- a/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs @@ -63,13 +63,7 @@ await WriteHeaderAsync(channel, } }); - bool f = true; - channel.OnClose(() => - { - f = false; - return Task.CompletedTask; - }); - while (f) + while (await channel.CanReadAsync()) { YamuxHeader header = await ReadHeaderAsync(channel); ReadOnlySequence data = default; @@ -168,6 +162,8 @@ await WriteHeaderAsync(channel, } } + await WriteGoAwayAsync(channel, SessionTerminationCode.Ok); + async Task ActivateUpchannel(int streamId, IChannelRequest? channelRequest) { if (channels[streamId].Channel is not null) @@ -193,12 +189,13 @@ async Task ActivateUpchannel(int streamId, IChannelRequest? channelRequest) channels[streamId] = new(upChannel, channelRequest); + try { - await foreach (var data in upChannel.ReadAllAsync()) + await foreach (var upData in upChannel.ReadAllAsync()) { - ReadOnlySequence upData = - await channels[streamId].Channel!.ReadAsync(channels[streamId].WindowSize, ReadBlockingMode.WaitAny, channel.Token); + //ReadOnlySequence upData = + // await channels[streamId].Channel!.ReadAsync(channels[streamId].WindowSize, ReadBlockingMode.WaitAny, channel.Token); _logger?.LogDebug("Read data from upchannel, stream-{0}, len={1}", streamId, upData.Length); for(int i = 0; i < upData.Length;) @@ -230,14 +227,13 @@ await WriteHeaderAsync(channel, new YamuxHeader { Flags = YamuxHeaderFlags.Rst, - Type = YamuxHeaderType.Data, + Type = YamuxHeaderType.WindowUpdate, StreamID = streamId }); _ = upChannel.CloseAsync(); channels.Remove(streamId); _logger?.LogDebug("Unexpected close, stream-{id}", streamId); - } upChannel.OnClose(() => @@ -249,7 +245,7 @@ await WriteHeaderAsync(channel, new YamuxHeader { Flags = YamuxHeaderFlags.Fin, - Type = YamuxHeaderType.Data, + Type = YamuxHeaderType.WindowUpdate, StreamID = streamId }); } @@ -257,8 +253,6 @@ await WriteHeaderAsync(channel, return Task.CompletedTask; }); } - - await WriteGoAwayAsync(channel, SessionTerminationCode.Ok); } catch (Exception ex) { diff --git a/src/samples/transport-interop/Program.cs b/src/samples/transport-interop/Program.cs index 2a497de6..c495e688 100644 --- a/src/samples/transport-interop/Program.cs +++ b/src/samples/transport-interop/Program.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MIT using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Nethermind.Libp2p.Core; using Nethermind.Libp2p.Protocols; using StackExchange.Redis; @@ -61,8 +62,17 @@ { if (ip == "0.0.0.0") { - IEnumerable addresses = NetworkInterface.GetAllNetworkInterfaces()! - .FirstOrDefault(i => i.Name == "eth0")! + var d = NetworkInterface.GetAllNetworkInterfaces()! + .Where(i => i.Name == "eth0" || + (i.OperationalStatus == OperationalStatus.Up && + i.NetworkInterfaceType == NetworkInterfaceType.Ethernet)).ToList(); + + IEnumerable addresses = NetworkInterface.GetAllNetworkInterfaces()! + .Where(i => i.Name == "eth0" || + (i.OperationalStatus == OperationalStatus.Up && + i.NetworkInterfaceType == NetworkInterfaceType.Ethernet && + i.GetIPProperties().GatewayAddresses.Any()) + ).First() .GetIPProperties() .UnicastAddresses .Where(a => a.Address.AddressFamily == AddressFamily.InterNetwork); @@ -99,6 +109,13 @@ class TestPlansPeerFactoryBuilder : PeerFactoryBuilderBase + builder.SetMinimumLevel(LogLevel.Trace) + .AddSimpleConsole(l => + { + l.SingleLine = true; + l.TimestampFormat = "[HH:mm:ss.FFF]"; + })) .AddScoped(_ => defaultPeerFactoryBuilder!) .BuildServiceProvider()) { From cf6d7ea03fd774425330c324cf90ea107943fbef Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Thu, 18 Apr 2024 10:25:21 +0300 Subject: [PATCH 03/14] Remake channels: add read/write results; disable cancellation temporary --- src/cs-multiaddress | 2 +- src/cs-multihash | 2 +- .../Libp2p.Core.Benchmarks/Benchmarks.cs | 2 +- src/libp2p/Libp2p.Core.Benchmarks/Program.cs | 4 +- .../Libp2p.Core.Tests/ChannelsBindingTests.cs | 19 - .../Libp2p.Core.Tests/ReaderWriterTests.cs | 38 +- .../DebugLoggerFactory.cs | 49 +++ .../Libp2p.Core.TestsBase/TestChannel.cs | 33 +- src/libp2p/Libp2p.Core.TestsBase/TestPeers.cs | 1 - src/libp2p/Libp2p.Core/Channel.cs | 325 +++++++++--------- src/libp2p/Libp2p.Core/ChannelFactory.cs | 73 +--- src/libp2p/Libp2p.Core/IChannel.cs | 5 +- src/libp2p/Libp2p.Core/IChannelFactory.cs | 10 +- src/libp2p/Libp2p.Core/IOResult.cs | 11 + src/libp2p/Libp2p.Core/IReader.cs | 46 +-- src/libp2p/Libp2p.Core/IWriter.cs | 14 +- src/libp2p/Libp2p.Core/LogMessages.cs | 12 +- src/libp2p/Libp2p.Core/PeerFactory.cs | 7 +- .../Libp2p.Core/PeerFactoryBuilderBase.cs | 3 +- src/libp2p/Libp2p.Core/ReadBlockingMode.cs | 10 + src/libp2p/Libp2p.Core/ReadResult.cs | 18 + src/libp2p/Libp2p.Core/Swarm.cs | 30 ++ .../Libp2p.Core/UnwarpResultExtensions.cs | 37 ++ src/libp2p/Libp2p.Core/VarInt.cs | 4 +- .../Libp2p.Protocols.IpTcp/IpTcpProtocol.cs | 52 ++- .../MultistreamProtocolTests.cs | 6 +- .../MultistreamProtocol.cs | 2 +- .../Libp2p.Protocols.Noise/NoiseProtocol.cs | 86 +++-- .../Libp2p.Protocols.Ping/LogMessages.cs | 1 - .../Libp2p.Protocols.Ping/PingProtocol.cs | 14 +- .../PlainTextProtocol.cs | 2 +- .../Libp2p.Protocols.Pubsub/PubsubRouter.cs | 1 - .../Libp2p.Protocols.Quic/LogMessages.cs | 1 - .../Libp2p.Protocols.Quic/QuicProtocol.cs | 38 +- .../YamuxProtocolTests.cs | 167 +++------ .../SessionTerminationCode.cs | 2 +- .../Libp2p.Protocols.Yamux/YamuxProtocol.cs | 43 ++- src/samples/chat/ChatProtocol.cs | 11 +- src/samples/chat/ConsoleReader.cs | 2 +- src/samples/perf-benchmarks/PerfProtocol.cs | 26 +- src/samples/transport-interop/Program.cs | 2 +- 41 files changed, 587 insertions(+), 624 deletions(-) delete mode 100644 src/libp2p/Libp2p.Core.Tests/ChannelsBindingTests.cs create mode 100644 src/libp2p/Libp2p.Core.TestsBase/DebugLoggerFactory.cs create mode 100644 src/libp2p/Libp2p.Core/IOResult.cs create mode 100644 src/libp2p/Libp2p.Core/ReadBlockingMode.cs create mode 100644 src/libp2p/Libp2p.Core/ReadResult.cs create mode 100644 src/libp2p/Libp2p.Core/Swarm.cs create mode 100644 src/libp2p/Libp2p.Core/UnwarpResultExtensions.cs diff --git a/src/cs-multiaddress b/src/cs-multiaddress index 3f66710e..86f97ef4 160000 --- a/src/cs-multiaddress +++ b/src/cs-multiaddress @@ -1 +1 @@ -Subproject commit 3f66710e730d1886c7d8f891947964fb182e4b39 +Subproject commit 86f97ef4f50a631862fe586fe741402d4884e64f diff --git a/src/cs-multihash b/src/cs-multihash index f01e83ee..be9f6e46 160000 --- a/src/cs-multihash +++ b/src/cs-multihash @@ -1 +1 @@ -Subproject commit f01e83eeea7c5f2c1ae9e9218b99f7feed48b3f0 +Subproject commit be9f6e4601bdfd145f1bbaa745dcb7aa892d6c1b diff --git a/src/libp2p/Libp2p.Core.Benchmarks/Benchmarks.cs b/src/libp2p/Libp2p.Core.Benchmarks/Benchmarks.cs index ee8108ce..56433be1 100644 --- a/src/libp2p/Libp2p.Core.Benchmarks/Benchmarks.cs +++ b/src/libp2p/Libp2p.Core.Benchmarks/Benchmarks.cs @@ -55,7 +55,7 @@ await Task.Run(async () => long i = 0; while (i < TotalSize) { - i += (await revChan.ReadAsync(0, ReadBlockingMode.WaitAny)).Length; + i += (await revChan.ReadAsync(0, ReadBlockingMode.WaitAny).OrThrow()).Length; } }); } diff --git a/src/libp2p/Libp2p.Core.Benchmarks/Program.cs b/src/libp2p/Libp2p.Core.Benchmarks/Program.cs index 766e08ae..22ceb117 100644 --- a/src/libp2p/Libp2p.Core.Benchmarks/Program.cs +++ b/src/libp2p/Libp2p.Core.Benchmarks/Program.cs @@ -8,7 +8,7 @@ using System.Diagnostics; Channel chan = new(); -IChannel revChan = ((Channel)chan).Reverse; +IChannel revChan = chan.Reverse; const long GiB = 1024 * 1024 * 1024; long PacketSize = 1 * 1024; @@ -42,7 +42,7 @@ await Task.Run(async () => { try { - d = (await revChan.ReadAsync(0, ReadBlockingMode.WaitAny)); + d = (await revChan.ReadAsync(0, ReadBlockingMode.WaitAny).OrThrow()); i += d.Length; } catch diff --git a/src/libp2p/Libp2p.Core.Tests/ChannelsBindingTests.cs b/src/libp2p/Libp2p.Core.Tests/ChannelsBindingTests.cs deleted file mode 100644 index b68ff5fe..00000000 --- a/src/libp2p/Libp2p.Core.Tests/ChannelsBindingTests.cs +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited -// SPDX-License-Identifier: MIT - -namespace Nethermind.Libp2p.Core.Tests; - -public class ChannelsBindingTests -{ - [Test] - public async Task Test_DownchannelClosesUpChannel_WhenBound() - { - Channel downChannel = new(); - Channel upChannel = new(); - Channel downChannelFromProtocolPov = (Channel)downChannel.Reverse; - downChannelFromProtocolPov.Bind(upChannel); - - await downChannel.CloseAsync(); - Assert.That(upChannel.IsClosed, Is.True); - } -} diff --git a/src/libp2p/Libp2p.Core.Tests/ReaderWriterTests.cs b/src/libp2p/Libp2p.Core.Tests/ReaderWriterTests.cs index aab1758a..6f870f5c 100644 --- a/src/libp2p/Libp2p.Core.Tests/ReaderWriterTests.cs +++ b/src/libp2p/Libp2p.Core.Tests/ReaderWriterTests.cs @@ -19,19 +19,19 @@ public async Task Test_ChannelWrites_WhenReadIsRequested() }); await Task.Delay(100); Assert.That(isWritten, Is.False); - ReadOnlySequence chunk1 = await readerWriter.ReadAsync(1); + ReadOnlySequence chunk1 = await readerWriter.ReadAsync(1).OrThrow(); Assert.Multiple(() => { Assert.That(chunk1.ToArray(), Is.EquivalentTo(new byte[] { 1 })); Assert.That(isWritten, Is.False); }); - ReadOnlySequence chunk2 = await readerWriter.ReadAsync(2); + ReadOnlySequence chunk2 = await readerWriter.ReadAsync(2).OrThrow(); Assert.Multiple(() => { Assert.That(chunk2.ToArray(), Is.EquivalentTo(new byte[] { 2, 3 })); Assert.That(isWritten, Is.False); }); - ReadOnlySequence chunk3 = await readerWriter.ReadAsync(1); + ReadOnlySequence chunk3 = await readerWriter.ReadAsync(1).OrThrow(); Assert.That(chunk3.ToArray(), Is.EquivalentTo(new byte[] { 4 })); await wrote; Assert.That(isWritten, Is.True); @@ -46,7 +46,7 @@ public async Task Test_ChannelReads_MultipleWrites() await readerWriter.WriteAsync(new ReadOnlySequence([1])); await readerWriter.WriteAsync(new ReadOnlySequence([2])); }); - ReadOnlySequence allTheData = await readerWriter.ReadAsync(2); + ReadOnlySequence allTheData = await readerWriter.ReadAsync(2).OrThrow(); Assert.That(allTheData.ToArray(), Is.EquivalentTo(new byte[] { 1, 2 })); } @@ -54,8 +54,8 @@ public async Task Test_ChannelReads_MultipleWrites() public async Task Test_ChannelReads_SequentialChunks() { Channel.ReaderWriter readerWriter = new(); - ValueTask> t1 = readerWriter.ReadAsync(2); - ValueTask> t2 = readerWriter.ReadAsync(2); + ValueTask> t1 = readerWriter.ReadAsync(2).OrThrow(); + ValueTask> t2 = readerWriter.ReadAsync(2).OrThrow(); await readerWriter.WriteAsync(new ReadOnlySequence([1])); await readerWriter.WriteAsync(new ReadOnlySequence([2])); await readerWriter.WriteAsync(new ReadOnlySequence([3])); @@ -71,7 +71,7 @@ public async Task Test_ChannelWrites_WhenReadIsRequested2() { Channel.ReaderWriter readerWriter = new(); _ = Task.Run(async () => await readerWriter.WriteAsync(new ReadOnlySequence([1, 2]))); - ReadOnlySequence res1 = await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAny); + ReadOnlySequence res1 = await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAny).OrThrow(); Assert.That(res1.ToArray().Length, Is.EqualTo(2)); } @@ -79,11 +79,11 @@ public async Task Test_ChannelWrites_WhenReadIsRequested2() public async Task Test_ChannelReadsNithing_WhenItIsDontWaitAndEmpty() { Channel.ReaderWriter readerWriter = new(); - ReadOnlySequence anyData = await readerWriter.ReadAsync(0, ReadBlockingMode.DontWait); + ReadOnlySequence anyData = await readerWriter.ReadAsync(0, ReadBlockingMode.DontWait).OrThrow(); Assert.That(anyData.ToArray(), Is.Empty); - anyData = await readerWriter.ReadAsync(1, ReadBlockingMode.DontWait); + anyData = await readerWriter.ReadAsync(1, ReadBlockingMode.DontWait).OrThrow(); Assert.That(anyData.ToArray(), Is.Empty); - anyData = await readerWriter.ReadAsync(10, ReadBlockingMode.DontWait); + anyData = await readerWriter.ReadAsync(10, ReadBlockingMode.DontWait).OrThrow(); Assert.That(anyData.ToArray(), Is.Empty); } @@ -91,7 +91,7 @@ public async Task Test_ChannelReadsNithing_WhenItIsDontWaitAndEmpty() public async Task Test_ChannelWrites_WhenReadIsRequested3() { Channel.ReaderWriter readerWriter = new(); - ReadOnlySequence res1 = await readerWriter.ReadAsync(3, ReadBlockingMode.DontWait); + ReadOnlySequence res1 = await readerWriter.ReadAsync(3, ReadBlockingMode.DontWait).OrThrow(); Assert.That(res1.ToArray().Length, Is.EqualTo(0)); } @@ -107,7 +107,7 @@ public async Task Test_ChannelWrites_Eof() }); Assert.That(await readerWriter.CanReadAsync(), Is.True); - ReadOnlySequence res1 = await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAll); + ReadOnlySequence res1 = await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAll).OrThrow(); Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.DontWait)); Assert.That(await readerWriter.CanReadAsync(), Is.False); @@ -129,7 +129,7 @@ public async Task Test_ChannelWrites_CannotWriteAfterEof(byte[] toWrite) await readerWriter.WriteEofAsync(); Assert.That(await readerWriter.CanReadAsync(), Is.False); - Assert.ThrowsAsync(async () => await readerWriter.WriteAsync(new ReadOnlySequence(toWrite))); + Assert.ThrowsAsync(async () => await readerWriter.WriteAsync(new ReadOnlySequence(toWrite)).OrThrow()); Assert.That(await readerWriter.CanReadAsync(), Is.False); } @@ -144,17 +144,17 @@ public async Task Test_ChannelWrites_CanReadAny() await readerWriter.WriteEofAsync(); }); - ReadOnlySequence res1 = await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAll); + ReadOnlySequence res1 = await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAll).OrThrow(); Assert.That(res1, Has.Length.EqualTo(3)); - Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.DontWait)); + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.DontWait).OrThrow()); Assert.That(await readerWriter.CanReadAsync(), Is.False); - Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAny)); + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAny).OrThrow()); Assert.That(await readerWriter.CanReadAsync(), Is.False); - Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAll)); + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAll).OrThrow()); Assert.That(await readerWriter.CanReadAsync(), Is.False); } @@ -169,7 +169,7 @@ public async Task Test_ChannelWrites_CannotReadAll_OnePacket() await readerWriter.WriteEofAsync(); }); - Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(5, ReadBlockingMode.WaitAll)); + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(5, ReadBlockingMode.WaitAll).OrThrow()); Assert.That(await readerWriter.CanReadAsync(), Is.False); } @@ -186,7 +186,7 @@ public async Task Test_ChannelWrites_CannotReadAll_Fragmented() await readerWriter.WriteEofAsync(); }); - Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(5, ReadBlockingMode.WaitAll)); + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(5, ReadBlockingMode.WaitAll).OrThrow()); Assert.That(await readerWriter.CanReadAsync(), Is.False); } } diff --git a/src/libp2p/Libp2p.Core.TestsBase/DebugLoggerFactory.cs b/src/libp2p/Libp2p.Core.TestsBase/DebugLoggerFactory.cs new file mode 100644 index 00000000..21de3e2b --- /dev/null +++ b/src/libp2p/Libp2p.Core.TestsBase/DebugLoggerFactory.cs @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: MIT + +using Microsoft.Extensions.Logging; +using NUnit.Framework; + +namespace Nethermind.Libp2p.Core.TestsBase; + +public class DebugLoggerFactory : ILoggerFactory +{ + class DebugLogger(string categoryName) : ILogger, IDisposable + { + private readonly string _categoryName = categoryName; + + public IDisposable? BeginScope(TState state) where TState : notnull + { + return this; + } + + public void Dispose() + { + } + + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + TestContext.Out.WriteLine($"{logLevel} {_categoryName}:{eventId}: {(exception is null ? state?.ToString() : formatter(state, exception))}"); + } + } + + public void AddProvider(ILoggerProvider provider) + { + + } + + public ILogger CreateLogger(string categoryName) + { + return new DebugLogger(categoryName); + } + + public void Dispose() + { + + } +} diff --git a/src/libp2p/Libp2p.Core.TestsBase/TestChannel.cs b/src/libp2p/Libp2p.Core.TestsBase/TestChannel.cs index 366a8485..24affca7 100644 --- a/src/libp2p/Libp2p.Core.TestsBase/TestChannel.cs +++ b/src/libp2p/Libp2p.Core.TestsBase/TestChannel.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: MIT +using Newtonsoft.Json.Linq; using System.Buffers; using System.Runtime.CompilerServices; @@ -15,19 +16,6 @@ public TestChannel() _channel = new Channel(); } - public bool IsClosed => _channel.IsClosed; - public CancellationToken Token => _channel.Token; - - public Task CloseAsync() - { - return _channel.CloseAsync(); - } - - public void OnClose(Func action) - { - _channel.OnClose(action); - } - public TaskAwaiter GetAwaiter() { return _channel.GetAwaiter(); @@ -38,29 +26,24 @@ public IChannel Reverse() return _channel.Reverse; } - public ValueTask> ReadAsync(int length, ReadBlockingMode blockingMode = ReadBlockingMode.WaitAll, + public ValueTask ReadAsync(int length, ReadBlockingMode blockingMode = ReadBlockingMode.WaitAll, CancellationToken token = default) { return _channel.ReadAsync(length, blockingMode, token); } - public ValueTask WriteAsync(ReadOnlySequence bytes) - { - return _channel.WriteAsync(bytes); - } - - public ValueTask WriteEofAsync() + public ValueTask WriteAsync(ReadOnlySequence bytes, CancellationToken token = default) { - return _channel.WriteEofAsync(); + return _channel.WriteAsync(bytes, token); } - public ValueTask CanReadAsync(CancellationToken token = default) + public ValueTask WriteEofAsync(CancellationToken token = default) { - return _channel.CanReadAsync(token); + return _channel.WriteEofAsync(token); } - public Task CloseAsync(bool graceful = true) + public ValueTask CloseAsync() { - return Task.CompletedTask; + return _channel.CloseAsync(); } } diff --git a/src/libp2p/Libp2p.Core.TestsBase/TestPeers.cs b/src/libp2p/Libp2p.Core.TestsBase/TestPeers.cs index 5dc8ab94..f4341e10 100644 --- a/src/libp2p/Libp2p.Core.TestsBase/TestPeers.cs +++ b/src/libp2p/Libp2p.Core.TestsBase/TestPeers.cs @@ -3,7 +3,6 @@ using Multiformats.Address; using Multiformats.Address.Protocols; -using Nethermind.Libp2p.Core.Dto; using System.Buffers.Binary; using System.Collections.Concurrent; diff --git a/src/libp2p/Libp2p.Core/Channel.cs b/src/libp2p/Libp2p.Core/Channel.cs index 059b30a8..b0ac0d81 100644 --- a/src/libp2p/Libp2p.Core/Channel.cs +++ b/src/libp2p/Libp2p.Core/Channel.cs @@ -3,7 +3,6 @@ using System.Buffers; using System.Runtime.CompilerServices; -using Microsoft.Extensions.Logging; [assembly: InternalsVisibleTo("Nethermind.Libp2p.Core.TestsBase")] [assembly: InternalsVisibleTo("Nethermind.Libp2p.Core.Tests")] @@ -14,101 +13,75 @@ namespace Nethermind.Libp2p.Core; internal class Channel : IChannel { private IChannel? _reversedChannel; - private ILogger? _logger; - - public Channel(ILoggerFactory? loggerFactory = null) - { - _logger = loggerFactory?.CreateLogger(); - Id = "unknown"; - Reader = new ReaderWriter(_logger); - Writer = new ReaderWriter(_logger); - } + private ReaderWriter _reader; + private ReaderWriter _writer; + private TaskCompletionSource Completion = new(); public Channel() { - Id = "unknown"; - Reader = new ReaderWriter(); - Writer = new ReaderWriter(); + _reader = new ReaderWriter(this); + _writer = new ReaderWriter(this); } - private Channel(IReader reader, IWriter writer) + private Channel(ReaderWriter reader, ReaderWriter writer) { - Id = "unknown"; - Reader = reader; - Writer = writer; + _reader = reader; + _writer = writer; } - private CancellationTokenSource State { get; init; } = new(); - public IChannel Reverse { - get + get => _reversedChannel ??= new Channel((ReaderWriter)Writer, (ReaderWriter)Reader) { - if (_reversedChannel is not null) - { - return _reversedChannel; - } - - Channel x = new((ReaderWriter)Writer, (ReaderWriter)Reader) - { - _logger = _logger, - _reversedChannel = this, - State = State, - Id = Id + "-rev" - }; - return _reversedChannel = x; - } + _reversedChannel = this, + Completion = Completion + }; } - public string Id { get; set; } - public IReader Reader { get; private set; } - public IWriter Writer { get; private set; } + public IReader Reader { get => _reader; } + public IWriter Writer { get => _writer; } - public bool IsClosed => State.IsCancellationRequested; - public CancellationToken Token => State.Token; - - public async Task CloseAsync() + public ValueTask ReadAsync(int length, ReadBlockingMode blockingMode = ReadBlockingMode.WaitAll, + CancellationToken token = default) { - await Writer.WriteEofAsync(); - - State.Cancel(); + return Reader.ReadAsync(length, blockingMode, token); } - public void OnClose(Func action) + public ValueTask WriteAsync(ReadOnlySequence bytes, CancellationToken token = default) { - State.Token.Register(() => action().Wait()); + return Writer.WriteAsync(bytes, token); } - public TaskAwaiter GetAwaiter() + public ValueTask WriteEofAsync(CancellationToken token = default) => Writer.WriteEofAsync(token); + + public TaskAwaiter GetAwaiter() => Completion.Task.GetAwaiter(); + + public async ValueTask CloseAsync() { - return Task.Delay(-1, State.Token).ContinueWith(_ => { }, TaskContinuationOptions.OnlyOnCanceled).GetAwaiter(); + ValueTask stopReader = _reader.WriteEofAsync(); + await _writer.WriteEofAsync(); + if (!stopReader.IsCompleted) + { + await stopReader; + } + Completion.TrySetResult(); } - public void Bind(IChannel parent) + private void TryComplete() { - Reader = (ReaderWriter)((Channel)parent).Writer; - Writer = (ReaderWriter)((Channel)parent).Reader; - Channel parentChannel = (Channel)parent; - OnClose(() => - { - parentChannel.State.Cancel(); - return Task.CompletedTask; - }); - parentChannel.OnClose(() => + if (_reader._eow && _writer._eow) { - State.Cancel(); - return Task.CompletedTask; - }); + Completion.TrySetResult(); + } } + internal class ReaderWriter : IReader, IWriter { - private readonly ILogger? _logger; - - public ReaderWriter(ILogger? logger) + internal protected ReaderWriter(Channel tryComplete) { - _logger = logger; + _externalCompletionMonitor = tryComplete; } public ReaderWriter() @@ -120,144 +93,160 @@ public ReaderWriter() private readonly SemaphoreSlim _read = new(0, 1); private readonly SemaphoreSlim _canRead = new(0, 1); private readonly SemaphoreSlim _readLock = new(1, 1); - private bool _eof = false; + private readonly Channel? _externalCompletionMonitor; + internal bool _eow = false; - public async ValueTask> ReadAsync(int length, + public async ValueTask ReadAsync(int length, ReadBlockingMode blockingMode = ReadBlockingMode.WaitAll, CancellationToken token = default) { - await _readLock.WaitAsync(token); - - if (_eof) + try { - _readLock.Release(); - throw new Exception("Can't read after EOF"); - } + await _readLock.WaitAsync(token); - if (blockingMode == ReadBlockingMode.DontWait && _bytes.Length == 0) - { - _readLock.Release(); - return new ReadOnlySequence(); - } - - await _canRead.WaitAsync(token); - - if (_eof) - { - _canRead.Release(); - _readLock.Release(); - _read.Release(); - _canWrite.Release(); - throw new Exception("Can't read after EOF"); - } + if (_eow) + { + _readLock.Release(); + return ReadResult.Ended; + } - bool lockAgain = false; - long bytesToRead = length != 0 - ? (blockingMode == ReadBlockingMode.WaitAll ? length : Math.Min(length, _bytes.Length)) - : _bytes.Length; + if (blockingMode == ReadBlockingMode.DontWait && _bytes.Length == 0) + { + _readLock.Release(); + return ReadResult.Empty; + } - ReadOnlySequence chunk = default; - do - { - if (lockAgain) await _canRead.WaitAsync(token); + await _canRead.WaitAsync(token); - if (_eof) + if (_eow) { _canRead.Release(); _readLock.Release(); _read.Release(); - throw new Exception("Can't read after EOF"); + return ReadResult.Ended; } - ReadOnlySequence anotherChunk = default; + bool lockAgain = false; + long bytesToRead = length != 0 + ? (blockingMode == ReadBlockingMode.WaitAll ? length : Math.Min(length, _bytes.Length)) + : _bytes.Length; - if (_bytes.Length <= bytesToRead) + ReadOnlySequence chunk = default; + do + { + if (lockAgain) await _canRead.WaitAsync(token); + + if (_eow) + { + _canRead.Release(); + _readLock.Release(); + _read.Release(); + return ReadResult.Ended; + } + + ReadOnlySequence anotherChunk = default; + + if (_bytes.Length <= bytesToRead) + { + anotherChunk = _bytes; + bytesToRead -= _bytes.Length; + _bytes = default; + _read.Release(); + _canWrite.Release(); + } + else if (_bytes.Length > bytesToRead) + { + anotherChunk = _bytes.Slice(0, bytesToRead); + _bytes = _bytes.Slice(bytesToRead, _bytes.End); + bytesToRead = 0; + _canRead.Release(); + } + + chunk = chunk.Length == 0 ? anotherChunk : chunk.Append(anotherChunk.First); + lockAgain = true; + } while (bytesToRead != 0); + + _readLock.Release(); + return ReadResult.Ok(chunk); + } + catch (TaskCanceledException) + { + return ReadResult.Cancelled; + } + } + + public async ValueTask WriteAsync(ReadOnlySequence bytes, CancellationToken token = default) + { + try + { + await _canWrite.WaitAsync(token); + + if (_eow) { - anotherChunk = _bytes; - bytesToRead -= _bytes.Length; - _logger?.ReadChunk(_bytes.Length); - _bytes = default; - _read.Release(); _canWrite.Release(); + return IOResult.Ended; } - else if (_bytes.Length > bytesToRead) + + if (_bytes.Length != 0) { - anotherChunk = _bytes.Slice(0, bytesToRead); - _bytes = _bytes.Slice(bytesToRead, _bytes.End); - _logger?.ReadEnough(_bytes.Length); - bytesToRead = 0; - _canRead.Release(); + return IOResult.InternalError; } - chunk = chunk.Length == 0 ? anotherChunk : chunk.Append(anotherChunk.First); - lockAgain = true; - } while (bytesToRead != 0); + if (bytes.Length == 0) + { + _canWrite.Release(); + return IOResult.Ok; + } - _readLock.Release(); - return chunk; + _bytes = bytes; + _canRead.Release(); + await _read.WaitAsync(token); + return IOResult.Ok; + } + catch (TaskCanceledException) + { + return IOResult.Cancelled; + } } - public async ValueTask WriteAsync(ReadOnlySequence bytes) + public async ValueTask WriteEofAsync(CancellationToken token = default) { - await _canWrite.WaitAsync(); - - if (_eof) + try { - _canWrite.Release(); - throw new Exception("Can't write after EOF"); - } + await _canWrite.WaitAsync(token); - if (_bytes.Length != 0) - { - throw new Exception("Channel is not properly locked"); + if (_eow) + { + _canWrite.Release(); + return IOResult.Ended; + } + _eow = true; + _externalCompletionMonitor?.TryComplete(); + _canRead.Release(); + _canWrite.Release(); + return IOResult.Ok; } - - _logger?.WriteBytes(bytes.Length); - - if (bytes.Length == 0) + catch (TaskCanceledException) { - _canWrite.Release(); - return; + return IOResult.Cancelled; } - - _bytes = bytes; - _canRead.Release(); - await _read.WaitAsync(); - } - - public async ValueTask WriteEofAsync() - { - await _canWrite.WaitAsync(); - _eof = true; - _canRead.Release(); - _canWrite.Release(); } - public async ValueTask CanReadAsync(CancellationToken token = default) + public async ValueTask CanReadAsync(CancellationToken token = default) { - if (_eof) + try + { + if (_eow) + { + return IOResult.Ended; + } + await _readLock.WaitAsync(token); + _readLock.Release(); + return !_eow ? IOResult.Ok : IOResult.Ended; + } + catch (TaskCanceledException) { - return false; + return IOResult.Cancelled; } - await _readLock.WaitAsync(token); - _readLock.Release(); - return !_eof; } } - - public ValueTask> ReadAsync(int length, ReadBlockingMode blockingMode = ReadBlockingMode.WaitAll, - CancellationToken token = default) - { - return Reader.ReadAsync(length, blockingMode, token); - } - - public ValueTask WriteAsync(ReadOnlySequence bytes) - { - return Writer.WriteAsync(bytes); - } - - public ValueTask WriteEofAsync() => Writer.WriteEofAsync(); - - public ValueTask CanReadAsync(CancellationToken token = default) => Reader.CanReadAsync(token); - - public Task CloseAsync(bool graceful = true) => WriteEofAsync().AsTask(); } diff --git a/src/libp2p/Libp2p.Core/ChannelFactory.cs b/src/libp2p/Libp2p.Core/ChannelFactory.cs index cb8053c2..20bf449e 100644 --- a/src/libp2p/Libp2p.Core/ChannelFactory.cs +++ b/src/libp2p/Libp2p.Core/ChannelFactory.cs @@ -10,14 +10,15 @@ namespace Nethermind.Libp2p.Core; public class ChannelFactory : IChannelFactory { private readonly IServiceProvider _serviceProvider; - private IProtocol _parent; + private readonly ILoggerFactory? _loggerFactory; private IDictionary _factories; private readonly ILogger? _logger; public ChannelFactory(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; - _logger = _serviceProvider.GetService()?.CreateLogger(); + _loggerFactory = _serviceProvider.GetService(); + _logger = _loggerFactory?.CreateLogger(); } public IEnumerable SubProtocols => _factories.Keys; @@ -25,22 +26,18 @@ public ChannelFactory(IServiceProvider serviceProvider) public IChannel SubDial(IPeerContext context, IChannelRequest? req = null) { IProtocol? subProtocol = req?.SubProtocol ?? SubProtocols.FirstOrDefault(); - Channel channel = CreateChannel(subProtocol); + Channel channel = new(); ChannelFactory? channelFactory = _factories[subProtocol] as ChannelFactory; - _logger?.DialStarted(channel.Id, subProtocol.Id, channelFactory.GetSubProtocols()); _ = subProtocol.DialAsync(channel.Reverse, channelFactory, context) .ContinueWith(async task => { if (!task.IsCompletedSuccessfully) { - _logger?.DialFailed(channel.Id, subProtocol.Id, task.Exception, task.Exception.GetErrorMessage()); - } - if (!channel.IsClosed) - { - await channel.CloseAsync(task.Exception is null); + _logger?.DialFailed(subProtocol.Id, task.Exception, task.Exception.GetErrorMessage()); } + await channel.CloseAsync(); req?.CompletionSource?.SetResult(); }); @@ -51,22 +48,18 @@ public IChannel SubDial(IPeerContext context, IChannelRequest? req = null) public IChannel SubListen(IPeerContext context, IChannelRequest? req = null) { IProtocol? subProtocol = req?.SubProtocol ?? SubProtocols.FirstOrDefault(); - Channel channel = CreateChannel(subProtocol); + Channel channel = new(); ChannelFactory? channelFactory = _factories[subProtocol] as ChannelFactory; - _logger?.ListenStarted(channel.Id, subProtocol.Id, channelFactory.GetSubProtocols()); _ = subProtocol.ListenAsync(channel.Reverse, channelFactory, context) .ContinueWith(async task => { if (!task.IsCompletedSuccessfully) { - _logger?.ListenFailed(channel.Id, subProtocol.Id, task.Exception, task.Exception.GetErrorMessage()); - } - if (!channel.IsClosed) - { - await channel.CloseAsync(); + _logger?.ListenFailed(subProtocol.Id, task.Exception, task.Exception.GetErrorMessage()); } + await channel.CloseAsync(); req?.CompletionSource?.SetResult(); }); @@ -74,74 +67,42 @@ public IChannel SubListen(IPeerContext context, IChannelRequest? req = null) return channel; } - public IChannel SubDialAndBind(IChannel parent, IPeerContext context, + public Task SubDialAndBind(IChannel parent, IPeerContext context, IChannelRequest? req = null) { IProtocol? subProtocol = req?.SubProtocol ?? SubProtocols.FirstOrDefault(); - Channel channel = CreateChannel(subProtocol); ChannelFactory? channelFactory = _factories[subProtocol] as ChannelFactory; - _logger?.DialAndBindStarted(channel.Id, subProtocol.Id, channelFactory.GetSubProtocols()); - - channel.Bind(parent); - _ = subProtocol.DialAsync(channel.Reverse, channelFactory, context) + return subProtocol.DialAsync(((Channel)parent).Reverse, channelFactory, context) .ContinueWith(async task => { if (!task.IsCompletedSuccessfully) { - _logger?.DialAndBindFailed(channel.Id, subProtocol.Id, task.Exception, task.Exception.GetErrorMessage()); - } - if (!channel.IsClosed) - { - await channel.CloseAsync(); + _logger?.DialAndBindFailed(subProtocol.Id, task.Exception, task.Exception.GetErrorMessage()); } + await parent.CloseAsync(); req?.CompletionSource?.SetResult(); }); - - return channel; } - public IChannel SubListenAndBind(IChannel parent, IPeerContext context, + public Task SubListenAndBind(IChannel parent, IPeerContext context, IChannelRequest? req = null) { IProtocol? subProtocol = req?.SubProtocol ?? SubProtocols.FirstOrDefault(); - Channel channel = CreateChannel(subProtocol); ChannelFactory? channelFactory = _factories[subProtocol] as ChannelFactory; - _logger?.ListenAndBindStarted(channel.Id, subProtocol.Id, channelFactory.GetSubProtocols()); - - channel.Bind(parent); - _ = subProtocol.ListenAsync(channel.Reverse, channelFactory, context) + return subProtocol.ListenAsync(((Channel)parent).Reverse, channelFactory, context) .ContinueWith(async task => { - if (!task.IsCompletedSuccessfully) - { - _logger?.ListenAndBindFailed(channel.Id, subProtocol.Id, task.Exception, task.Exception.GetErrorMessage()); - } - if (!channel.IsClosed) - { - await channel.CloseAsync(); - } - + await parent.CloseAsync(); req?.CompletionSource?.SetResult(); }); - - return channel; } - public ChannelFactory Setup(IProtocol parent, IDictionary factories) + public ChannelFactory Setup(IDictionary factories) { - _parent = parent; _factories = factories; return this; } - - private Channel CreateChannel(IProtocol? subProtocol) - { - Channel channel = ActivatorUtilities.CreateInstance(_serviceProvider); - channel.Id = $"{_parent.Id} <> {subProtocol?.Id}"; - _logger?.ChannelCreated(channel.Id); - return channel; - } } diff --git a/src/libp2p/Libp2p.Core/IChannel.cs b/src/libp2p/Libp2p.Core/IChannel.cs index ee03bc06..4970dadc 100644 --- a/src/libp2p/Libp2p.Core/IChannel.cs +++ b/src/libp2p/Libp2p.Core/IChannel.cs @@ -7,9 +7,6 @@ namespace Nethermind.Libp2p.Core; public interface IChannel : IReader, IWriter { - bool IsClosed { get; } - CancellationToken Token { get; } - Task CloseAsync(bool graceful = true); - void OnClose(Func action); + ValueTask CloseAsync(); TaskAwaiter GetAwaiter(); } diff --git a/src/libp2p/Libp2p.Core/IChannelFactory.cs b/src/libp2p/Libp2p.Core/IChannelFactory.cs index 22a36808..d8ff230e 100644 --- a/src/libp2p/Libp2p.Core/IChannelFactory.cs +++ b/src/libp2p/Libp2p.Core/IChannelFactory.cs @@ -10,9 +10,11 @@ public interface IChannelFactory IChannel SubListen(IPeerContext context, IChannelRequest? request = null); - IChannel SubDialAndBind(IChannel parentChannel, IPeerContext context, IChannelRequest? request = null); + Task SubDialAndBind(IChannel parentChannel, IPeerContext context, IChannelRequest? request = null); + + Task SubListenAndBind(IChannel parentChannel, IPeerContext context, IChannelRequest? request = null); + - IChannel SubListenAndBind(IChannel parentChannel, IPeerContext context, IChannelRequest? request = null); IChannel SubDial(IPeerContext context, IProtocol protocol) { @@ -24,12 +26,12 @@ IChannel SubListen(IPeerContext context, IProtocol protocol) return SubListen(context, new ChannelRequest { SubProtocol = protocol }); } - IChannel SubDialAndBind(IChannel parentChannel, IPeerContext context, IProtocol protocol) + Task SubDialAndBind(IChannel parentChannel, IPeerContext context, IProtocol protocol) { return SubDialAndBind(parentChannel, context, new ChannelRequest { SubProtocol = protocol }); } - IChannel SubListenAndBind(IChannel parentChannel, IPeerContext context, IProtocol protocol) + Task SubListenAndBind(IChannel parentChannel, IPeerContext context, IProtocol protocol) { return SubListenAndBind(parentChannel, context, new ChannelRequest { SubProtocol = protocol }); } diff --git a/src/libp2p/Libp2p.Core/IOResult.cs b/src/libp2p/Libp2p.Core/IOResult.cs new file mode 100644 index 00000000..63879a73 --- /dev/null +++ b/src/libp2p/Libp2p.Core/IOResult.cs @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: MIT + +namespace Nethermind.Libp2p.Core; +public enum IOResult +{ + Ok, + Ended, + Cancelled, + InternalError, +} diff --git a/src/libp2p/Libp2p.Core/IReader.cs b/src/libp2p/Libp2p.Core/IReader.cs index aab59f20..87458b81 100644 --- a/src/libp2p/Libp2p.Core/IReader.cs +++ b/src/libp2p/Libp2p.Core/IReader.cs @@ -10,21 +10,30 @@ namespace Nethermind.Libp2p.Core; public interface IReader { - /// - /// Waits for any data or EOF marker, returns false if EOF was reached, true without reading data otherwise. - /// - /// - ValueTask CanReadAsync(CancellationToken token = default); - - ValueTask> ReadAsync(int length, ReadBlockingMode blockingMode = ReadBlockingMode.WaitAll, - CancellationToken token = default); + ValueTask ReadAsync(int length, ReadBlockingMode blockingMode = ReadBlockingMode.WaitAll, CancellationToken token = default); #region Read helpers + async IAsyncEnumerable> ReadAllAsync( + [EnumeratorCancellation] CancellationToken token = default) + { + for (; ; ) + { + token.ThrowIfCancellationRequested(); + + switch (await ReadAsync(0, ReadBlockingMode.WaitAny, token)) + { + case { Result: IOResult.Ok, Data: ReadOnlySequence data }: yield return data; break; + case { Result: IOResult.Ended }: yield break; + default: throw new Exception(); + } + } + } + async Task ReadLineAsync() { int size = await ReadVarintAsync(); - return Encoding.UTF8.GetString((await ReadAsync(size)).ToArray()).TrimEnd('\n'); + return Encoding.UTF8.GetString((await ReadAsync(size).OrThrow()).ToArray()).TrimEnd('\n'); } Task ReadVarintAsync(CancellationToken token = default) @@ -40,26 +49,9 @@ Task ReadVarintUlongAsync() async ValueTask ReadPrefixedProtobufAsync(MessageParser parser, CancellationToken token = default) where T : IMessage { int messageLength = await ReadVarintAsync(token); - ReadOnlySequence serializedMessage = await ReadAsync(messageLength, token: token); + ReadOnlySequence serializedMessage = await ReadAsync(messageLength, token: token).OrThrow(); return parser.ParseFrom(serializedMessage); } - - async IAsyncEnumerable> ReadAllAsync( - [EnumeratorCancellation] CancellationToken token = default) - { - while (!token.IsCancellationRequested && await CanReadAsync()) - { - yield return await ReadAsync(0, ReadBlockingMode.WaitAny, token); - } - } - #endregion } - -public enum ReadBlockingMode -{ - WaitAll, - WaitAny, - DontWait -} diff --git a/src/libp2p/Libp2p.Core/IWriter.cs b/src/libp2p/Libp2p.Core/IWriter.cs index ffda19bc..47f22416 100644 --- a/src/libp2p/Libp2p.Core/IWriter.cs +++ b/src/libp2p/Libp2p.Core/IWriter.cs @@ -9,7 +9,7 @@ namespace Nethermind.Libp2p.Core; public interface IWriter { - ValueTask WriteLineAsync(string str, bool prependedWithSize = true) + ValueTask WriteLineAsync(string str, bool prependedWithSize = true) { int len = Encoding.UTF8.GetByteCount(str) + 1; byte[] buf = new byte[VarInt.GetSizeInBytes(len) + len]; @@ -20,7 +20,7 @@ ValueTask WriteLineAsync(string str, bool prependedWithSize = true) return WriteAsync(new ReadOnlySequence(buf)); } - ValueTask WriteVarintAsync(int val) + ValueTask WriteVarintAsync(int val) { byte[] buf = new byte[VarInt.GetSizeInBytes(val)]; int offset = 0; @@ -28,7 +28,7 @@ ValueTask WriteVarintAsync(int val) return WriteAsync(new ReadOnlySequence(buf)); } - ValueTask WriteVarintAsync(ulong val) + ValueTask WriteVarintAsync(ulong val) { byte[] buf = new byte[VarInt.GetSizeInBytes(val)]; int offset = 0; @@ -36,7 +36,7 @@ ValueTask WriteVarintAsync(ulong val) return WriteAsync(new ReadOnlySequence(buf)); } - ValueTask WriteSizeAndDataAsync(byte[] data) + ValueTask WriteSizeAndDataAsync(byte[] data) { byte[] buf = new byte[VarInt.GetSizeInBytes(data.Length) + data.Length]; int offset = 0; @@ -52,7 +52,7 @@ async ValueTask WritePrefixedProtobufAsync(T grpcMessage) where T : IMessage< await WriteVarintAsync(serializedMessage.Length); await WriteAsync(new ReadOnlySequence(serializedMessage)); } - - ValueTask WriteAsync(ReadOnlySequence bytes); - ValueTask WriteEofAsync(); + ValueTask WriteAsync(ReadOnlySequence bytes, CancellationToken token = default); + ValueTask WriteEofAsync(CancellationToken token = default); } + diff --git a/src/libp2p/Libp2p.Core/LogMessages.cs b/src/libp2p/Libp2p.Core/LogMessages.cs index a20153aa..895381e8 100644 --- a/src/libp2p/Libp2p.Core/LogMessages.cs +++ b/src/libp2p/Libp2p.Core/LogMessages.cs @@ -92,12 +92,11 @@ internal static partial void ChannelCreated( [LoggerMessage( EventId = EventId + 9, EventName = nameof(DialFailed), - Message = "Dial error {protocol} via {channel}: {errorMessage}", + Message = "Dial error {protocol}: {errorMessage}", Level = LogLevel.Error, SkipEnabledCheck = true)] internal static partial void DialFailed( this ILogger logger, - string channel, string protocol, Exception? exception, string errorMessage); @@ -105,12 +104,11 @@ internal static partial void DialFailed( [LoggerMessage( EventId = EventId + 10, EventName = nameof(ListenFailed), - Message = "Listen error {protocol} via {channel}: {errorMessage}", + Message = "Listen error {protocol}: {errorMessage}", Level = LogLevel.Error, SkipEnabledCheck = true)] internal static partial void ListenFailed( this ILogger logger, - string channel, string protocol, Exception? exception, string errorMessage); @@ -118,12 +116,11 @@ internal static partial void ListenFailed( [LoggerMessage( EventId = EventId + 11, EventName = nameof(DialAndBindFailed), - Message = "Dial and bind error {protocol} via {channel}: {errorMessage}", + Message = "Dial and bind error {protocol}: {errorMessage}", Level = LogLevel.Error, SkipEnabledCheck = true)] internal static partial void DialAndBindFailed( this ILogger logger, - string channel, string protocol, Exception? exception, string errorMessage); @@ -131,12 +128,11 @@ internal static partial void DialAndBindFailed( [LoggerMessage( EventId = EventId + 12, EventName = nameof(ListenAndBindFailed), - Message = "Listen and bind error {protocol} via {channel}: {errorMessage}", + Message = "Listen and bind error {protocol}: {errorMessage}", Level = LogLevel.Error, SkipEnabledCheck = true)] internal static partial void ListenAndBindFailed( this ILogger logger, - string channel, string protocol, Exception? exception, string errorMessage); diff --git a/src/libp2p/Libp2p.Core/PeerFactory.cs b/src/libp2p/Libp2p.Core/PeerFactory.cs index 7a9635dc..1360e8a1 100644 --- a/src/libp2p/Libp2p.Core/PeerFactory.cs +++ b/src/libp2p/Libp2p.Core/PeerFactory.cs @@ -156,13 +156,12 @@ public PeerListener(Channel chan, LocalPeer localPeer) public Task DisconnectAsync() { - return _chan.CloseAsync(); + return _chan.CloseAsync().AsTask(); } public TaskAwaiter GetAwaiter() { - return Task.Delay(-1, _chan.Token).ContinueWith(_ => { }, TaskContinuationOptions.OnlyOnCanceled) - .GetAwaiter(); + return _chan.GetAwaiter(); } internal void RaiseOnConnection(IRemotePeer peer) @@ -219,7 +218,7 @@ public Task DialAsync(CancellationToken token = default) where TProto public Task DisconnectAsync() { - return Channel.CloseAsync(); + return Channel.CloseAsync().AsTask(); } public IPeer Fork() diff --git a/src/libp2p/Libp2p.Core/PeerFactoryBuilderBase.cs b/src/libp2p/Libp2p.Core/PeerFactoryBuilderBase.cs index dd5e1786..5df4a01c 100644 --- a/src/libp2p/Libp2p.Core/PeerFactoryBuilderBase.cs +++ b/src/libp2p/Libp2p.Core/PeerFactoryBuilderBase.cs @@ -150,8 +150,7 @@ public IPeerFactory Build() static void SetupChannelFactories(ProtocolStack root) { - root.UpChannelsFactory.Setup(root.Protocol, - new Dictionary(root.TopProtocols + root.UpChannelsFactory.Setup(new Dictionary(root.TopProtocols .Select(p => new KeyValuePair(p.Protocol, p.UpChannelsFactory)))); foreach (ProtocolStack topProto in root.TopProtocols) { diff --git a/src/libp2p/Libp2p.Core/ReadBlockingMode.cs b/src/libp2p/Libp2p.Core/ReadBlockingMode.cs new file mode 100644 index 00000000..9f435246 --- /dev/null +++ b/src/libp2p/Libp2p.Core/ReadBlockingMode.cs @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: MIT + +namespace Nethermind.Libp2p.Core; +public enum ReadBlockingMode +{ + WaitAll, + WaitAny, + DontWait +} diff --git a/src/libp2p/Libp2p.Core/ReadResult.cs b/src/libp2p/Libp2p.Core/ReadResult.cs new file mode 100644 index 00000000..94e1251e --- /dev/null +++ b/src/libp2p/Libp2p.Core/ReadResult.cs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: MIT + +using System.Buffers; + +namespace Nethermind.Libp2p.Core; +public readonly struct ReadResult +{ + public static ReadResult Ended = new() { Result = IOResult.Ended }; + public static ReadResult Cancelled = new() { Result = IOResult.Cancelled }; + + public static ReadResult Empty = new() { Result = IOResult.Ok, Data = new ReadOnlySequence() }; + public IOResult Result { get; init; } + public ReadOnlySequence Data { get; init; } + + internal static ReadResult Ok(ReadOnlySequence data) => new() { Result = IOResult.Ok, Data = data }; +} + diff --git a/src/libp2p/Libp2p.Core/Swarm.cs b/src/libp2p/Libp2p.Core/Swarm.cs new file mode 100644 index 00000000..1e1877c8 --- /dev/null +++ b/src/libp2p/Libp2p.Core/Swarm.cs @@ -0,0 +1,30 @@ +/// +/// Summary description for Class1 +/// +//public class PeerStore +//{ +// class Peer +// { +// public Pubkey PubKey { get; set; } +// public List Addresses { get; set; } +// } + +// public Dictionary Peers { get; set; } = new(); + +//} + +//public class Swarm +//{ +// Task DialAsync(PeerId peerId) where TProtocol : IProtocol; +// Task DialAsync(MultiAddress addr) where TProtocol : IProtocol; + + +// Task DialAsync(PeerId peerId) where TProtocol : IProtocol; +// Task DialAsync(MultiAddress addr) where TProtocol : IProtocol; + +// Task DialAsync(MultiAddress addr, params MultiAddress[] altAddresses) where TProtocol : IProtocol; + +// Task ListenAsync(PeerId peerId); +// IAsyncEnumerable<> ListAsync(CancellationToken token) where TProtocol : where TProtocol : IProtocol; +//} + diff --git a/src/libp2p/Libp2p.Core/UnwarpResultExtensions.cs b/src/libp2p/Libp2p.Core/UnwarpResultExtensions.cs new file mode 100644 index 00000000..10b86642 --- /dev/null +++ b/src/libp2p/Libp2p.Core/UnwarpResultExtensions.cs @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: MIT + +using System.Buffers; + +namespace Nethermind.Libp2p.Core; +public static class UnwarpResultExtensions +{ + public static async ValueTask OrThrow(this ValueTask self) + { + if (self.IsCompleted && self.Result != IOResult.Ok) + { + throw new Exception(); + } + var result = await self.AsTask(); + if (result != IOResult.Ok) + { + throw new Exception($"Unable to read, error: {result}"); + } + } + public static async ValueTask> OrThrow(this ValueTask self) + { + if (self.IsCompleted && self.Result.Result != IOResult.Ok) + { + throw new Exception(); + } + var result = await self.AsTask(); + if (result.Result != IOResult.Ok) + { + throw new Exception($"Unable to read, error: {result}"); + } + else + { + return result.Data; + } + } +} diff --git a/src/libp2p/Libp2p.Core/VarInt.cs b/src/libp2p/Libp2p.Core/VarInt.cs index 84faa884..4413d0e1 100644 --- a/src/libp2p/Libp2p.Core/VarInt.cs +++ b/src/libp2p/Libp2p.Core/VarInt.cs @@ -102,7 +102,7 @@ public static async Task DecodeUlong(IReader buf) byte mul = 0; for (int i = 0; i < 9; i++) { - byte @byte = (await buf.ReadAsync(1)).FirstSpan[0]; + byte @byte = (await buf.ReadAsync(1).OrThrow()).FirstSpan[0]; res += ((ulong)@byte & 127) << mul; mul += 7; if ((@byte & 128) == 0) @@ -120,7 +120,7 @@ public static async Task Decode(IReader buf, CancellationToken token = defa byte mul = 0; for (int i = 0; i < 9; i++) { - byte @byte = (await buf.ReadAsync(1, token: token)).FirstSpan[0]; + byte @byte = (await buf.ReadAsync(1, token: token).OrThrow()).FirstSpan[0]; res += (@byte & 127) << mul; mul += 7; if ((@byte & 128) == 0) diff --git a/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs b/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs index b2fb72f8..31f660fe 100644 --- a/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs @@ -12,18 +12,13 @@ namespace Nethermind.Libp2p.Protocols; // TODO: Rewrite with SocketAsyncEventArgs -public class IpTcpProtocol : IProtocol +public class IpTcpProtocol(ILoggerFactory? loggerFactory = null) : IProtocol { - private readonly ILogger? _logger; - - public IpTcpProtocol(ILoggerFactory? loggerFactory = null) - { - _logger = loggerFactory?.CreateLogger(); - } + private readonly ILogger? _logger = loggerFactory?.CreateLogger(); public string Id => "ip-tcp"; - public async Task ListenAsync(IChannel channel, IChannelFactory? channelFactory, IPeerContext context) + public async Task ListenAsync(IChannel __, IChannelFactory? channelFactory, IPeerContext context) { Multiaddress addr = context.LocalPeer.Address; bool isIP4 = addr.Has(); @@ -36,11 +31,7 @@ public async Task ListenAsync(IChannel channel, IChannelFactory? channelFactory, srv.Listen(tcpPort); IPEndPoint localIpEndpoint = (IPEndPoint)srv.LocalEndPoint!; - channel.OnClose(() => - { - srv.Close(); - return Task.CompletedTask; - }); + Multiaddress localMultiaddress = new(); localMultiaddress = isIP4 ? localMultiaddress.Add(localIpEndpoint.Address.MapToIPv4()) : localMultiaddress.Add(localIpEndpoint.Address.MapToIPv6()); @@ -58,7 +49,7 @@ public async Task ListenAsync(IChannel channel, IChannelFactory? channelFactory, await Task.Run(async () => { - while (!channel.IsClosed) + for (; ; ) { Socket client = await srv.AcceptAsync(); IPeerContext clientContext = context.Fork(); @@ -76,7 +67,7 @@ await Task.Run(async () => { try { - while (!chan.IsClosed) + for (; ; ) { if (client.Available == 0) { @@ -87,16 +78,19 @@ await Task.Run(async () => int len = await client.ReceiveAsync(buf, SocketFlags.None); if (len != 0) { - await chan.WriteAsync(new ReadOnlySequence(buf.AsMemory()[..len])); + if (await chan.WriteAsync(new ReadOnlySequence(buf.AsMemory()[..len])) != IOResult.Ok) + { + break; + } } } } catch (SocketException e) { - - await chan.CloseAsync(false); + + await chan.CloseAsync(); } - }, chan.Token); + }); _ = Task.Run(async () => { try @@ -109,16 +103,16 @@ await Task.Run(async () => catch (SocketException) { _logger?.LogInformation($"Disconnected({context.Id}) due to a socket exception"); - await chan.CloseAsync(false); + await chan.CloseAsync(); } - }, chan.Token); + }); } }); } - public async Task DialAsync(IChannel channel, IChannelFactory? channelFactory, IPeerContext context) + public async Task DialAsync(IChannel _, IChannelFactory? channelFactory, IPeerContext context) { - if(channelFactory is null) + if (channelFactory is null) { throw new ProtocolViolationException(); } @@ -133,7 +127,7 @@ public async Task DialAsync(IChannel channel, IChannelFactory? channelFactory, I int tcpPort = addr.Get().Port; try { - await client.ConnectAsync(new IPEndPoint(ipAddress, tcpPort), channel.Token); + await client.ConnectAsync(new IPEndPoint(ipAddress, tcpPort)); } catch (SocketException e) { @@ -162,7 +156,6 @@ public async Task DialAsync(IChannel channel, IChannelFactory? channelFactory, I context.LocalPeer.Address = context.LocalEndpoint.Add(context.LocalPeer.Identity.PeerId.ToString()); IChannel upChannel = channelFactory.SubDial(context); - channel.GetAwaiter().OnCompleted(() => upChannel.CloseAsync()); //upChannel.OnClosing += (graceful) => upChannel.CloseAsync(graceful); @@ -171,13 +164,16 @@ public async Task DialAsync(IChannel channel, IChannelFactory? channelFactory, I byte[] buf = new byte[client.ReceiveBufferSize]; try { - while (!upChannel.IsClosed) + for (; ; ) { int len = await client.ReceiveAsync(buf, SocketFlags.None); if (len != 0) { _logger?.LogDebug("Receive {0} data, len={1}", context.Id, len); - await upChannel.WriteAsync(new ReadOnlySequence(buf[..len])); + if ((await upChannel.WriteAsync(new ReadOnlySequence(buf[..len]))) != IOResult.Ok) + { + break; + }; } } @@ -203,7 +199,7 @@ public async Task DialAsync(IChannel channel, IChannelFactory? channelFactory, I } catch (SocketException) { - await upChannel.CloseAsync(false); + await upChannel.CloseAsync(); waitForStop.SetCanceled(); } }); diff --git a/src/libp2p/Libp2p.Protocols.Multistream.Tests/MultistreamProtocolTests.cs b/src/libp2p/Libp2p.Protocols.Multistream.Tests/MultistreamProtocolTests.cs index 9a83824d..4951124f 100644 --- a/src/libp2p/Libp2p.Protocols.Multistream.Tests/MultistreamProtocolTests.cs +++ b/src/libp2p/Libp2p.Protocols.Multistream.Tests/MultistreamProtocolTests.cs @@ -21,7 +21,7 @@ public async Task Test_ConnectionEstablished_AfterHandshake() channelFactory.SubProtocols.Returns(new[] { proto1 }); IChannel upChannel = new TestChannel(); channelFactory.SubDialAndBind(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(upChannel); + .Returns(Task.CompletedTask); MultistreamProtocol proto = new(); _ = proto.DialAsync(downChannelFromProtocolPov, channelFactory, peerContext); @@ -53,7 +53,7 @@ public async Task Test_ConnectionEstablished_AfterHandshake_With_SpecificRequest IChannel upChannel = new TestChannel(); channelFactory.SubDialAndBind(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(upChannel); + .Returns(Task.CompletedTask); MultistreamProtocol proto = new(); _ = proto.DialAsync(downChannelFromProtocolPov, channelFactory, peerContext); @@ -114,7 +114,7 @@ public async Task Test_ConnectionEstablished_ForAnyOfProtocols() channelFactory.SubProtocols.Returns(new[] { proto1, proto2 }); IChannel upChannel = new TestChannel(); channelFactory.SubDialAndBind(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(upChannel); + .Returns(Task.CompletedTask); MultistreamProtocol proto = new(); _ = proto.DialAsync(downChannelFromProtocolPov, channelFactory, peerContext); diff --git a/src/libp2p/Libp2p.Protocols.Multistream/MultistreamProtocol.cs b/src/libp2p/Libp2p.Protocols.Multistream/MultistreamProtocol.cs index 65b8c5b5..44acdb15 100644 --- a/src/libp2p/Libp2p.Protocols.Multistream/MultistreamProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Multistream/MultistreamProtocol.cs @@ -93,7 +93,7 @@ public async Task ListenAsync(IChannel channel, IChannelFactory channelFactory, } IProtocol? selected = null; - while (!channel.IsClosed) + for (; ; ) { string proto = await channel.ReadLineAsync(); selected = channelFactory.SubProtocols.FirstOrDefault(x => x.Id == proto); diff --git a/src/libp2p/Libp2p.Protocols.Noise/NoiseProtocol.cs b/src/libp2p/Libp2p.Protocols.Noise/NoiseProtocol.cs index f9b07906..3f1b46ce 100644 --- a/src/libp2p/Libp2p.Protocols.Noise/NoiseProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Noise/NoiseProtocol.cs @@ -13,6 +13,7 @@ using Nethermind.Libp2p.Protocols.Noise.Dto; using PublicKey = Nethermind.Libp2p.Core.Dto.PublicKey; using Org.BouncyCastle.Utilities.Encoders; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace Nethermind.Libp2p.Protocols; @@ -50,9 +51,10 @@ public async Task DialAsync(IChannel downChannel, IChannelFactory upChannelFacto await downChannel.WriteAsync(new ReadOnlySequence(lenBytes)); await downChannel.WriteAsync(new ReadOnlySequence(buffer, 0, msg0.BytesWritten)); - lenBytes = (await downChannel.ReadAsync(2)).ToArray(); - int len = (int)BinaryPrimitives.ReadInt16BigEndian(lenBytes.AsSpan()); - ReadOnlySequence received = await downChannel.ReadAsync(len); + lenBytes = (await downChannel.ReadAsync(2).OrThrow()).ToArray(); + + int len = BinaryPrimitives.ReadInt16BigEndian(lenBytes.AsSpan()); + ReadOnlySequence received = await downChannel.ReadAsync(len).OrThrow(); (int BytesRead, byte[] HandshakeHash, Transport Transport) msg1 = handshakeState.ReadMessage(received.ToArray(), buffer); NoiseHandshakePayload? msg1Decoded = NoiseHandshakePayload.Parser.ParseFrom(buffer.AsSpan(0, msg1.BytesRead)); @@ -91,46 +93,42 @@ public async Task DialAsync(IChannel downChannel, IChannelFactory upChannelFacto Transport? transport = msg2.Transport; IChannel upChannel = upChannelFactory.SubDial(context); - downChannel.OnClose(() => upChannel.CloseAsync()); - // UP -> DOWN + + Task t = Task.Run(async () => { - while (!downChannel.IsClosed && !upChannel.IsClosed) + for (; ; ) { - ReadOnlySequence request = - await upChannel.ReadAsync(Protocol.MaxMessageLength - 16, ReadBlockingMode.WaitAny); - byte[] buffer = new byte[2 + 16 + request.Length]; + ReadResult dataReadResult = await upChannel.ReadAsync(Protocol.MaxMessageLength - 16, ReadBlockingMode.WaitAny); + if (dataReadResult.Result != IOResult.Ok) + { + return; + } - int bytesWritten = transport.WriteMessage(request.ToArray(), buffer.AsSpan(2)); - BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(), (ushort)bytesWritten); - - string str = Encoding.UTF8.GetString(request.ToArray()); - _logger?.LogTrace($"> {buffer.Length}(payload {request.Length})"); + byte[] buffer = new byte[2 + 16 + dataReadResult.Data.Length]; + int bytesWritten = transport.WriteMessage(dataReadResult.Data.ToArray(), buffer.AsSpan(2)); + BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(), (ushort)bytesWritten); await downChannel.WriteAsync(new ReadOnlySequence(buffer)); } }); // DOWN -> UP Task t2 = Task.Run(async () => { - while (!downChannel.IsClosed && !upChannel.IsClosed) + for (; ; ) { - lenBytes = (await downChannel.ReadAsync(2)).ToArray(); - int len = (int)BinaryPrimitives.ReadUInt16BigEndian(lenBytes.AsSpan()); - ReadOnlySequence request = - await downChannel.ReadAsync(len); - byte[] buffer = new byte[len - 16]; + byte[] lengthBytes = (await downChannel.ReadAsync(2, ReadBlockingMode.WaitAll).OrThrow()).ToArray(); + int length = BinaryPrimitives.ReadUInt16BigEndian(lengthBytes.AsSpan()); - _logger?.LogTrace("start READ"); + ReadOnlySequence request = await downChannel.ReadAsync(length).OrThrow(); + byte[] buffer = new byte[len - 16]; int bytesRead = transport.ReadMessage(request.ToArray(), buffer); - _logger?.LogTrace("READ"); - _logger?.LogTrace($"< {len + 2}/(payload {bytesRead}) {Hex.ToHexString(buffer)} {Encoding.UTF8.GetString(buffer).ReplaceLineEndings()}"); await upChannel.WriteAsync(new ReadOnlySequence(buffer, 0, bytesRead)); } }); - await Task.WhenAll(t, t2); + await Task.WhenAny(t, t2); } public async Task ListenAsync(IChannel downChannel, IChannelFactory upChannelFactory, IPeerContext context) @@ -140,10 +138,10 @@ public async Task ListenAsync(IChannel downChannel, IChannelFactory upChannelFac _protocol.Create(false, s: serverStatic.PrivateKey); - byte[]? lenBytes = (await downChannel.ReadAsync(2)).ToArray(); + byte[]? lenBytes = (await downChannel.ReadAsync(2).OrThrow()).ToArray(); short len = BinaryPrimitives.ReadInt16BigEndian(lenBytes); byte[] buffer = new byte[Protocol.MaxMessageLength]; - ReadOnlySequence msg0Bytes = await downChannel.ReadAsync(len); + ReadOnlySequence msg0Bytes = await downChannel.ReadAsync(len).OrThrow(); handshakeState.ReadMessage(msg0Bytes.ToArray(), buffer); byte[] msg = Encoding.UTF8.GetBytes(PayloadSigPrefix) @@ -169,9 +167,9 @@ public async Task ListenAsync(IChannel downChannel, IChannelFactory upChannelFac BinaryPrimitives.WriteInt16BigEndian(buffer.AsSpan(), (short)msg1.BytesWritten); await downChannel.WriteAsync(new ReadOnlySequence(buffer, 0, msg1.BytesWritten + 2)); - lenBytes = (await downChannel.ReadAsync(2)).ToArray(); + lenBytes = (await downChannel.ReadAsync(2).OrThrow()).ToArray(); len = BinaryPrimitives.ReadInt16BigEndian(lenBytes); - ReadOnlySequence hs2Bytes = await downChannel.ReadAsync(len); + ReadOnlySequence hs2Bytes = await downChannel.ReadAsync(len).OrThrow(); (int BytesRead, byte[] HandshakeHash, Transport Transport) msg2 = handshakeState.ReadMessage(hs2Bytes.ToArray(), buffer); NoiseHandshakePayload? msg2Decoded = NoiseHandshakePayload.Parser.ParseFrom(buffer.AsSpan(0, msg2.BytesRead)); @@ -186,40 +184,40 @@ public async Task ListenAsync(IChannel downChannel, IChannelFactory upChannelFac } IChannel upChannel = upChannelFactory.SubListen(context); - // UP -> DOWN + Task t = Task.Run(async () => { - while (!downChannel.IsClosed && !upChannel.IsClosed) + for (; ; ) { - ReadOnlySequence request = - await upChannel.ReadAsync(Protocol.MaxMessageLength - 16, ReadBlockingMode.WaitAny); - byte[] buffer = new byte[2 + 16 + request.Length]; + ReadResult dataReadResult = await upChannel.ReadAsync(Protocol.MaxMessageLength - 16, ReadBlockingMode.WaitAny); + if (dataReadResult.Result != IOResult.Ok) + { + return; + } - int bytesWritten = transport.WriteMessage(request.ToArray(), buffer.AsSpan(2)); - BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(), (ushort)bytesWritten); + byte[] buffer = new byte[2 + 16 + dataReadResult.Data.Length]; - _logger?.LogTrace($"> {request.Length}"); + int bytesWritten = transport.WriteMessage(dataReadResult.Data.ToArray(), buffer.AsSpan(2)); + BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(), (ushort)bytesWritten); await downChannel.WriteAsync(new ReadOnlySequence(buffer)); } }); - // DOWN -> UP Task t2 = Task.Run(async () => { - while (!downChannel.IsClosed && !upChannel.IsClosed) + for (; ; ) { - lenBytes = (await downChannel.ReadAsync(2)).ToArray(); - int len = BinaryPrimitives.ReadUInt16BigEndian(lenBytes.AsSpan()); - ReadOnlySequence request = - await downChannel.ReadAsync(len); + byte[] lengthBytes = (await downChannel.ReadAsync(2, ReadBlockingMode.WaitAll).OrThrow()).ToArray(); + int length = BinaryPrimitives.ReadUInt16BigEndian(lengthBytes.AsSpan()); + + ReadOnlySequence request = await downChannel.ReadAsync(length).OrThrow(); byte[] buffer = new byte[len - 16]; int bytesRead = transport.ReadMessage(request.ToArray(), buffer); - _logger?.LogTrace($"< {bytesRead}"); await upChannel.WriteAsync(new ReadOnlySequence(buffer, 0, bytesRead)); } }); - await Task.WhenAll(t, t2); + await Task.WhenAny(t, t2); } } diff --git a/src/libp2p/Libp2p.Protocols.Ping/LogMessages.cs b/src/libp2p/Libp2p.Protocols.Ping/LogMessages.cs index 0f93028e..34bc4ba0 100644 --- a/src/libp2p/Libp2p.Protocols.Ping/LogMessages.cs +++ b/src/libp2p/Libp2p.Protocols.Ping/LogMessages.cs @@ -3,7 +3,6 @@ using Microsoft.Extensions.Logging; using Multiformats.Address; -using Nethermind.Libp2p.Core; namespace Nethermind.Libp2p.Protocols.Ping; diff --git a/src/libp2p/Libp2p.Protocols.Ping/PingProtocol.cs b/src/libp2p/Libp2p.Protocols.Ping/PingProtocol.cs index 7d5e1927..9f50c0e9 100644 --- a/src/libp2p/Libp2p.Protocols.Ping/PingProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Ping/PingProtocol.cs @@ -35,7 +35,7 @@ public async Task DialAsync(IChannel channel, IChannelFactory? channelFactory, await channel.WriteAsync(bytes); _logger?.ReadingPong(context.RemotePeer.Address); - ReadOnlySequence response = await channel.ReadAsync(PayloadLength, ReadBlockingMode.WaitAll); + ReadOnlySequence response = await channel.ReadAsync(PayloadLength, ReadBlockingMode.WaitAll).OrThrow(); _logger?.VerifyingPong(context.RemotePeer.Address); if (!byteArray[0..PayloadLength].SequenceEqual(response.ToArray())) @@ -52,15 +52,17 @@ public async Task ListenAsync(IChannel channel, IChannelFactory? channelFactory, { _logger?.PingListenStarted(context.RemotePeer.Address); - while (await channel.CanReadAsync()) + while (true) { _logger?.ReadingPing(context.RemotePeer.Address); - ReadOnlySequence request = await channel.ReadAsync(PayloadLength, ReadBlockingMode.WaitAll); - byte[] byteArray = request.ToArray(); - ReadOnlySequence bytes = new(byteArray); + ReadResult read = await channel.ReadAsync(PayloadLength, ReadBlockingMode.WaitAll); + if (read.Result != IOResult.Ok) + { + break; + } _logger?.ReturningPong(context.RemotePeer.Address); - await channel.WriteAsync(bytes); + await channel.WriteAsync(read.Data); } _logger?.PingFinished(context.RemotePeer.Address); diff --git a/src/libp2p/Libp2p.Protocols.Plaintext/PlainTextProtocol.cs b/src/libp2p/Libp2p.Protocols.Plaintext/PlainTextProtocol.cs index 8e0c7f58..6dc12e34 100644 --- a/src/libp2p/Libp2p.Protocols.Plaintext/PlainTextProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Plaintext/PlainTextProtocol.cs @@ -32,7 +32,7 @@ protected override async Task ConnectAsync(IChannel channel, IChannelFactory? ch await channel.WriteAsync(new ReadOnlySequence(sizeBuf.Concat(buf).ToArray())); int structSize = await channel.ReadVarintAsync(); - buf = (await channel.ReadAsync(structSize)).ToArray(); + buf = (await channel.ReadAsync(structSize).OrThrow()).ToArray(); Exchange? dest = Exchange.Parser.ParseFrom(buf); await (isListener diff --git a/src/libp2p/Libp2p.Protocols.Pubsub/PubsubRouter.cs b/src/libp2p/Libp2p.Protocols.Pubsub/PubsubRouter.cs index 18bf4ee5..0746f8e0 100644 --- a/src/libp2p/Libp2p.Protocols.Pubsub/PubsubRouter.cs +++ b/src/libp2p/Libp2p.Protocols.Pubsub/PubsubRouter.cs @@ -3,7 +3,6 @@ using Google.Protobuf; using Microsoft.Extensions.Logging; -using Multiformats.Address; using Multiformats.Address.Protocols; using Nethermind.Libp2p.Core; using Nethermind.Libp2p.Core.Discovery; diff --git a/src/libp2p/Libp2p.Protocols.Quic/LogMessages.cs b/src/libp2p/Libp2p.Protocols.Quic/LogMessages.cs index f1349766..1f13efa1 100644 --- a/src/libp2p/Libp2p.Protocols.Quic/LogMessages.cs +++ b/src/libp2p/Libp2p.Protocols.Quic/LogMessages.cs @@ -3,7 +3,6 @@ using System.Net; using Microsoft.Extensions.Logging; -using Nethermind.Libp2p.Core; namespace Nethermind.Libp2p.Protocols.Quic; diff --git a/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs b/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs index e002d4a9..05c00af3 100644 --- a/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs @@ -97,18 +97,14 @@ public async Task ListenAsync(IChannel channel, IChannelFactory? channelFactory, .ReplaceOrAdd(listener.LocalEndPoint.Port); } - channel.OnClose(async () => - { - await listener.DisposeAsync(); - }); _logger?.ReadyToHandleConnections(); context.ListenerReady(); - while (!channel.IsClosed) + for (; ; ) { - QuicConnection connection = await listener.AcceptConnectionAsync(channel.Token); - _ = ProcessStreams(connection, context.Fork(), channelFactory, channel.Token); + QuicConnection connection = await listener.AcceptConnectionAsync(); + _ = ProcessStreams(connection, context.Fork(), channelFactory); } } @@ -159,21 +155,17 @@ public async Task DialAsync(IChannel channel, IChannelFactory? channelFactory, I QuicConnection connection = await QuicConnection.ConnectAsync(clientConnectionOptions); - channel.OnClose(async () => - { - await connection.CloseAsync(0); - await connection.DisposeAsync(); - }); + _logger?.Connected(connection.LocalEndPoint, connection.RemoteEndPoint); - await ProcessStreams(connection, context, channelFactory, channel.Token); + await ProcessStreams(connection, context, channelFactory); } private static bool VerifyRemoteCertificate(IPeer? remotePeer, X509Certificate certificate) => CertificateHelper.ValidateCertificate(certificate as X509Certificate2, remotePeer?.Address.Get().ToString()); - private async Task ProcessStreams(QuicConnection connection, IPeerContext context, IChannelFactory channelFactory, CancellationToken token) + private async Task ProcessStreams(QuicConnection connection, IPeerContext context, IChannelFactory channelFactory, CancellationToken token = default) { bool isIP4 = connection.LocalEndPoint.AddressFamily == AddressFamily.InterNetwork; @@ -220,36 +212,30 @@ private async Task ProcessStreams(QuicConnection connection, IPeerContext contex private void ExchangeData(QuicStream stream, IChannel upChannel, TaskCompletionSource? tcs) { - upChannel.OnClose(async () => - { - tcs?.SetResult(); - stream.Close(); - }); - _ = Task.Run(async () => { try { await foreach (ReadOnlySequence data in upChannel.ReadAllAsync()) { - await stream.WriteAsync(data.ToArray(), upChannel.Token); + await stream.WriteAsync(data.ToArray()); } } catch (SocketException ex) { _logger?.SocketException(ex, ex.Message); - await upChannel.CloseAsync(false); + await upChannel.CloseAsync(); } - }, upChannel.Token); + }); _ = Task.Run(async () => { try { - while (!upChannel.IsClosed) + while (stream.CanRead) { byte[] buf = new byte[1024]; - int len = await stream.ReadAtLeastAsync(buf, 1, false, upChannel.Token); + int len = await stream.ReadAtLeastAsync(buf, 1, false); if (len != 0) { await upChannel.WriteAsync(new ReadOnlySequence(buf.AsMemory()[..len])); @@ -259,7 +245,7 @@ private void ExchangeData(QuicStream stream, IChannel upChannel, TaskCompletionS catch (SocketException ex) { _logger?.SocketException(ex, ex.Message); - await upChannel.CloseAsync(false); + await upChannel.CloseAsync(); } }); } diff --git a/src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs b/src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs index fed43741..087951d4 100644 --- a/src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs +++ b/src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs @@ -6,11 +6,8 @@ using Nethermind.Libp2p.Core.TestsBase; using NSubstitute; using NUnit.Framework.Internal; -using Org.BouncyCastle.Crypto.Agreement.Srp; using System.Buffers; using System.Collections.Concurrent; -using System.Diagnostics; -using System.Threading.Channels; namespace Nethermind.Libp2p.Protocols.Noise.Tests; @@ -24,60 +21,57 @@ public class YamuxProtocolTests // Exchange data // Expect error and react to it - //[Test] - //public async Task Test_Connection_Ackowledged() - //{ - // IChannel downChannel = new TestChannel(); - // IChannel downChannelFromProtocolPov = ((TestChannel)downChannel).Reverse(); - // IChannelFactory channelFactory = Substitute.For(); - // IPeerContext peerContext = Substitute.For(); - - // IProtocol? proto1 = Substitute.For(); - // proto1.Id.Returns("proto1"); - // channelFactory.SubProtocols.Returns(new[] { proto1 }); - // IChannel upChannel = new TestChannel(); - // channelFactory.SubDialAndBind(Arg.Any(), Arg.Any(), Arg.Any()) - // .Returns(upChannel); - - // YamuxProtocol proto = new(); - // _ = proto.DialAsync(downChannelFromProtocolPov, channelFactory, peerContext); - // await downChannel.WriteLineAsync(proto.Id); - // await downChannel.WriteLineAsync("proto1"); - - // Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo(proto.Id)); - // Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo("proto1")); - // channelFactory.Received().SubDialAndBind(downChannelFromProtocolPov, peerContext, proto1); - // await downChannel.CloseAsync(); - //} - - //[Test] - //public async Task Test_ConnectionClosed_ForBrokenHandshake() - //{ - // // IChannel downChannel = new TestChannel(); - // // IChannel downChannelFromProtocolPov = ((TestChannel)downChannel).Reverse(); - // // IChannelFactory channelFactory = Substitute.For(); - // // IPeerContext peerContext = Substitute.For(); - // // - // // IProtocol? proto1 = Substitute.For(); - // // proto1.Id.Returns("proto1"); - // // channelFactory.SubProtocols.Returns(new[] { proto1 }); - // // IChannel upChannel = new TestChannel(); - // // channelFactory.SubDialAndBind(Arg.Any(), Arg.Any(), Arg.Any()) - // // .Returns(upChannel); - // // - // // NoiseProtocol proto = new(); - // // _ = proto.DialAsync(downChannelFromProtocolPov, channelFactory, peerContext); - // // await downChannel.WriteLineAsync(proto.Id); - // // await downChannel.WriteLineAsync("proto2"); - // // - // // Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo(proto.Id)); - // // Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo("proto1")); - // // channelFactory.DidNotReceive().SubDialAndBind(downChannelFromProtocolPov, peerContext, proto1); - // // await upChannel.CloseAsync(); - //} - [Test] public async Task Test_Connection_Ackowledged() + { + IChannel downChannel = new TestChannel(); + IChannel downChannelFromProtocolPov = ((TestChannel)downChannel).Reverse(); + IChannelFactory channelFactory = Substitute.For(); + IPeerContext peerContext = Substitute.For(); + + IProtocol? proto1 = Substitute.For(); + proto1.Id.Returns("proto1"); + channelFactory.SubProtocols.Returns(new[] { proto1 }); + channelFactory.SubDialAndBind(Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(Task.CompletedTask); + + YamuxProtocol proto = new(); + _ = proto.DialAsync(downChannelFromProtocolPov, channelFactory, peerContext); + await downChannel.WriteLineAsync(proto.Id); + await downChannel.WriteLineAsync("proto1"); + + Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo(proto.Id)); + Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo("proto1")); + channelFactory.Received().SubDialAndBind(downChannelFromProtocolPov, peerContext, proto1); + await downChannel.CloseAsync(); + } + + [Test] + public async Task Test_ConnectionClosed_ForBrokenHandshake() + { + IChannel downChannel = new TestChannel(); + IChannel downChannelFromProtocolPov = ((TestChannel)downChannel).Reverse(); + IChannelFactory channelFactory = Substitute.For(); + IPeerContext peerContext = Substitute.For(); + + IProtocol? proto1 = Substitute.For(); + proto1.Id.Returns("proto1"); + channelFactory.SubProtocols.Returns(new[] { proto1 }); + channelFactory.SubDialAndBind(Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(Task.CompletedTask); + + NoiseProtocol proto = new(); + _ = proto.DialAsync(downChannelFromProtocolPov, channelFactory, peerContext); + await downChannel.WriteLineAsync(proto.Id); + await downChannel.WriteLineAsync("proto2"); + + Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo(proto.Id)); + Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo("proto1")); + channelFactory.DidNotReceive().SubDialAndBind(downChannelFromProtocolPov, peerContext, proto1); + } + + [Test] + public async Task Test_Protocol_Communication() { IProtocol? proto1 = Substitute.For(); proto1.Id.Returns("proto1"); @@ -113,23 +107,11 @@ public async Task Test_Connection_Ackowledged() var res = await listenerUpChannel.Reverse().ReadLineAsync(); - //await listenerUpChannel.CloseAsync(); + await listenerUpChannel.CloseAsync(); Assert.That(res, Is.EqualTo("hello")); await Task.Delay(1000); - //IChannel listenerUpChannel = new TestChannel(); - - - // - //_ = proto.DialAsync(downChannelFromProtocolPov, channelFactory, peerContext); - //await downChannel.WriteLineAsync(proto.Id); - //await downChannel.WriteLineAsync("proto1"); - - //Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo(proto.Id)); - //Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo("proto1")); - //channelFactory.Received().SubDialAndBind(downChannelFromProtocolPov, peerContext, proto1); - //await downChannel.CloseAsync(); } @@ -149,56 +131,9 @@ public async Task DialAsync(IChannel downChannel, IChannelFactory? upChannelFact public async Task ListenAsync(IChannel downChannel, IChannelFactory? upChannelFactory, IPeerContext context) { string line = await downChannel.ReadLineAsync(); - ReadOnlySequence? readAfter = await downChannel.ReadAsync(0, ReadBlockingMode.WaitAny); + ReadOnlySequence? readAfter = await downChannel.ReadAsync(0, ReadBlockingMode.WaitAny).OrThrow(); Assert.That(readAfter, Is.Null); await downChannel.WriteLineAsync(line); } } } - -internal class DebugLoggerFactory : Microsoft.Extensions.Logging.ILoggerFactory -{ - class DebugLogger : Microsoft.Extensions.Logging.ILogger, IDisposable - { - private string categoryName; - - public DebugLogger(string categoryName) - { - this.categoryName = categoryName; - } - - public IDisposable? BeginScope(TState state) where TState : notnull - { - return this; - } - - public void Dispose() - { - } - - public bool IsEnabled(LogLevel logLevel) - { - return true; - } - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) - { - TestContext.Out.WriteLine($"{logLevel} {categoryName}:{eventId}: {(exception is null ? state?.ToString() : formatter(state, exception))}"); - } - } - - public void AddProvider(ILoggerProvider provider) - { - - } - - public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) - { - return new DebugLogger(categoryName); - } - - public void Dispose() - { - - } -} diff --git a/src/libp2p/Libp2p.Protocols.Yamux/SessionTerminationCode.cs b/src/libp2p/Libp2p.Protocols.Yamux/SessionTerminationCode.cs index 90d69333..80556f38 100644 --- a/src/libp2p/Libp2p.Protocols.Yamux/SessionTerminationCode.cs +++ b/src/libp2p/Libp2p.Protocols.Yamux/SessionTerminationCode.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: MIT namespace Nethermind.Libp2p.Protocols; diff --git a/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs b/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs index 862c8ff3..26013085 100644 --- a/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs @@ -63,7 +63,7 @@ await WriteHeaderAsync(channel, } }); - while (await channel.CanReadAsync()) + for (; ; ) { YamuxHeader header = await ReadHeaderAsync(channel); ReadOnlySequence data = default; @@ -88,7 +88,7 @@ await WriteHeaderAsync(channel, foreach (ChannelState channelState in channels.Values) { - if(channelState.Channel is not null) + if (channelState.Channel is not null) { await channelState.Channel.WriteEofAsync(); } @@ -119,7 +119,7 @@ await WriteHeaderAsync(channel, await WriteGoAwayAsync(channel, SessionTerminationCode.ProtocolError); return; } - data = new ReadOnlySequence((await channel.ReadAsync(header.Length)).ToArray()); + data = new ReadOnlySequence((await channel.ReadAsync(header.Length).OrThrow()).ToArray()); _logger?.LogDebug("Recv data, stream-{0}, len={1}, data: {data}", header.StreamID, data.Length, Encoding.ASCII.GetString(data.ToArray().Select(c => c == 0x1b || c == 0x07 ? (byte)0x2e : c).ToArray())); @@ -198,7 +198,7 @@ async Task ActivateUpchannel(int streamId, IChannelRequest? channelRequest) // await channels[streamId].Channel!.ReadAsync(channels[streamId].WindowSize, ReadBlockingMode.WaitAny, channel.Token); _logger?.LogDebug("Read data from upchannel, stream-{0}, len={1}", streamId, upData.Length); - for(int i = 0; i < upData.Length;) + for (int i = 0; i < upData.Length;) { int sendingSize = Math.Min((int)upData.Length - i, channels[streamId].WindowSize); await WriteHeaderAsync(channel, @@ -222,7 +222,8 @@ await WriteHeaderAsync(channel, channels[streamId] = channels[streamId] with { State = channels[streamId].State | State.UpClosed }; _logger?.LogDebug("Close, stream-{id}", streamId); } - catch { + catch + { await WriteHeaderAsync(channel, new YamuxHeader { @@ -236,22 +237,20 @@ await WriteHeaderAsync(channel, _logger?.LogDebug("Unexpected close, stream-{id}", streamId); } - upChannel.OnClose(() => + await upChannel; + + channels.Remove(streamId); + if ((channels[streamId].State | State.UpClosed) == 0) { - channels.Remove(streamId); - if ((channels[streamId].State | State.UpClosed) == 0) - { - _ = WriteHeaderAsync(channel, - new YamuxHeader - { - Flags = YamuxHeaderFlags.Fin, - Type = YamuxHeaderType.WindowUpdate, - StreamID = streamId - }); - } - _logger?.LogDebug("Close, stream-{id}", streamId); - return Task.CompletedTask; - }); + _ = WriteHeaderAsync(channel, + new YamuxHeader + { + Flags = YamuxHeaderFlags.Fin, + Type = YamuxHeaderType.WindowUpdate, + StreamID = streamId + }); + } + _logger?.LogDebug("Close, stream-{id}", streamId); } } catch (Exception ex) @@ -263,7 +262,7 @@ await WriteHeaderAsync(channel, private async Task ReadHeaderAsync(IReader reader, CancellationToken token = default) { - byte[] headerData = (await reader.ReadAsync(HeaderLength, token: token)).ToArray(); + byte[] headerData = (await reader.ReadAsync(HeaderLength, token: token).OrThrow()).ToArray(); YamuxHeader header = YamuxHeader.FromBytes(headerData); _logger?.LogDebug("Read, stream-{streamId} type={type} flags={flags}{dataLength}", header.StreamID, header.Type, header.Flags, header.Type == YamuxHeaderType.Data ? $", {header.Length}B content" : ""); @@ -279,7 +278,7 @@ private async Task WriteHeaderAsync(IWriter writer, YamuxHeader header, ReadOnly _logger?.LogDebug("Write, stream-{streamId} type={type} flags={flags}{dataLength}", header.StreamID, header.Type, header.Flags, header.Type == YamuxHeaderType.Data ? $", {header.Length}B content" : ""); await writer.WriteAsync( - data.Length == 0 ? new ReadOnlySequence(headerBuffer) : data.Prepend(headerBuffer)); + data.Length == 0 ? new ReadOnlySequence(headerBuffer) : data.Prepend(headerBuffer)).OrThrow(); } private Task WriteGoAwayAsync(IWriter channel, SessionTerminationCode code) => diff --git a/src/samples/chat/ChatProtocol.cs b/src/samples/chat/ChatProtocol.cs index ee2b2d8e..4bd458d2 100644 --- a/src/samples/chat/ChatProtocol.cs +++ b/src/samples/chat/ChatProtocol.cs @@ -16,16 +16,15 @@ protected override async Task ConnectAsync(IChannel channel, IChannelFactory cha Console.Write("> "); _ = Task.Run(async () => { - while (!channel.Token.IsCancellationRequested) + for (; ;) { - ReadOnlySequence read = - await channel.ReadAsync(0, ReadBlockingMode.WaitAny, channel.Token); + ReadOnlySequence read = await channel.ReadAsync(0, ReadBlockingMode.WaitAny).OrThrow(); Console.Write(Encoding.UTF8.GetString(read).Replace("\n\n", "\n> ")); } - }, channel.Token); - while (!channel.Token.IsCancellationRequested) + }); + for (; ; ) { - string line = await Reader.ReadLineAsync(channel.Token); + string line = await Reader.ReadLineAsync(); Console.Write("> "); byte[] buf = Encoding.UTF8.GetBytes(line + "\n\n"); await channel.WriteAsync(new ReadOnlySequence(buf)); diff --git a/src/samples/chat/ConsoleReader.cs b/src/samples/chat/ConsoleReader.cs index e6e3affc..d11eb6bd 100644 --- a/src/samples/chat/ConsoleReader.cs +++ b/src/samples/chat/ConsoleReader.cs @@ -6,7 +6,7 @@ internal class ConsoleReader private readonly Queue> _requests = new(); private bool _isRequested; - public Task ReadLineAsync(CancellationToken token) + public Task ReadLineAsync(CancellationToken token = default) { TaskCompletionSource result = new(); token.Register(() => { result.SetResult(""); }); diff --git a/src/samples/perf-benchmarks/PerfProtocol.cs b/src/samples/perf-benchmarks/PerfProtocol.cs index eee59ba5..6dc94841 100644 --- a/src/samples/perf-benchmarks/PerfProtocol.cs +++ b/src/samples/perf-benchmarks/PerfProtocol.cs @@ -31,7 +31,7 @@ public async Task DialAsync(IChannel downChannel, IChannelFactory upChannelFacto byte[] bytes = new byte[1024 * 1024]; long bytesWritten = 0; - while (!downChannel.Token.IsCancellationRequested) + for (; ; ) { int bytesToWrite = (int)Math.Min(bytes.Length, TotalLoad - bytesWritten); if (bytesToWrite == 0) @@ -39,18 +39,17 @@ public async Task DialAsync(IChannel downChannel, IChannelFactory upChannelFacto break; } rand.NextBytes(bytes.AsSpan(0, bytesToWrite)); - ReadOnlySequence bytesToSend = new(bytes, 0, bytesToWrite); + ReadOnlySequence request = new(bytes, 0, bytesToWrite); + await downChannel.WriteAsync(request); bytesWritten += bytesToWrite; - await downChannel.WriteAsync(bytesToSend); - _logger?.LogDebug($"DIAL WRIT {bytesToSend.Length}"); + _logger?.LogDebug($"Sent {request.Length} more bytes"); } }); long bytesRead = 0; - while (!downChannel.Token.IsCancellationRequested) + for (; ; ) { - ReadOnlySequence read = - await downChannel.ReadAsync(0, ReadBlockingMode.WaitAny, downChannel.Token); + ReadOnlySequence read = await downChannel.ReadAsync(0, ReadBlockingMode.WaitAny).OrThrow(); _logger?.LogDebug($"DIAL READ {read.Length}"); bytesRead += read.Length; if (bytesRead == TotalLoad) @@ -65,23 +64,22 @@ public async Task ListenAsync(IChannel downChannel, IChannelFactory upChannelFac { ulong total = await downChannel.ReadVarintUlongAsync(); ulong bytesRead = 0; - while (!downChannel.Token.IsCancellationRequested) + for (; ; ) { - ReadOnlySequence read = - await downChannel.ReadAsync(0, ReadBlockingMode.WaitAny, downChannel.Token); + ReadOnlySequence read = await downChannel.ReadAsync(0, ReadBlockingMode.WaitAny).OrThrow(); if (read.Length == 0) { continue; } - _logger?.LogDebug($"LIST READ {read.Length}"); - await downChannel.WriteAsync(read); - _logger?.LogDebug($"LIST WRITE {read.Length}"); + _logger?.LogDebug($"Read {read.Length} more bytes"); + await downChannel.WriteAsync(read).OrThrow(); + _logger?.LogDebug($"Sent back {read.Length}"); bytesRead += (ulong)read.Length; if (bytesRead == total) { - _logger?.LogInformation($"LIST DONE"); + _logger?.LogInformation($"Finished"); return; } } diff --git a/src/samples/transport-interop/Program.cs b/src/samples/transport-interop/Program.cs index c495e688..3858d389 100644 --- a/src/samples/transport-interop/Program.cs +++ b/src/samples/transport-interop/Program.cs @@ -67,7 +67,7 @@ (i.OperationalStatus == OperationalStatus.Up && i.NetworkInterfaceType == NetworkInterfaceType.Ethernet)).ToList(); - IEnumerable addresses = NetworkInterface.GetAllNetworkInterfaces()! + IEnumerable addresses = NetworkInterface.GetAllNetworkInterfaces()! .Where(i => i.Name == "eth0" || (i.OperationalStatus == OperationalStatus.Up && i.NetworkInterfaceType == NetworkInterfaceType.Ethernet && From ded7bed53de36d97c21487a74a5bd5a4bdb4ff6f Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Thu, 18 Apr 2024 10:44:04 +0300 Subject: [PATCH 04/14] Fix tests --- .../Libp2p.Core.Tests/ReaderWriterTests.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/libp2p/Libp2p.Core.Tests/ReaderWriterTests.cs b/src/libp2p/Libp2p.Core.Tests/ReaderWriterTests.cs index 6f870f5c..80c90568 100644 --- a/src/libp2p/Libp2p.Core.Tests/ReaderWriterTests.cs +++ b/src/libp2p/Libp2p.Core.Tests/ReaderWriterTests.cs @@ -106,17 +106,17 @@ public async Task Test_ChannelWrites_Eof() await readerWriter.WriteEofAsync(); }); - Assert.That(await readerWriter.CanReadAsync(), Is.True); + Assert.That(await readerWriter.CanReadAsync(), Is.EqualTo(IOResult.Ok)); ReadOnlySequence res1 = await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAll).OrThrow(); - Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.DontWait)); - Assert.That(await readerWriter.CanReadAsync(), Is.False); + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.DontWait).OrThrow()); + Assert.That(await readerWriter.CanReadAsync(), Is.EqualTo(IOResult.Ended)); - Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAny)); - Assert.That(await readerWriter.CanReadAsync(), Is.False); + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAny).OrThrow()); + Assert.That(await readerWriter.CanReadAsync(), Is.EqualTo(IOResult.Ended)); - Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAll)); - Assert.That(await readerWriter.CanReadAsync(), Is.False); + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAll).OrThrow()); + Assert.That(await readerWriter.CanReadAsync(), Is.EqualTo(IOResult.Ended)); } @@ -127,10 +127,10 @@ public async Task Test_ChannelWrites_CannotWriteAfterEof(byte[] toWrite) Channel.ReaderWriter readerWriter = new(); await readerWriter.WriteEofAsync(); - Assert.That(await readerWriter.CanReadAsync(), Is.False); + Assert.That(await readerWriter.CanReadAsync(), Is.EqualTo(IOResult.Ended)); Assert.ThrowsAsync(async () => await readerWriter.WriteAsync(new ReadOnlySequence(toWrite)).OrThrow()); - Assert.That(await readerWriter.CanReadAsync(), Is.False); + Assert.That(await readerWriter.CanReadAsync(), Is.EqualTo(IOResult.Ended)); } [Test] @@ -149,13 +149,13 @@ public async Task Test_ChannelWrites_CanReadAny() Assert.That(res1, Has.Length.EqualTo(3)); Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.DontWait).OrThrow()); - Assert.That(await readerWriter.CanReadAsync(), Is.False); + Assert.That(await readerWriter.CanReadAsync(), Is.EqualTo(IOResult.Ended)); Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAny).OrThrow()); - Assert.That(await readerWriter.CanReadAsync(), Is.False); + Assert.That(await readerWriter.CanReadAsync(), Is.EqualTo(IOResult.Ended)); Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAll).OrThrow()); - Assert.That(await readerWriter.CanReadAsync(), Is.False); + Assert.That(await readerWriter.CanReadAsync(), Is.EqualTo(IOResult.Ended)); } [Test] @@ -170,7 +170,7 @@ public async Task Test_ChannelWrites_CannotReadAll_OnePacket() }); Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(5, ReadBlockingMode.WaitAll).OrThrow()); - Assert.That(await readerWriter.CanReadAsync(), Is.False); + Assert.That(await readerWriter.CanReadAsync(), Is.EqualTo(IOResult.Ended)); } [Test] @@ -187,6 +187,6 @@ public async Task Test_ChannelWrites_CannotReadAll_Fragmented() }); Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(5, ReadBlockingMode.WaitAll).OrThrow()); - Assert.That(await readerWriter.CanReadAsync(), Is.False); + Assert.That(await readerWriter.CanReadAsync(), Is.EqualTo(IOResult.Ended)); } } From 7bf34bdbecf278179368171277f923d9b177a43c Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Thu, 18 Apr 2024 15:55:03 +0300 Subject: [PATCH 05/14] Fix yamux --- src/libp2p/Libp2p.Core/ChannelFactory.cs | 4 +- src/libp2p/Libp2p.Core/PeerFactory.cs | 13 +- .../Libp2p.Core/UnwarpResultExtensions.cs | 6 +- .../Libp2p.Protocols.IpTcp/IpTcpProtocol.cs | 30 ++- .../MultistreamProtocolTests.cs | 2 +- .../MultistreamProtocol.cs | 16 +- .../Libp2p.Protocols.Noise/NoiseProtocol.cs | 121 ++++++------ .../Libp2p.Protocols.Yamux/YamuxProtocol.cs | 180 +++++++++--------- src/samples/chat/ChatProtocol.cs | 8 +- src/samples/chat/Program.cs | 8 +- 10 files changed, 195 insertions(+), 193 deletions(-) diff --git a/src/libp2p/Libp2p.Core/ChannelFactory.cs b/src/libp2p/Libp2p.Core/ChannelFactory.cs index 20bf449e..f63821c8 100644 --- a/src/libp2p/Libp2p.Core/ChannelFactory.cs +++ b/src/libp2p/Libp2p.Core/ChannelFactory.cs @@ -73,7 +73,7 @@ public Task SubDialAndBind(IChannel parent, IPeerContext context, IProtocol? subProtocol = req?.SubProtocol ?? SubProtocols.FirstOrDefault(); ChannelFactory? channelFactory = _factories[subProtocol] as ChannelFactory; - return subProtocol.DialAsync(((Channel)parent).Reverse, channelFactory, context) + return subProtocol.DialAsync(((Channel)parent), channelFactory, context) .ContinueWith(async task => { if (!task.IsCompletedSuccessfully) @@ -92,7 +92,7 @@ public Task SubListenAndBind(IChannel parent, IPeerContext context, IProtocol? subProtocol = req?.SubProtocol ?? SubProtocols.FirstOrDefault(); ChannelFactory? channelFactory = _factories[subProtocol] as ChannelFactory; - return subProtocol.ListenAsync(((Channel)parent).Reverse, channelFactory, context) + return subProtocol.ListenAsync(((Channel)parent), channelFactory, context) .ContinueWith(async task => { await parent.CloseAsync(); diff --git a/src/libp2p/Libp2p.Core/PeerFactory.cs b/src/libp2p/Libp2p.Core/PeerFactory.cs index 1360e8a1..5e343eb0 100644 --- a/src/libp2p/Libp2p.Core/PeerFactory.cs +++ b/src/libp2p/Libp2p.Core/PeerFactory.cs @@ -99,7 +99,10 @@ private Task DialAsync(IPeerContext peerContext, CancellationToken to { TaskCompletionSource cts = new(token); peerContext.SubDialRequests.Add(new ChannelRequest - { SubProtocol = PeerFactoryBuilderBase.CreateProtocolInstance(_serviceProvider), CompletionSource = cts }); + { + SubProtocol = PeerFactoryBuilderBase.CreateProtocolInstance(_serviceProvider), + CompletionSource = cts + }); return cts.Task; } @@ -108,7 +111,7 @@ protected virtual async Task DialAsync(LocalPeer peer, Multiaddress try { Channel chan = new(); - token.Register(() => chan.CloseAsync()); + token.Register(() => _ = chan.CloseAsync()); PeerContext context = new() { @@ -119,7 +122,9 @@ protected virtual async Task DialAsync(LocalPeer peer, Multiaddress context.RemotePeer = result; TaskCompletionSource tcs = new(); - context.OnRemotePeerConnection += remotePeer => + RemotePeerConnected remotePeerConnected = null!; + + remotePeerConnected = remotePeer => { if (((RemotePeer)remotePeer).LocalPeer != peer) { @@ -127,7 +132,9 @@ protected virtual async Task DialAsync(LocalPeer peer, Multiaddress } ConnectedTo(remotePeer, true).ContinueWith((t) => { tcs.TrySetResult(true); }); + context.OnRemotePeerConnection -= remotePeerConnected; }; + context.OnRemotePeerConnection += remotePeerConnected; _ = _protocol.DialAsync(chan, _upChannelFactory, context); diff --git a/src/libp2p/Libp2p.Core/UnwarpResultExtensions.cs b/src/libp2p/Libp2p.Core/UnwarpResultExtensions.cs index 10b86642..df85780b 100644 --- a/src/libp2p/Libp2p.Core/UnwarpResultExtensions.cs +++ b/src/libp2p/Libp2p.Core/UnwarpResultExtensions.cs @@ -10,19 +10,19 @@ public static async ValueTask OrThrow(this ValueTask self) { if (self.IsCompleted && self.Result != IOResult.Ok) { - throw new Exception(); + throw new Exception($"Unable to write, error: {self.Result}"); } var result = await self.AsTask(); if (result != IOResult.Ok) { - throw new Exception($"Unable to read, error: {result}"); + throw new Exception($"Unable to write, error: {result}"); } } public static async ValueTask> OrThrow(this ValueTask self) { if (self.IsCompleted && self.Result.Result != IOResult.Ok) { - throw new Exception(); + throw new Exception($"Unable to read, error: {self.Result}"); } var result = await self.AsTask(); if (result.Result != IOResult.Ok) diff --git a/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs b/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs index 31f660fe..1385619f 100644 --- a/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs @@ -11,7 +11,6 @@ namespace Nethermind.Libp2p.Protocols; -// TODO: Rewrite with SocketAsyncEventArgs public class IpTcpProtocol(ILoggerFactory? loggerFactory = null) : IProtocol { private readonly ILogger? _logger = loggerFactory?.CreateLogger(); @@ -110,30 +109,29 @@ await Task.Run(async () => }); } - public async Task DialAsync(IChannel _, IChannelFactory? channelFactory, IPeerContext context) + public async Task DialAsync(IChannel __, IChannelFactory? channelFactory, IPeerContext context) { if (channelFactory is null) { throw new ProtocolViolationException(); } - _logger?.LogInformation("DialAsync({contextId})", context.Id); - - TaskCompletionSource waitForStop = new(TaskCreationOptions.RunContinuationsAsynchronously); Socket client = new(SocketType.Stream, ProtocolType.Tcp); Multiaddress addr = context.RemotePeer.Address; MultiaddressProtocol ipProtocol = addr.Has() ? addr.Get() : addr.Get(); IPAddress ipAddress = IPAddress.Parse(ipProtocol.ToString()); int tcpPort = addr.Get().Port; + + _logger?.LogDebug("Dialing {0}:{1}", ipAddress, tcpPort); + try { await client.ConnectAsync(new IPEndPoint(ipAddress, tcpPort)); } catch (SocketException e) { - _logger?.LogInformation($"Failed({context.Id}) to connect {addr}"); + _logger?.LogDebug($"Failed({context.Id}) to connect {addr}"); _logger?.LogTrace($"Failed with {e.GetType()}: {e.Message}"); - // TODO: Add proper exception and reconnection handling return; } @@ -166,23 +164,21 @@ public async Task DialAsync(IChannel _, IChannelFactory? channelFactory, IPeerCo { for (; ; ) { - int len = await client.ReceiveAsync(buf, SocketFlags.None); - if (len != 0) + int dataLength = await client.ReceiveAsync(buf, SocketFlags.None); + if (dataLength != 0) { - _logger?.LogDebug("Receive {0} data, len={1}", context.Id, len); - if ((await upChannel.WriteAsync(new ReadOnlySequence(buf[..len]))) != IOResult.Ok) + _logger?.LogDebug("Receive {0} data, len={1}", context.Id, dataLength); + if ((await upChannel.WriteAsync(new ReadOnlySequence(buf[..dataLength]))) != IOResult.Ok) { break; }; } } - waitForStop.SetCanceled(); } catch (SocketException) { - await upChannel.CloseAsync(); - waitForStop.SetCanceled(); + _ = upChannel.CloseAsync(); } }); @@ -192,15 +188,13 @@ public async Task DialAsync(IChannel _, IChannelFactory? channelFactory, IPeerCo { await foreach (ReadOnlySequence data in upChannel.ReadAllAsync()) { + _logger?.LogDebug("Send {0} data, len={1}", context.Id, data.Length); await client.SendAsync(data.ToArray(), SocketFlags.None); } - - waitForStop.SetCanceled(); } catch (SocketException) { - await upChannel.CloseAsync(); - waitForStop.SetCanceled(); + _ = upChannel.CloseAsync(); } }); diff --git a/src/libp2p/Libp2p.Protocols.Multistream.Tests/MultistreamProtocolTests.cs b/src/libp2p/Libp2p.Protocols.Multistream.Tests/MultistreamProtocolTests.cs index 4951124f..62edfb23 100644 --- a/src/libp2p/Libp2p.Protocols.Multistream.Tests/MultistreamProtocolTests.cs +++ b/src/libp2p/Libp2p.Protocols.Multistream.Tests/MultistreamProtocolTests.cs @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: MIT -namespace Nethermind.Libp2p.Protocols.Multistream.Tests; +using Nethermind.Libp2p.Protocols; [TestFixture] [Parallelizable(scope: ParallelScope.All)] diff --git a/src/libp2p/Libp2p.Protocols.Multistream/MultistreamProtocol.cs b/src/libp2p/Libp2p.Protocols.Multistream/MultistreamProtocol.cs index 44acdb15..aee2356c 100644 --- a/src/libp2p/Libp2p.Protocols.Multistream/MultistreamProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Multistream/MultistreamProtocol.cs @@ -32,7 +32,7 @@ public async Task DialAsync(IChannel channel, IChannelFactory channelFactory, { await channel.WriteLineAsync(selector.Id); string selectorLine = await channel.ReadLineAsync(); - _logger?.LogDebug($"Sent {selector.Id}, recv {selectorLine}"); + _logger?.LogTrace($"Proposed {selector.Id}, answer: {selectorLine}"); if (selectorLine == selector.Id) { return true; @@ -50,7 +50,7 @@ public async Task DialAsync(IChannel channel, IChannelFactory channelFactory, if (context.SpecificProtocolRequest?.SubProtocol is not null) { - _logger?.LogDebug($"DIAL FOR SPECIFIC PROTOCOL {context.SpecificProtocolRequest.SubProtocol}"); + _logger?.LogDebug($"Proposing just {context.SpecificProtocolRequest.SubProtocol}"); if (await DialProtocol(context.SpecificProtocolRequest.SubProtocol) == true) { selected = context.SpecificProtocolRequest.SubProtocol; @@ -76,10 +76,10 @@ public async Task DialAsync(IChannel channel, IChannelFactory channelFactory, if (selected is null) { - _logger?.LogDebug($"DIAL NEG FAILED {string.Join(", ", channelFactory.SubProtocols)}"); + _logger?.LogDebug($"Negotiation failed"); return; } - _logger?.LogDebug($"DIAL NEG SUCCEED {string.Join(", ", channelFactory.SubProtocols)} -> {selected}"); + _logger?.LogDebug($"Protocol selected during dialing: {selected}"); await channelFactory.SubDialAndBind(channel, context, selected); } @@ -100,21 +100,21 @@ public async Task ListenAsync(IChannel channel, IChannelFactory channelFactory, if (selected is not null) { await channel.WriteLineAsync(selected.Id); - _logger?.LogDebug($"Recv {proto}, sent {selected?.Id}"); + _logger?.LogTrace($"Proposed by remote {proto}, answer: {selected?.Id}"); break; } - _logger?.LogDebug($"Recv {proto}, sent {ProtocolNotSupported}"); + _logger?.LogTrace($"Proposed by remote {proto}, answer: {ProtocolNotSupported}"); await channel.WriteLineAsync(ProtocolNotSupported); } if (selected is null) { - _logger?.LogDebug($"LIST NEG FAILED {string.Join(", ", channelFactory.SubProtocols)}"); + _logger?.LogDebug($"Negotiation failed"); return; } - _logger?.LogDebug($"LIST NEG SUCCEED {string.Join(", ", channelFactory.SubProtocols)} -> {selected}"); + _logger?.LogDebug($"Protocol selected during listening: {selected}"); await channelFactory.SubListenAndBind(channel, context, selected); } diff --git a/src/libp2p/Libp2p.Protocols.Noise/NoiseProtocol.cs b/src/libp2p/Libp2p.Protocols.Noise/NoiseProtocol.cs index 3f1b46ce..b79bf87b 100644 --- a/src/libp2p/Libp2p.Protocols.Noise/NoiseProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Noise/NoiseProtocol.cs @@ -12,31 +12,21 @@ using Multiformats.Address.Protocols; using Nethermind.Libp2p.Protocols.Noise.Dto; using PublicKey = Nethermind.Libp2p.Core.Dto.PublicKey; -using Org.BouncyCastle.Utilities.Encoders; -using static System.Runtime.InteropServices.JavaScript.JSType; namespace Nethermind.Libp2p.Protocols; /// /// -public class NoiseProtocol : IProtocol +public class NoiseProtocol(ILoggerFactory? loggerFactory = null) : IProtocol { - private readonly Protocol _protocol; - private readonly byte[][] _psks; - private readonly ILogger? _logger; - public string Id => "/noise"; - private const string PayloadSigPrefix = "noise-libp2p-static-key:"; - - public NoiseProtocol(ILoggerFactory? loggerFactory = null) - { - _logger = loggerFactory?.CreateLogger(); - _protocol = new Protocol( + private readonly Protocol _protocol = new Protocol( HandshakePattern.XX, CipherFunction.ChaChaPoly, HashFunction.Sha256 ); - _psks = Array.Empty(); - } + private readonly ILogger? _logger = loggerFactory?.CreateLogger(); + public string Id => "/noise"; + private const string PayloadSigPrefix = "noise-libp2p-static-key:"; public async Task DialAsync(IChannel downChannel, IChannelFactory upChannelFactory, IPeerContext context) { @@ -67,11 +57,9 @@ public async Task DialAsync(IChannel downChannel, IChannelFactory upChannelFacto context.RemotePeer.Address.Add(new P2P(remotePeerId.ToString())); } - byte[] msg = Encoding.UTF8.GetBytes(PayloadSigPrefix) - .Concat(ByteString.CopyFrom(clientStatic.PublicKey)) - .ToArray(); + byte[] msg = [.. Encoding.UTF8.GetBytes(PayloadSigPrefix), .. ByteString.CopyFrom(clientStatic.PublicKey)]; byte[] sig = new byte[64]; - Ed25519.Sign(context.LocalPeer.Identity.PrivateKey.Data.ToArray(), 0, msg, 0, msg.Length, sig, 0); + Ed25519.Sign([.. context.LocalPeer.Identity.PrivateKey!.Data], 0, msg, 0, msg.Length, sig, 0); NoiseHandshakePayload payload = new() { IdentityKey = context.LocalPeer.Identity.PublicKey.ToByteString(), @@ -82,9 +70,14 @@ public async Task DialAsync(IChannel downChannel, IChannelFactory upChannelFacto StreamMuxers = { "na" } } }; - _logger?.LogInformation("local pub key {0}", clientStatic.PublicKey); - _logger?.LogInformation("local prv key {0}", clientStatic.PrivateKey); - _logger?.LogInformation("remote pub key {0}", handshakeState.RemoteStaticPublicKey.ToArray()); + + if (_logger is not null && _logger.IsEnabled(LogLevel.Trace)) + { + _logger?.LogTrace("Local public key {0}", Convert.ToHexString(clientStatic.PublicKey)); + //_logger?.LogTrace("local prv key {0}", clientStatic.PrivateKey); + _logger?.LogTrace("Remote public key {0}", Convert.ToHexString(handshakeState.RemoteStaticPublicKey.ToArray())); + } + (int BytesWritten, byte[] HandshakeHash, Transport Transport) msg2 = handshakeState.WriteMessage(payload.ToByteArray(), buffer); BinaryPrimitives.WriteInt16BigEndian(lenBytes.AsSpan(), (short)msg2.BytesWritten); @@ -92,43 +85,11 @@ public async Task DialAsync(IChannel downChannel, IChannelFactory upChannelFacto await downChannel.WriteAsync(new ReadOnlySequence(buffer, 0, msg2.BytesWritten)); Transport? transport = msg2.Transport; - IChannel upChannel = upChannelFactory.SubDial(context); - + _logger?.LogDebug("Established connection to {peer}", context.RemotePeer.Address); - Task t = Task.Run(async () => - { - for (; ; ) - { - ReadResult dataReadResult = await upChannel.ReadAsync(Protocol.MaxMessageLength - 16, ReadBlockingMode.WaitAny); - if (dataReadResult.Result != IOResult.Ok) - { - return; - } - - byte[] buffer = new byte[2 + 16 + dataReadResult.Data.Length]; - - int bytesWritten = transport.WriteMessage(dataReadResult.Data.ToArray(), buffer.AsSpan(2)); - BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(), (ushort)bytesWritten); - await downChannel.WriteAsync(new ReadOnlySequence(buffer)); - } - }); - // DOWN -> UP - Task t2 = Task.Run(async () => - { - for (; ; ) - { - byte[] lengthBytes = (await downChannel.ReadAsync(2, ReadBlockingMode.WaitAll).OrThrow()).ToArray(); - int length = BinaryPrimitives.ReadUInt16BigEndian(lengthBytes.AsSpan()); - - ReadOnlySequence request = await downChannel.ReadAsync(length).OrThrow(); - byte[] buffer = new byte[len - 16]; - - int bytesRead = transport.ReadMessage(request.ToArray(), buffer); - await upChannel.WriteAsync(new ReadOnlySequence(buffer, 0, bytesRead)); - } - }); + IChannel upChannel = upChannelFactory.SubDial(context); - await Task.WhenAny(t, t2); + await ExchangeData(transport, downChannel, upChannel); } public async Task ListenAsync(IChannel downChannel, IChannelFactory upChannelFactory, IPeerContext context) @@ -148,7 +109,7 @@ public async Task ListenAsync(IChannel downChannel, IChannelFactory upChannelFac .Concat(ByteString.CopyFrom(serverStatic.PublicKey)) .ToArray(); byte[] sig = new byte[64]; - Ed25519.Sign(context.LocalPeer.Identity.PrivateKey.Data.ToArray(), 0, msg, 0, msg.Length, sig, 0); + Ed25519.Sign(context.LocalPeer.Identity.PrivateKey!.Data.ToArray(), 0, msg, 0, msg.Length, sig, 0); NoiseHandshakePayload payload = new() { IdentityKey = context.LocalPeer.Identity.PublicKey.ToByteString(), @@ -183,8 +144,18 @@ public async Task ListenAsync(IChannel downChannel, IChannelFactory upChannelFac context.RemotePeer.Address.Add(new P2P(remotePeerId.ToString())); } + _logger?.LogDebug("Established connection to {peer}", context.RemotePeer.Address); + IChannel upChannel = upChannelFactory.SubListen(context); + await ExchangeData(transport, downChannel, upChannel); + + _logger?.LogDebug("Closed"); + } + + private static Task ExchangeData(Transport transport, IChannel downChannel, IChannel upChannel) + { + // UP -> DOWN Task t = Task.Run(async () => { for (; ; ) @@ -199,7 +170,11 @@ public async Task ListenAsync(IChannel downChannel, IChannelFactory upChannelFac int bytesWritten = transport.WriteMessage(dataReadResult.Data.ToArray(), buffer.AsSpan(2)); BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(), (ushort)bytesWritten); - await downChannel.WriteAsync(new ReadOnlySequence(buffer)); + IOResult writeResult = await downChannel.WriteAsync(new ReadOnlySequence(buffer)); + if (writeResult != IOResult.Ok) + { + return; + } } }); // DOWN -> UP @@ -207,17 +182,31 @@ public async Task ListenAsync(IChannel downChannel, IChannelFactory upChannelFac { for (; ; ) { - byte[] lengthBytes = (await downChannel.ReadAsync(2, ReadBlockingMode.WaitAll).OrThrow()).ToArray(); - int length = BinaryPrimitives.ReadUInt16BigEndian(lengthBytes.AsSpan()); + ReadResult lengthBytesReadResult = await downChannel.ReadAsync(2, ReadBlockingMode.WaitAll); + if (lengthBytesReadResult.Result != IOResult.Ok) + { + return; + } - ReadOnlySequence request = await downChannel.ReadAsync(length).OrThrow(); - byte[] buffer = new byte[len - 16]; + int length = BinaryPrimitives.ReadUInt16BigEndian(lengthBytesReadResult.Data.ToArray().AsSpan()); - int bytesRead = transport.ReadMessage(request.ToArray(), buffer); - await upChannel.WriteAsync(new ReadOnlySequence(buffer, 0, bytesRead)); + ReadResult dataReadResult = await downChannel.ReadAsync(length); + if (dataReadResult.Result != IOResult.Ok) + { + return; + } + byte[] buffer = new byte[length - 16]; + + int bytesRead = transport.ReadMessage(dataReadResult.Data.ToArray(), buffer); + + IOResult writeResult = await upChannel.WriteAsync(new ReadOnlySequence(buffer, 0, bytesRead)); + if (writeResult != IOResult.Ok) + { + return; + } } }); - await Task.WhenAny(t, t2); + return Task.WhenAny(t, t2); } } diff --git a/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs b/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs index 26013085..7ca3e956 100644 --- a/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs @@ -4,19 +4,17 @@ using Microsoft.Extensions.Logging; using Nethermind.Libp2p.Core; using System.Buffers; -using System.Text; +using System.Threading.Channels; +using System.Xml.Linq; namespace Nethermind.Libp2p.Protocols; -public class YamuxProtocol : SymmetricProtocol, IProtocol +public class YamuxProtocol(ILoggerFactory? loggerFactory = null) : SymmetricProtocol, IProtocol { private const int HeaderLength = 12; - private readonly ILogger? _logger; + private const int PingDelay = 30_000; - public YamuxProtocol(ILoggerFactory? loggerFactory = null) - { - _logger = loggerFactory?.CreateLogger(); - } + private readonly ILogger? _logger = loggerFactory?.CreateLogger(); public string Id => "/yamux/1.0.0"; @@ -30,36 +28,32 @@ protected override async Task ConnectAsync(IChannel channel, IChannelFactory? ch throw new ArgumentException("ChannelFactory should be available for a muxer", nameof(channelFactory)); } - _logger?.LogInformation("Yamux as {role}", isListener ? "listener" : "dialer"); + _logger?.LogInformation(isListener ? "Listen" : "Dial"); int streamIdCounter = isListener ? 2 : 1; Dictionary channels = new(); - if (!isListener) - { - await WriteHeaderAsync(channel, - new YamuxHeader { Flags = YamuxHeaderFlags.Syn, Type = YamuxHeaderType.Ping, StreamID = 0 }); - } - context.Connected(context.RemotePeer); - _ = Task.Run(async () => + int pingCounter = 0; + + using Timer timer = new((s) => + { + int localPingCounter = ++pingCounter; + _ = WriteHeaderAsync(channel, new YamuxHeader { Type = YamuxHeaderType.Ping, Flags = YamuxHeaderFlags.Syn, Length = localPingCounter }); + }, null, 0, PingDelay); + + _ = Task.Run(() => { - foreach (IChannelRequest request in context.SubDialRequests) + foreach (IChannelRequest request in context.SubDialRequests.GetConsumingEnumerable()) { int streamId = streamIdCounter; Interlocked.Add(ref streamIdCounter, 2); - _logger?.LogDebug("Trying to dial with protocol {proto} via stream-{streamId}", request.SubProtocol?.Id, streamId); + _logger?.LogDebug("Stream {stream id}: Dialing with protocol {proto}", streamId, request.SubProtocol?.Id); channels[streamId] = new ChannelState { Request = request }; - await WriteHeaderAsync(channel, - new YamuxHeader - { - Flags = YamuxHeaderFlags.Syn, - Type = YamuxHeaderType.Data, - StreamID = streamId, - }); - _ = ActivateUpchannel(streamId, request); + + _ = ActivateUpchannel(streamId, YamuxHeaderFlags.Syn, request); } }); @@ -70,16 +64,20 @@ await WriteHeaderAsync(channel, if (header.StreamID is 0) { - if ((header.Flags & YamuxHeaderFlags.Syn) == YamuxHeaderFlags.Syn) + if (header.Type == YamuxHeaderType.Ping) { - _logger?.LogDebug("Confirming session stream"); - _ = WriteHeaderAsync(channel, - new YamuxHeader - { - Flags = YamuxHeaderFlags.Ack, - Type = YamuxHeaderType.Data, - StreamID = header.StreamID - }); + if ((header.Flags & YamuxHeaderFlags.Syn) == YamuxHeaderFlags.Syn) + { + _ = WriteHeaderAsync(channel, + new YamuxHeader + { + Flags = YamuxHeaderFlags.Ack, + Type = YamuxHeaderType.Ping, + Length = header.Length, + }); + + _logger?.LogDebug("Ping received and acknowledged"); + } } if (header.Type == YamuxHeaderType.GoAway) @@ -90,7 +88,7 @@ await WriteHeaderAsync(channel, { if (channelState.Channel is not null) { - await channelState.Channel.WriteEofAsync(); + await channelState.Channel.CloseAsync(); } } @@ -102,7 +100,8 @@ await WriteHeaderAsync(channel, if (channels.TryAdd(header.StreamID, new()) || (header.Flags & YamuxHeaderFlags.Syn) == YamuxHeaderFlags.Syn) { - _logger?.LogDebug("Request for a stream"); + _logger?.LogDebug("Stream {stream id}: Requested", header.StreamID); + _ = WriteHeaderAsync(channel, new YamuxHeader { @@ -116,41 +115,62 @@ await WriteHeaderAsync(channel, { if (header.Length > channels[header.StreamID].WindowSize) { + _logger?.LogDebug("Stream {stream id}: data length > windows size: {length} > {window size}", + header.StreamID, data.Length, channels[header.StreamID].WindowSize); + await WriteGoAwayAsync(channel, SessionTerminationCode.ProtocolError); return; } + data = new ReadOnlySequence((await channel.ReadAsync(header.Length).OrThrow()).ToArray()); - _logger?.LogDebug("Recv data, stream-{0}, len={1}, data: {data}", - header.StreamID, data.Length, - Encoding.ASCII.GetString(data.ToArray().Select(c => c == 0x1b || c == 0x07 ? (byte)0x2e : c).ToArray())); + _logger?.LogDebug("Stream {stream id}: Read {1}", header.StreamID, data.Length); } if (channels[header.StreamID].Channel is null) { - _ = ActivateUpchannel(header.StreamID, null); - _logger?.LogDebug("Channel activated for stream-{streamId}", header.StreamID); + _ = ActivateUpchannel(header.StreamID, YamuxHeaderFlags.Ack, null); + _logger?.LogDebug("Stream {stream id}: Acknowledged", header.StreamID); } if (header.Type == YamuxHeaderType.Data) { - _logger?.LogDebug("Write data to upchannel, stream-{0}, len={1}", header.StreamID, data.Length); + _logger?.LogDebug("Stream {stream id}: Send to upchannel, length={length}", header.StreamID, data.Length); await channels[header.StreamID].Channel!.WriteAsync(data); } if (header.Type == YamuxHeaderType.WindowUpdate) { - _logger?.LogDebug("Write data to upchannel, stream-{0}, len={1}", header.StreamID, data.Length); - channels[header.StreamID] = channels[header.StreamID] with { WindowSize = header.Length }; + if (header.Length != 0) + { + _logger?.LogDebug("Stream {stream id}: Window update requested: {old} => {new}", header.StreamID, channels[header.StreamID].WindowSize, header.Length); + channels[header.StreamID] = channels[header.StreamID] with { WindowSize = header.Length }; + } + else + { + _logger?.LogDebug("Stream {stream id}: Window update as a signal received: {flags}", header.StreamID, header.Flags); + } } if ((header.Flags & YamuxHeaderFlags.Fin) == YamuxHeaderFlags.Fin) { - _ = channels[header.StreamID].Channel?.WriteEofAsync(); - channels[header.StreamID] = channels[header.StreamID] with { State = channels[header.StreamID].State | State.DownClosed }; - _logger?.LogDebug("Fin, stream-{0}", header.StreamID); - if (channels[header.StreamID].State == State.Closed) + if (!channels.TryGetValue(header.StreamID, out ChannelState state)) + { + continue; + } + + IChannel? upChannel = state.Channel; + if (upChannel is null) + { + continue; + } + + _ = upChannel.WriteEofAsync(); + _logger?.LogDebug("Stream {stream id}: Finish receiving", header.StreamID); + + if (upChannel.GetAwaiter().IsCompleted) { channels.Remove(header.StreamID); + _logger?.LogDebug("Stream {stream id}: Closed", header.StreamID); } } @@ -158,13 +178,13 @@ await WriteHeaderAsync(channel, { _ = channels[header.StreamID].Channel?.CloseAsync(); channels.Remove(header.StreamID); - _logger?.LogDebug("Rst, stream-{0}", header.StreamID); + _logger?.LogDebug("Stream {stream id}: Reset", header.StreamID); } } await WriteGoAwayAsync(channel, SessionTerminationCode.Ok); - async Task ActivateUpchannel(int streamId, IChannelRequest? channelRequest) + async Task ActivateUpchannel(int streamId, YamuxHeaderFlags initiationFlag, IChannelRequest? channelRequest) { if (channels[streamId].Channel is not null) { @@ -173,7 +193,7 @@ async Task ActivateUpchannel(int streamId, IChannelRequest? channelRequest) bool isListenerChannel = isListener ^ (streamId % 2 == 0); - _logger?.LogDebug("Create chan for stream-{0} isListener = {1}", streamId, isListenerChannel); + _logger?.LogDebug("Create chan for Stream {stream id} isListener = {1}", streamId, isListenerChannel); IChannel upChannel; if (isListenerChannel) @@ -188,15 +208,21 @@ async Task ActivateUpchannel(int streamId, IChannelRequest? channelRequest) } channels[streamId] = new(upChannel, channelRequest); - + TaskCompletionSource? tcs = channels[streamId].Request?.CompletionSource; try { + await WriteHeaderAsync(channel, + new YamuxHeader + { + Flags = initiationFlag, + Type = YamuxHeaderType.Data, + StreamID = streamId + }); + await foreach (var upData in upChannel.ReadAllAsync()) { - //ReadOnlySequence upData = - // await channels[streamId].Channel!.ReadAsync(channels[streamId].WindowSize, ReadBlockingMode.WaitAny, channel.Token); - _logger?.LogDebug("Read data from upchannel, stream-{0}, len={1}", streamId, upData.Length); + _logger?.LogDebug("Stream {stream id}: Receive from upchannel, length={length}", streamId, upData.Length); for (int i = 0; i < upData.Length;) { @@ -219,10 +245,14 @@ await WriteHeaderAsync(channel, Type = YamuxHeaderType.WindowUpdate, StreamID = streamId }); - channels[streamId] = channels[streamId] with { State = channels[streamId].State | State.UpClosed }; - _logger?.LogDebug("Close, stream-{id}", streamId); + _logger?.LogDebug("Stream {stream id}: Upchannel finished writing", streamId); + + await upChannel; + channels.Remove(streamId); + + _logger?.LogDebug("Stream {stream id}: Closed", streamId); } - catch + catch (Exception e) { await WriteHeaderAsync(channel, new YamuxHeader @@ -234,29 +264,18 @@ await WriteHeaderAsync(channel, _ = upChannel.CloseAsync(); channels.Remove(streamId); - _logger?.LogDebug("Unexpected close, stream-{id}", streamId); + _logger?.LogDebug("Stream {stream id}: Unexpected error, closing: {error}", streamId, e.Message); } - - await upChannel; - - channels.Remove(streamId); - if ((channels[streamId].State | State.UpClosed) == 0) + finally { - _ = WriteHeaderAsync(channel, - new YamuxHeader - { - Flags = YamuxHeaderFlags.Fin, - Type = YamuxHeaderType.WindowUpdate, - StreamID = streamId - }); + tcs?.SetResult(); } - _logger?.LogDebug("Close, stream-{id}", streamId); } } catch (Exception ex) { await WriteGoAwayAsync(channel, SessionTerminationCode.InternalError); - _logger?.LogDebug("Multiplexer closed with exception {exception}", ex.Message); + _logger?.LogDebug("Closed with exception {exception}", ex.Message); } } @@ -264,8 +283,7 @@ private async Task ReadHeaderAsync(IReader reader, CancellationToke { byte[] headerData = (await reader.ReadAsync(HeaderLength, token: token).OrThrow()).ToArray(); YamuxHeader header = YamuxHeader.FromBytes(headerData); - _logger?.LogDebug("Read, stream-{streamId} type={type} flags={flags}{dataLength}", header.StreamID, header.Type, header.Flags, - header.Type == YamuxHeaderType.Data ? $", {header.Length}B content" : ""); + _logger?.LogTrace("Stream {stream id}: Receive type={type} flags={flags} length={length}", header.StreamID, header.Type, header.Flags, header.Length); return header; } @@ -275,8 +293,7 @@ private async Task WriteHeaderAsync(IWriter writer, YamuxHeader header, ReadOnly header.Length = (int)data.Length; YamuxHeader.ToBytes(headerBuffer, ref header); - _logger?.LogDebug("Write, stream-{streamId} type={type} flags={flags}{dataLength}", header.StreamID, header.Type, header.Flags, - header.Type == YamuxHeaderType.Data ? $", {header.Length}B content" : ""); + _logger?.LogTrace("Stream {stream id}: Send type={type} flags={flags} length={length}", header.StreamID, header.Type, header.Flags, header.Length); await writer.WriteAsync( data.Length == 0 ? new ReadOnlySequence(headerBuffer) : data.Prepend(headerBuffer)).OrThrow(); } @@ -294,14 +311,5 @@ private struct ChannelState(IChannel? channel, IChannelRequest? request = defaul public IChannel? Channel { get; set; } = channel; public IChannelRequest? Request { get; set; } = request; public int WindowSize { get; set; } = 256_000; - public State State { get; set; } - } - - private enum State - { - Open = 0, - UpClosed = 1, - DownClosed = 2, - Closed = UpClosed | DownClosed, } } diff --git a/src/samples/chat/ChatProtocol.cs b/src/samples/chat/ChatProtocol.cs index 4bd458d2..0bdf1997 100644 --- a/src/samples/chat/ChatProtocol.cs +++ b/src/samples/chat/ChatProtocol.cs @@ -10,11 +10,11 @@ internal class ChatProtocol : SymmetricProtocol, IProtocol private static readonly ConsoleReader Reader = new(); public string Id => "/chat/1.0.0"; - protected override async Task ConnectAsync(IChannel channel, IChannelFactory channelFactory, + protected override async Task ConnectAsync(IChannel channel, IChannelFactory? channelFactory, IPeerContext context, bool isListener) { Console.Write("> "); - _ = Task.Run(async () => + _ =Task.Run(async () => { for (; ;) { @@ -25,6 +25,10 @@ protected override async Task ConnectAsync(IChannel channel, IChannelFactory cha for (; ; ) { string line = await Reader.ReadLineAsync(); + if(line == "exit") + { + return; + } Console.Write("> "); byte[] buf = Encoding.UTF8.GetBytes(line + "\n\n"); await channel.WriteAsync(new ReadOnlySequence(buf)); diff --git a/src/samples/chat/Program.cs b/src/samples/chat/Program.cs index 1f6d3152..0d83cacd 100644 --- a/src/samples/chat/Program.cs +++ b/src/samples/chat/Program.cs @@ -19,9 +19,9 @@ })) .BuildServiceProvider(); +ILogger logger = serviceProvider.GetService()!.CreateLogger("Chat"); IPeerFactory peerFactory = serviceProvider.GetService()!; -ILogger logger = serviceProvider.GetService()!.CreateLogger("Chat"); CancellationTokenSource ts = new(); if (args.Length > 0 && args[0] == "-d") @@ -46,11 +46,11 @@ ILocalPeer peer = peerFactory.Create(optionalFixedIdentity); string addrTemplate = args.Contains("-quic") ? - "/ip4/0.0.0.0/udp/{0}/quic-v1/p2p/{1}" : - "/ip4/0.0.0.0/tcp/{0}/p2p/{1}"; + "/ip4/0.0.0.0/udp/{0}/quic-v1" : + "/ip4/0.0.0.0/tcp/{0}"; IListener listener = await peer.ListenAsync( - string.Format(addrTemplate, args.Length > 0 && args[0] == "-sp" ? args[1] : "0", peer.Identity.PeerId), + string.Format(addrTemplate, args.Length > 0 && args[0] == "-sp" ? args[1] : "0"), ts.Token); logger.LogInformation($"Listener started at {listener.Address}"); listener.OnConnection += async remotePeer => logger.LogInformation($"A peer connected {remotePeer.Address}"); From c9742ac2d60db2667276cdd0fd1ba0982a064f94 Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Thu, 18 Apr 2024 16:49:02 +0300 Subject: [PATCH 06/14] More fixes --- .../IdentifyProtocol.cs | 4 +- .../Libp2p.Protocols.IpTcp/IpTcpProtocol.cs | 5 ++ .../Libp2p.Protocols.Yamux/YamuxProtocol.cs | 90 ++++++++----------- 3 files changed, 42 insertions(+), 57 deletions(-) diff --git a/src/libp2p/Libp2p.Protocols.Identify/IdentifyProtocol.cs b/src/libp2p/Libp2p.Protocols.Identify/IdentifyProtocol.cs index 16f24498..fda66b4a 100644 --- a/src/libp2p/Libp2p.Protocols.Identify/IdentifyProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Identify/IdentifyProtocol.cs @@ -62,8 +62,8 @@ public async Task ListenAsync(IChannel channel, IChannelFactory? channelFactory, }; byte[] ar = new byte[identify.CalculateSize()]; identify.WriteTo(ar); + await channel.WriteSizeAndDataAsync(ar); - _logger?.LogDebug("Sent peer info {0}", identify); - _logger?.LogInformation("Sent peer id to {0}", context.RemotePeer.Address); + _logger?.LogDebug("Sent peer info {identify}", identify); } } diff --git a/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs b/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs index 1385619f..0b8d1d17 100644 --- a/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs @@ -19,6 +19,11 @@ public class IpTcpProtocol(ILoggerFactory? loggerFactory = null) : IProtocol public async Task ListenAsync(IChannel __, IChannelFactory? channelFactory, IPeerContext context) { + if(channelFactory is null) + { + throw new Exception("Protocol is not properly instantiated"); + } + Multiaddress addr = context.LocalPeer.Address; bool isIP4 = addr.Has(); MultiaddressProtocol ipProtocol = isIP4 ? addr.Get() : addr.Get(); diff --git a/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs b/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs index 7ca3e956..a6baf61e 100644 --- a/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs @@ -98,24 +98,27 @@ protected override async Task ConnectAsync(IChannel channel, IChannelFactory? ch continue; } - if (channels.TryAdd(header.StreamID, new()) || (header.Flags & YamuxHeaderFlags.Syn) == YamuxHeaderFlags.Syn) + if ((header.Flags & YamuxHeaderFlags.Syn) == YamuxHeaderFlags.Syn) { - _logger?.LogDebug("Stream {stream id}: Requested", header.StreamID); - - _ = WriteHeaderAsync(channel, - new YamuxHeader + try + { + if (!channels.ContainsKey(header.StreamID)) { - Flags = YamuxHeaderFlags.Ack, - Type = YamuxHeaderType.Data, - StreamID = header.StreamID - }); + channels[header.StreamID] = new(); + _ = ActivateUpchannel(header.StreamID, YamuxHeaderFlags.Ack, null); + } + } + catch + { + throw; + } } if (header is { Type: YamuxHeaderType.Data, Length: not 0 }) { if (header.Length > channels[header.StreamID].WindowSize) { - _logger?.LogDebug("Stream {stream id}: data length > windows size: {length} > {window size}", + _logger?.LogDebug("Stream {stream id}: Data length > windows size: {length} > {window size}", header.StreamID, data.Length, channels[header.StreamID].WindowSize); await WriteGoAwayAsync(channel, SessionTerminationCode.ProtocolError); @@ -123,32 +126,15 @@ protected override async Task ConnectAsync(IChannel channel, IChannelFactory? ch } data = new ReadOnlySequence((await channel.ReadAsync(header.Length).OrThrow()).ToArray()); - _logger?.LogDebug("Stream {stream id}: Read {1}", header.StreamID, data.Length); - } - - if (channels[header.StreamID].Channel is null) - { - _ = ActivateUpchannel(header.StreamID, YamuxHeaderFlags.Ack, null); - _logger?.LogDebug("Stream {stream id}: Acknowledged", header.StreamID); - } - - if (header.Type == YamuxHeaderType.Data) - { + _logger?.LogDebug("Stream {stream id}: Send to upchannel, length={length}", header.StreamID, data.Length); await channels[header.StreamID].Channel!.WriteAsync(data); } - if (header.Type == YamuxHeaderType.WindowUpdate) + if (header.Type == YamuxHeaderType.WindowUpdate && header.Length != 0) { - if (header.Length != 0) - { - _logger?.LogDebug("Stream {stream id}: Window update requested: {old} => {new}", header.StreamID, channels[header.StreamID].WindowSize, header.Length); - channels[header.StreamID] = channels[header.StreamID] with { WindowSize = header.Length }; - } - else - { - _logger?.LogDebug("Stream {stream id}: Window update as a signal received: {flags}", header.StreamID, header.Flags); - } + _logger?.LogDebug("Stream {stream id}: Window update requested: {old} => {new}", header.StreamID, channels[header.StreamID].WindowSize, header.Length); + channels[header.StreamID] = channels[header.StreamID] with { WindowSize = header.Length }; } if ((header.Flags & YamuxHeaderFlags.Fin) == YamuxHeaderFlags.Fin) @@ -158,26 +144,13 @@ protected override async Task ConnectAsync(IChannel channel, IChannelFactory? ch continue; } - IChannel? upChannel = state.Channel; - if (upChannel is null) - { - continue; - } - - _ = upChannel.WriteEofAsync(); + _ = state.Channel?.WriteEofAsync(); _logger?.LogDebug("Stream {stream id}: Finish receiving", header.StreamID); - - if (upChannel.GetAwaiter().IsCompleted) - { - channels.Remove(header.StreamID); - _logger?.LogDebug("Stream {stream id}: Closed", header.StreamID); - } } if ((header.Flags & YamuxHeaderFlags.Rst) == YamuxHeaderFlags.Rst) { _ = channels[header.StreamID].Channel?.CloseAsync(); - channels.Remove(header.StreamID); _logger?.LogDebug("Stream {stream id}: Reset", header.StreamID); } } @@ -193,7 +166,7 @@ async Task ActivateUpchannel(int streamId, YamuxHeaderFlags initiationFlag, ICha bool isListenerChannel = isListener ^ (streamId % 2 == 0); - _logger?.LogDebug("Create chan for Stream {stream id} isListener = {1}", streamId, isListenerChannel); + _logger?.LogDebug("Stream {stream id}: Create up channel, {mode}", streamId, isListenerChannel ? "listen" : "dial"); IChannel upChannel; if (isListenerChannel) @@ -210,6 +183,13 @@ async Task ActivateUpchannel(int streamId, YamuxHeaderFlags initiationFlag, ICha channels[streamId] = new(upChannel, channelRequest); TaskCompletionSource? tcs = channels[streamId].Request?.CompletionSource; + upChannel.GetAwaiter().OnCompleted(() => + { + tcs?.SetResult(); + channels.Remove(streamId); + _logger?.LogDebug("Stream {stream id}: Closed", streamId); + }); + try { await WriteHeaderAsync(channel, @@ -220,6 +200,15 @@ await WriteHeaderAsync(channel, StreamID = streamId }); + if (initiationFlag == YamuxHeaderFlags.Syn) + { + _logger?.LogDebug("Stream {stream id}: New stream request sent", streamId); + } + else + { + _logger?.LogDebug("Stream {stream id}: New stream request acknowledged", streamId); + } + await foreach (var upData in upChannel.ReadAllAsync()) { _logger?.LogDebug("Stream {stream id}: Receive from upchannel, length={length}", streamId, upData.Length); @@ -246,11 +235,6 @@ await WriteHeaderAsync(channel, StreamID = streamId }); _logger?.LogDebug("Stream {stream id}: Upchannel finished writing", streamId); - - await upChannel; - channels.Remove(streamId); - - _logger?.LogDebug("Stream {stream id}: Closed", streamId); } catch (Exception e) { @@ -266,10 +250,6 @@ await WriteHeaderAsync(channel, _logger?.LogDebug("Stream {stream id}: Unexpected error, closing: {error}", streamId, e.Message); } - finally - { - tcs?.SetResult(); - } } } catch (Exception ex) From d340713e471a0a6f37d2b13268ac091a85744908 Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Thu, 18 Apr 2024 17:17:44 +0300 Subject: [PATCH 07/14] More improvements --- .../Libp2p.Protocols.IpTcp/IpTcpProtocol.cs | 2 +- .../Libp2p.Protocols.Noise/NoiseProtocol.cs | 1 + .../Libp2p.Protocols.Yamux/YamuxProtocol.cs | 29 +++++++++++-------- src/samples/chat/ChatProtocol.cs | 6 ++-- 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs b/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs index 0b8d1d17..be6254cc 100644 --- a/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs @@ -19,7 +19,7 @@ public class IpTcpProtocol(ILoggerFactory? loggerFactory = null) : IProtocol public async Task ListenAsync(IChannel __, IChannelFactory? channelFactory, IPeerContext context) { - if(channelFactory is null) + if (channelFactory is null) { throw new Exception("Protocol is not properly instantiated"); } diff --git a/src/libp2p/Libp2p.Protocols.Noise/NoiseProtocol.cs b/src/libp2p/Libp2p.Protocols.Noise/NoiseProtocol.cs index b79bf87b..b143109d 100644 --- a/src/libp2p/Libp2p.Protocols.Noise/NoiseProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Noise/NoiseProtocol.cs @@ -150,6 +150,7 @@ public async Task ListenAsync(IChannel downChannel, IChannelFactory upChannelFac await ExchangeData(transport, downChannel, upChannel); + _ = upChannel.CloseAsync(); _logger?.LogDebug("Closed"); } diff --git a/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs b/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs index 64ef4ac8..027cfab2 100644 --- a/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs @@ -4,8 +4,7 @@ using Microsoft.Extensions.Logging; using Nethermind.Libp2p.Core; using System.Buffers; -using System.Threading.Channels; -using System.Xml.Linq; +using System.Runtime.CompilerServices; namespace Nethermind.Libp2p.Protocols; @@ -21,20 +20,21 @@ public class YamuxProtocol(ILoggerFactory? loggerFactory = null) : SymmetricProt protected override async Task ConnectAsync(IChannel channel, IChannelFactory? channelFactory, IPeerContext context, bool isListener) { - try + if (channelFactory is null) { - if (channelFactory is null) - { - throw new ArgumentException("ChannelFactory should be available for a muxer", nameof(channelFactory)); - } + throw new ArgumentException("ChannelFactory should be available for a muxer", nameof(channelFactory)); + } - _logger?.LogInformation(isListener ? "Listen" : "Dial"); - int streamIdCounter = isListener ? 2 : 1; + _logger?.LogInformation(isListener ? "Listen" : "Dial"); - Dictionary channels = new(); + TaskAwaiter downChannelAwaiter = channel.GetAwaiter(); - context.Connected(context.RemotePeer); + Dictionary channels = new(); + try + { + int streamIdCounter = isListener ? 2 : 1; + context.Connected(context.RemotePeer); int pingCounter = 0; using Timer timer = new((s) => @@ -57,7 +57,7 @@ protected override async Task ConnectAsync(IChannel channel, IChannelFactory? ch } }); - for (; ; ) + while (!downChannelAwaiter.IsCompleted) { YamuxHeader header = await ReadHeaderAsync(channel); ReadOnlySequence data = default; @@ -257,6 +257,11 @@ await WriteHeaderAsync(channel, await WriteGoAwayAsync(channel, SessionTerminationCode.InternalError); _logger?.LogDebug("Closed with exception {exception}", ex.Message); } + + foreach (ChannelState upChannel in channels.Values) + { + _ = upChannel.Channel?.CloseAsync(); + } } private async Task ReadHeaderAsync(IReader reader, CancellationToken token = default) diff --git a/src/samples/chat/ChatProtocol.cs b/src/samples/chat/ChatProtocol.cs index 0bdf1997..25195249 100644 --- a/src/samples/chat/ChatProtocol.cs +++ b/src/samples/chat/ChatProtocol.cs @@ -14,9 +14,9 @@ protected override async Task ConnectAsync(IChannel channel, IChannelFactory? ch IPeerContext context, bool isListener) { Console.Write("> "); - _ =Task.Run(async () => + _ = Task.Run(async () => { - for (; ;) + for (; ; ) { ReadOnlySequence read = await channel.ReadAsync(0, ReadBlockingMode.WaitAny).OrThrow(); Console.Write(Encoding.UTF8.GetString(read).Replace("\n\n", "\n> ")); @@ -25,7 +25,7 @@ protected override async Task ConnectAsync(IChannel channel, IChannelFactory? ch for (; ; ) { string line = await Reader.ReadLineAsync(); - if(line == "exit") + if (line == "exit") { return; } From 130c94a60a745fed65b16554e21f07805662ab04 Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Sun, 21 Apr 2024 14:33:43 +0300 Subject: [PATCH 08/14] Fix window update --- .../Libp2p.Core/Exceptions/Libp2pException.cs | 21 ++++++++ src/libp2p/Libp2p.Core/MultiplexerSettings.cs | 14 ++++++ .../Libp2p.Core/UnwarpResultExtensions.cs | 9 ++-- .../Libp2p.Protocols.IpTcp/IpTcpProtocol.cs | 31 +++++++----- .../Libp2p.Protocols.Noise/NoiseProtocol.cs | 41 ++++++++++------ .../Libp2p.Protocols.Yamux/YamuxProtocol.cs | 48 ++++++++++++------- .../Libp2p/ServiceProviderExtensions.cs | 1 + src/samples/chat/Program.cs | 8 ++-- src/samples/transport-interop/Dockerfile | 6 +-- src/samples/transport-interop/README.md | 7 +++ 10 files changed, 131 insertions(+), 55 deletions(-) create mode 100644 src/libp2p/Libp2p.Core/Exceptions/Libp2pException.cs create mode 100644 src/libp2p/Libp2p.Core/MultiplexerSettings.cs create mode 100644 src/samples/transport-interop/README.md diff --git a/src/libp2p/Libp2p.Core/Exceptions/Libp2pException.cs b/src/libp2p/Libp2p.Core/Exceptions/Libp2pException.cs new file mode 100644 index 00000000..8774ea59 --- /dev/null +++ b/src/libp2p/Libp2p.Core/Exceptions/Libp2pException.cs @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: MIT + +namespace Nethermind.Libp2p.Core.Exceptions; + +public class Libp2pException : Exception +{ + public Libp2pException(string? message) : base(message) + { + + } + public Libp2pException() : base() + { + + } +} + +public class ChannelClosedException : Libp2pException +{ + +} diff --git a/src/libp2p/Libp2p.Core/MultiplexerSettings.cs b/src/libp2p/Libp2p.Core/MultiplexerSettings.cs new file mode 100644 index 00000000..ef1c3ad3 --- /dev/null +++ b/src/libp2p/Libp2p.Core/MultiplexerSettings.cs @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: MIT + +namespace Nethermind.Libp2p.Core; +public class MultiplexerSettings +{ + public List _multiplexers = []; + + public IEnumerable Multiplexers => _multiplexers; + public void Add(IProtocol multiplexerProtocol) + { + _multiplexers.Add(multiplexerProtocol); + } +} diff --git a/src/libp2p/Libp2p.Core/UnwarpResultExtensions.cs b/src/libp2p/Libp2p.Core/UnwarpResultExtensions.cs index df85780b..381fc347 100644 --- a/src/libp2p/Libp2p.Core/UnwarpResultExtensions.cs +++ b/src/libp2p/Libp2p.Core/UnwarpResultExtensions.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: MIT +using Nethermind.Libp2p.Core.Exceptions; using System.Buffers; namespace Nethermind.Libp2p.Core; @@ -10,24 +11,24 @@ public static async ValueTask OrThrow(this ValueTask self) { if (self.IsCompleted && self.Result != IOResult.Ok) { - throw new Exception($"Unable to write, error: {self.Result}"); + throw new ChannelClosedException(); } var result = await self.AsTask(); if (result != IOResult.Ok) { - throw new Exception($"Unable to write, error: {result}"); + throw new ChannelClosedException(); } } public static async ValueTask> OrThrow(this ValueTask self) { if (self.IsCompleted && self.Result.Result != IOResult.Ok) { - throw new Exception($"Unable to read, error: {self.Result}"); + throw new ChannelClosedException(); } var result = await self.AsTask(); if (result.Result != IOResult.Ok) { - throw new Exception($"Unable to read, error: {result}"); + throw new ChannelClosedException(); } else { diff --git a/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs b/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs index be6254cc..a1329f3e 100644 --- a/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs @@ -17,7 +17,7 @@ public class IpTcpProtocol(ILoggerFactory? loggerFactory = null) : IProtocol public string Id => "ip-tcp"; - public async Task ListenAsync(IChannel __, IChannelFactory? channelFactory, IPeerContext context) + public async Task ListenAsync(IChannel singalingChannel, IChannelFactory? channelFactory, IPeerContext context) { if (channelFactory is null) { @@ -33,6 +33,10 @@ public async Task ListenAsync(IChannel __, IChannelFactory? channelFactory, IPee Socket srv = new(SocketType.Stream, ProtocolType.Tcp); srv.Bind(new IPEndPoint(ipAddress, tcpPort)); srv.Listen(tcpPort); + singalingChannel.GetAwaiter().OnCompleted(() => + { + srv.Close(); + }); IPEndPoint localIpEndpoint = (IPEndPoint)srv.LocalEndPoint!; @@ -65,7 +69,7 @@ await Task.Run(async () => clientContext.RemoteEndpoint = clientContext.RemotePeer.Address = remoteMultiaddress; - IChannel chan = channelFactory.SubListen(clientContext); + IChannel upChannel = channelFactory.SubListen(clientContext); _ = Task.Run(async () => { @@ -79,10 +83,10 @@ await Task.Run(async () => } byte[] buf = new byte[1024]; - int len = await client.ReceiveAsync(buf, SocketFlags.None); - if (len != 0) + int length = await client.ReceiveAsync(buf, SocketFlags.None); + if (length != 0) { - if (await chan.WriteAsync(new ReadOnlySequence(buf.AsMemory()[..len])) != IOResult.Ok) + if ((await upChannel.WriteAsync(new ReadOnlySequence(buf.AsMemory()[..length]))) != IOResult.Ok) { break; } @@ -91,15 +95,14 @@ await Task.Run(async () => } catch (SocketException e) { - - await chan.CloseAsync(); + await upChannel.CloseAsync(); } }); _ = Task.Run(async () => { try { - await foreach (ReadOnlySequence data in chan.ReadAllAsync()) + await foreach (ReadOnlySequence data in upChannel.ReadAllAsync()) { await client.SendAsync(data.ToArray(), SocketFlags.None); } @@ -107,14 +110,14 @@ await Task.Run(async () => catch (SocketException) { _logger?.LogInformation($"Disconnected({context.Id}) due to a socket exception"); - await chan.CloseAsync(); + await upChannel.CloseAsync(); } }); } }); } - public async Task DialAsync(IChannel __, IChannelFactory? channelFactory, IPeerContext context) + public async Task DialAsync(IChannel singalingChannel, IChannelFactory? channelFactory, IPeerContext context) { if (channelFactory is null) { @@ -132,11 +135,16 @@ public async Task DialAsync(IChannel __, IChannelFactory? channelFactory, IPeerC try { await client.ConnectAsync(new IPEndPoint(ipAddress, tcpPort)); + singalingChannel.GetAwaiter().OnCompleted(() => + { + client.Close(); + }); } catch (SocketException e) { _logger?.LogDebug($"Failed({context.Id}) to connect {addr}"); _logger?.LogTrace($"Failed with {e.GetType()}: {e.Message}"); + _ = singalingChannel.CloseAsync(); return; } @@ -160,8 +168,6 @@ public async Task DialAsync(IChannel __, IChannelFactory? channelFactory, IPeerC IChannel upChannel = channelFactory.SubDial(context); - //upChannel.OnClosing += (graceful) => upChannel.CloseAsync(graceful); - Task receiveTask = Task.Run(async () => { byte[] buf = new byte[client.ReceiveBufferSize]; @@ -204,5 +210,6 @@ public async Task DialAsync(IChannel __, IChannelFactory? channelFactory, IPeerC }); await Task.WhenAll(receiveTask, sendTask); + _ = upChannel.CloseAsync(); } } diff --git a/src/libp2p/Libp2p.Protocols.Noise/NoiseProtocol.cs b/src/libp2p/Libp2p.Protocols.Noise/NoiseProtocol.cs index b143109d..a3da7a51 100644 --- a/src/libp2p/Libp2p.Protocols.Noise/NoiseProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Noise/NoiseProtocol.cs @@ -12,24 +12,35 @@ using Multiformats.Address.Protocols; using Nethermind.Libp2p.Protocols.Noise.Dto; using PublicKey = Nethermind.Libp2p.Core.Dto.PublicKey; +using Google.Protobuf.Collections; namespace Nethermind.Libp2p.Protocols; /// /// -public class NoiseProtocol(ILoggerFactory? loggerFactory = null) : IProtocol +public class NoiseProtocol(MultiplexerSettings? multiplexerSettings = null, ILoggerFactory? loggerFactory = null) : IProtocol { - private readonly Protocol _protocol = new Protocol( + private readonly Protocol _protocol = new( HandshakePattern.XX, CipherFunction.ChaChaPoly, HashFunction.Sha256 ); private readonly ILogger? _logger = loggerFactory?.CreateLogger(); + private readonly NoiseExtensions _extensions = new NoiseExtensions() + { + StreamMuxers = + { + multiplexerSettings is null || multiplexerSettings.Multiplexers.Any() ? ["na"] : [.. multiplexerSettings.Multiplexers.Select(proto => proto.Id)] + } + }; + public string Id => "/noise"; private const string PayloadSigPrefix = "noise-libp2p-static-key:"; - public async Task DialAsync(IChannel downChannel, IChannelFactory upChannelFactory, IPeerContext context) + public async Task DialAsync(IChannel downChannel, IChannelFactory? upChannelFactory, IPeerContext context) { + ArgumentNullException.ThrowIfNull(upChannelFactory); + KeyPair? clientStatic = KeyPair.Generate(); using HandshakeState? handshakeState = _protocol.Create(true, s: clientStatic.PrivateKey); byte[] buffer = new byte[Protocol.MaxMessageLength]; @@ -64,11 +75,7 @@ public async Task DialAsync(IChannel downChannel, IChannelFactory upChannelFacto { IdentityKey = context.LocalPeer.Identity.PublicKey.ToByteString(), IdentitySig = ByteString.CopyFrom(sig), - Extensions = new NoiseExtensions - { - //StreamMuxers = { "/yamux/1.0.0" } - StreamMuxers = { "na" } - } + Extensions = _extensions }; if (_logger is not null && _logger.IsEnabled(LogLevel.Trace)) @@ -90,10 +97,15 @@ public async Task DialAsync(IChannel downChannel, IChannelFactory upChannelFacto IChannel upChannel = upChannelFactory.SubDial(context); await ExchangeData(transport, downChannel, upChannel); + + _ = upChannel.CloseAsync(); + _logger?.LogDebug("Closed"); } - public async Task ListenAsync(IChannel downChannel, IChannelFactory upChannelFactory, IPeerContext context) + public async Task ListenAsync(IChannel downChannel, IChannelFactory? upChannelFactory, IPeerContext context) { + ArgumentNullException.ThrowIfNull(upChannelFactory); + KeyPair? serverStatic = KeyPair.Generate(); using HandshakeState? handshakeState = _protocol.Create(false, @@ -114,11 +126,7 @@ public async Task ListenAsync(IChannel downChannel, IChannelFactory upChannelFac { IdentityKey = context.LocalPeer.Identity.PublicKey.ToByteString(), IdentitySig = ByteString.CopyFrom(sig), - Extensions = new NoiseExtensions - { - //StreamMuxers = { "/yamux/1.0.0" } - StreamMuxers = { "na" } - } + Extensions = _extensions }; // Send the second handshake message to the client. @@ -208,6 +216,9 @@ private static Task ExchangeData(Transport transport, IChannel downChannel, ICha } }); - return Task.WhenAny(t, t2); + return Task.WhenAny(t, t2).ContinueWith((t) => + { + + }); } } diff --git a/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs b/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs index 027cfab2..68d1ca29 100644 --- a/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs @@ -3,17 +3,24 @@ using Microsoft.Extensions.Logging; using Nethermind.Libp2p.Core; +using Nethermind.Libp2p.Core.Exceptions; using System.Buffers; using System.Runtime.CompilerServices; namespace Nethermind.Libp2p.Protocols; -public class YamuxProtocol(ILoggerFactory? loggerFactory = null) : SymmetricProtocol, IProtocol +public class YamuxProtocol : SymmetricProtocol, IProtocol { private const int HeaderLength = 12; private const int PingDelay = 30_000; - private readonly ILogger? _logger = loggerFactory?.CreateLogger(); + public YamuxProtocol(MultiplexerSettings? multiplexerSettings = null, ILoggerFactory? loggerFactory = null) + { + multiplexerSettings?.Add(this); + _logger = loggerFactory?.CreateLogger(); + } + + private readonly ILogger? _logger; public string Id => "/yamux/1.0.0"; @@ -28,7 +35,6 @@ protected override async Task ConnectAsync(IChannel channel, IChannelFactory? ch _logger?.LogInformation(isListener ? "Listen" : "Dial"); TaskAwaiter downChannelAwaiter = channel.GetAwaiter(); - Dictionary channels = new(); try @@ -39,9 +45,8 @@ protected override async Task ConnectAsync(IChannel channel, IChannelFactory? ch using Timer timer = new((s) => { - int localPingCounter = ++pingCounter; - _ = WriteHeaderAsync(channel, new YamuxHeader { Type = YamuxHeaderType.Ping, Flags = YamuxHeaderFlags.Syn, Length = localPingCounter }); - }, null, 0, PingDelay); + _ = WriteHeaderAsync(channel, new YamuxHeader { Type = YamuxHeaderType.Ping, Flags = YamuxHeaderFlags.Syn, Length = ++pingCounter }); + }, null, PingDelay, PingDelay); _ = Task.Run(() => { @@ -53,7 +58,7 @@ protected override async Task ConnectAsync(IChannel channel, IChannelFactory? ch _logger?.LogDebug("Stream {stream id}: Dialing with protocol {proto}", streamId, request.SubProtocol?.Id); channels[streamId] = new ChannelState { Request = request }; - _ = ActivateUpchannel(streamId, YamuxHeaderFlags.Syn, request); + _ = HandleUpchannel(streamId, YamuxHeaderFlags.Syn, request); } }); @@ -105,7 +110,7 @@ protected override async Task ConnectAsync(IChannel channel, IChannelFactory? ch if (!channels.ContainsKey(header.StreamID)) { channels[header.StreamID] = new(); - _ = ActivateUpchannel(header.StreamID, YamuxHeaderFlags.Ack, null); + _ = HandleUpchannel(header.StreamID, YamuxHeaderFlags.Ack, null); } } catch @@ -119,7 +124,7 @@ protected override async Task ConnectAsync(IChannel channel, IChannelFactory? ch if (header.Length > channels[header.StreamID].WindowSize) { _logger?.LogDebug("Stream {stream id}: Data length > windows size: {length} > {window size}", - header.StreamID, data.Length, channels[header.StreamID].WindowSize); + header.StreamID, header.Length, channels[header.StreamID].WindowSize); await WriteGoAwayAsync(channel, SessionTerminationCode.ProtocolError); return; @@ -133,8 +138,10 @@ protected override async Task ConnectAsync(IChannel channel, IChannelFactory? ch if (header.Type == YamuxHeaderType.WindowUpdate && header.Length != 0) { - _logger?.LogDebug("Stream {stream id}: Window update requested: {old} => {new}", header.StreamID, channels[header.StreamID].WindowSize, header.Length); - channels[header.StreamID] = channels[header.StreamID] with { WindowSize = header.Length }; + int oldSize = channels[header.StreamID].WindowSize; + int newSize = oldSize + header.Length; + _logger?.LogDebug("Stream {stream id}: Window update requested: {old} => {new}", header.StreamID, oldSize, newSize); + channels[header.StreamID].WindowSize = newSize; } if ((header.Flags & YamuxHeaderFlags.Fin) == YamuxHeaderFlags.Fin) @@ -157,7 +164,7 @@ protected override async Task ConnectAsync(IChannel channel, IChannelFactory? ch await WriteGoAwayAsync(channel, SessionTerminationCode.Ok); - async Task ActivateUpchannel(int streamId, YamuxHeaderFlags initiationFlag, IChannelRequest? channelRequest) + async Task HandleUpchannel(int streamId, YamuxHeaderFlags initiationFlag, IChannelRequest? channelRequest) { if (channels[streamId].Channel is not null) { @@ -220,7 +227,7 @@ await WriteHeaderAsync(channel, new YamuxHeader { Type = YamuxHeaderType.Data, - Length = (int)upData.Length, + Length = sendingSize, StreamID = streamId }, upData.Slice(i, sendingSize)); i += sendingSize; @@ -236,6 +243,10 @@ await WriteHeaderAsync(channel, }); _logger?.LogDebug("Stream {stream id}: Upchannel finished writing", streamId); } + catch (ChannelClosedException e) + { + _logger?.LogDebug("Stream {stream id}: Closed due to transport disconnection", streamId); + } catch (Exception e) { await WriteHeaderAsync(channel, @@ -252,6 +263,10 @@ await WriteHeaderAsync(channel, } } } + catch (ChannelClosedException ex) + { + _logger?.LogDebug("Closed due to transport disconnection"); + } catch (Exception ex) { await WriteGoAwayAsync(channel, SessionTerminationCode.InternalError); @@ -279,8 +294,7 @@ private async Task WriteHeaderAsync(IWriter writer, YamuxHeader header, ReadOnly YamuxHeader.ToBytes(headerBuffer, ref header); _logger?.LogTrace("Stream {stream id}: Send type={type} flags={flags} length={length}", header.StreamID, header.Type, header.Flags, header.Length); - await writer.WriteAsync( - data.Length == 0 ? new ReadOnlySequence(headerBuffer) : data.Prepend(headerBuffer)).OrThrow(); + await writer.WriteAsync(data.Length == 0 ? new ReadOnlySequence(headerBuffer) : data.Prepend(headerBuffer)).OrThrow(); } private Task WriteGoAwayAsync(IWriter channel, SessionTerminationCode code) => @@ -291,10 +305,10 @@ private Task WriteGoAwayAsync(IWriter channel, SessionTerminationCode code) => StreamID = 0, }); - private struct ChannelState(IChannel? channel, IChannelRequest? request = default) + private class ChannelState(IChannel? channel = default, IChannelRequest? request = default) { public IChannel? Channel { get; set; } = channel; public IChannelRequest? Request { get; set; } = request; - public int WindowSize { get; set; } = 256_000; + public int WindowSize { get; set; } = 256 * 1024; } } diff --git a/src/libp2p/Libp2p/ServiceProviderExtensions.cs b/src/libp2p/Libp2p/ServiceProviderExtensions.cs index aa4ab79e..bf955f9a 100644 --- a/src/libp2p/Libp2p/ServiceProviderExtensions.cs +++ b/src/libp2p/Libp2p/ServiceProviderExtensions.cs @@ -16,6 +16,7 @@ public static IServiceCollection AddLibp2p(this IServiceCollection services, Fun .AddScoped(sp => (ILibp2pPeerFactoryBuilder)factorySetup(new Libp2pPeerFactoryBuilder(sp))) .AddScoped(sp => sp.GetService()!.Build()) .AddScoped() + .AddScoped() ; } } diff --git a/src/samples/chat/Program.cs b/src/samples/chat/Program.cs index 0d83cacd..8d473daa 100644 --- a/src/samples/chat/Program.cs +++ b/src/samples/chat/Program.cs @@ -34,9 +34,9 @@ ILocalPeer localPeer = peerFactory.Create(localAddr: addrTemplate); - logger.LogInformation("Dialing {0}", remoteAddr); + logger.LogInformation("Dialing {remote}", remoteAddr); IRemotePeer remotePeer = await localPeer.DialAsync(remoteAddr, ts.Token); - + await remotePeer.DialAsync(ts.Token); await remotePeer.DisconnectAsync(); } @@ -52,8 +52,8 @@ IListener listener = await peer.ListenAsync( string.Format(addrTemplate, args.Length > 0 && args[0] == "-sp" ? args[1] : "0"), ts.Token); - logger.LogInformation($"Listener started at {listener.Address}"); - listener.OnConnection += async remotePeer => logger.LogInformation($"A peer connected {remotePeer.Address}"); + logger.LogInformation("Listener started at {address}", listener.Address); + listener.OnConnection += async remotePeer => logger.LogInformation("A peer connected {remote}", remotePeer.Address); Console.CancelKeyPress += delegate { listener.DisconnectAsync(); }; await listener; diff --git a/src/samples/transport-interop/Dockerfile b/src/samples/transport-interop/Dockerfile index d946e323..d4324984 100644 --- a/src/samples/transport-interop/Dockerfile +++ b/src/samples/transport-interop/Dockerfile @@ -2,7 +2,7 @@ FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env WORKDIR /app COPY . ./ -RUN cd ./src/samples/transport-interop && dotnet publish -c Release -o ../../../out +RUN ls && cd ./src/samples/transport-interop && dotnet publish -c Release -o /out FROM mcr.microsoft.com/dotnet/runtime:8.0-jammy WORKDIR /app @@ -12,10 +12,10 @@ RUN apt update -y && \ curl -sSL -O https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb && \ dpkg -i packages-microsoft-prod.deb && \ apt update -y && \ - apt install libmsquic=2.3.4 -y && \ + apt install libmsquic=2.3.5 -y && \ ln -s /usr/lib/x86_64-linux-gnu/libmsquic.so.2 /bin -COPY --from=build-env /app/out . +COPY --from=build-env /out . ENTRYPOINT ["dotnet", "TransportInterop.dll"] diff --git a/src/samples/transport-interop/README.md b/src/samples/transport-interop/README.md new file mode 100644 index 00000000..5a6d6a93 --- /dev/null +++ b/src/samples/transport-interop/README.md @@ -0,0 +1,7 @@ +# Transport interop app + +## Build with docker + +```sh +docker build -f ./src/samples/transport-interop/Dockerfile . +``` From e92bc4d386b60ab1ed5cb1d1f8a3afbdd1b5aeac Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Mon, 22 Apr 2024 13:58:06 +0300 Subject: [PATCH 09/14] More fixes --- .../Libp2p.Protocols.IpTcp/IpTcpProtocol.cs | 9 +- .../Libp2p.Protocols.Ping/PingProtocol.cs | 18 +- .../Libp2p.Protocols.Quic/QuicProtocol.cs | 18 +- .../Libp2p.Protocols.Yamux/YamuxProtocol.cs | 161 +++++++++--------- .../MultiaddressBasedSelectorProtocol.cs | 14 +- src/samples/chat/Program.cs | 2 +- 6 files changed, 119 insertions(+), 103 deletions(-) diff --git a/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs b/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs index a1329f3e..ed9452ce 100644 --- a/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs @@ -135,10 +135,6 @@ public async Task DialAsync(IChannel singalingChannel, IChannelFactory? channelF try { await client.ConnectAsync(new IPEndPoint(ipAddress, tcpPort)); - singalingChannel.GetAwaiter().OnCompleted(() => - { - client.Close(); - }); } catch (SocketException e) { @@ -148,6 +144,11 @@ public async Task DialAsync(IChannel singalingChannel, IChannelFactory? channelF return; } + singalingChannel.GetAwaiter().OnCompleted(() => + { + client.Close(); + }); + IPEndPoint localEndpoint = (IPEndPoint)client.LocalEndPoint!; IPEndPoint remoteEndpoint = (IPEndPoint)client.RemoteEndPoint!; diff --git a/src/libp2p/Libp2p.Protocols.Ping/PingProtocol.cs b/src/libp2p/Libp2p.Protocols.Ping/PingProtocol.cs index 9f50c0e9..ce49759d 100644 --- a/src/libp2p/Libp2p.Protocols.Ping/PingProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Ping/PingProtocol.cs @@ -27,18 +27,20 @@ public PingProtocol(ILoggerFactory? loggerFactory = null) public async Task DialAsync(IChannel channel, IChannelFactory? channelFactory, IPeerContext context) { - byte[] byteArray = new byte[PayloadLength]; - _random.NextBytes(byteArray.AsSpan(0, PayloadLength)); - ReadOnlySequence bytes = new(byteArray); + byte[] ping = new byte[PayloadLength]; + _random.NextBytes(ping.AsSpan(0, PayloadLength)); + ReadOnlySequence bytes = new(ping); _logger?.LogPing(context.RemotePeer.Address); await channel.WriteAsync(bytes); + _logger?.LogTrace("Sent ping: {ping}", Convert.ToHexString(ping)); _logger?.ReadingPong(context.RemotePeer.Address); ReadOnlySequence response = await channel.ReadAsync(PayloadLength, ReadBlockingMode.WaitAll).OrThrow(); + _logger?.LogTrace("Received pong: {ping}", Convert.ToHexString(ping)); _logger?.VerifyingPong(context.RemotePeer.Address); - if (!byteArray[0..PayloadLength].SequenceEqual(response.ToArray())) + if (!ping[0..PayloadLength].SequenceEqual(response.ToArray())) { _logger?.PingFailed(context.RemotePeer.Address); throw new ApplicationException(); @@ -55,14 +57,18 @@ public async Task ListenAsync(IChannel channel, IChannelFactory? channelFactory, while (true) { _logger?.ReadingPing(context.RemotePeer.Address); - ReadResult read = await channel.ReadAsync(PayloadLength, ReadBlockingMode.WaitAll); + ReadResult read = await channel.ReadAsync(PayloadLength, ReadBlockingMode.WaitAny); if (read.Result != IOResult.Ok) { break; } + byte[] ping = read.Data.ToArray(); + _logger?.LogTrace("Received ping: {ping}", Convert.ToHexString(ping)); + _logger?.ReturningPong(context.RemotePeer.Address); - await channel.WriteAsync(read.Data); + await channel.WriteAsync(new ReadOnlySequence(ping)); + _logger?.LogTrace("Sent pong: {ping}", Convert.ToHexString(ping)); } _logger?.PingFinished(context.RemotePeer.Address); diff --git a/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs b/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs index 05c00af3..4f2813f1 100644 --- a/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs @@ -39,9 +39,9 @@ public QuicProtocol(ILoggerFactory? loggerFactory = null) // SslApplicationProtocol.Http3, // webtransport }; - public string Id => "quic"; + public string Id => "quic-v1"; - public async Task ListenAsync(IChannel channel, IChannelFactory? channelFactory, IPeerContext context) + public async Task ListenAsync(IChannel singalingChannel, IChannelFactory? channelFactory, IPeerContext context) { if (channelFactory is null) { @@ -101,6 +101,11 @@ public async Task ListenAsync(IChannel channel, IChannelFactory? channelFactory, _logger?.ReadyToHandleConnections(); context.ListenerReady(); + singalingChannel.GetAwaiter().OnCompleted(() => + { + listener.DisposeAsync(); + }); + for (; ; ) { QuicConnection connection = await listener.AcceptConnectionAsync(); @@ -108,7 +113,7 @@ public async Task ListenAsync(IChannel channel, IChannelFactory? channelFactory, } } - public async Task DialAsync(IChannel channel, IChannelFactory? channelFactory, IPeerContext context) + public async Task DialAsync(IChannel singalingChannel, IChannelFactory? channelFactory, IPeerContext context) { if (channelFactory is null) { @@ -155,10 +160,13 @@ public async Task DialAsync(IChannel channel, IChannelFactory? channelFactory, I QuicConnection connection = await QuicConnection.ConnectAsync(clientConnectionOptions); - - _logger?.Connected(connection.LocalEndPoint, connection.RemoteEndPoint); + singalingChannel.GetAwaiter().OnCompleted(() => + { + connection.CloseAsync(0); + }); + await ProcessStreams(connection, context, channelFactory); } diff --git a/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs b/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs index 68d1ca29..3195007c 100644 --- a/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs @@ -56,9 +56,7 @@ protected override async Task ConnectAsync(IChannel channel, IChannelFactory? ch Interlocked.Add(ref streamIdCounter, 2); _logger?.LogDebug("Stream {stream id}: Dialing with protocol {proto}", streamId, request.SubProtocol?.Id); - channels[streamId] = new ChannelState { Request = request }; - - _ = HandleUpchannel(streamId, YamuxHeaderFlags.Syn, request); + channels[streamId] = CreateUpchannel(streamId, YamuxHeaderFlags.Syn, request); } }); @@ -103,20 +101,19 @@ protected override async Task ConnectAsync(IChannel channel, IChannelFactory? ch continue; } - if ((header.Flags & YamuxHeaderFlags.Syn) == YamuxHeaderFlags.Syn) + if ((header.Flags & YamuxHeaderFlags.Syn) == YamuxHeaderFlags.Syn && !channels.ContainsKey(header.StreamID)) { - try - { - if (!channels.ContainsKey(header.StreamID)) - { - channels[header.StreamID] = new(); - _ = HandleUpchannel(header.StreamID, YamuxHeaderFlags.Ack, null); - } - } - catch + channels[header.StreamID] = CreateUpchannel(header.StreamID, YamuxHeaderFlags.Ack, null); + } + + if (!channels.ContainsKey(header.StreamID)) + { + if (header.Type == YamuxHeaderType.Data && header.Length > 0) { - throw; + await channel.ReadAsync(header.Length); } + _logger?.LogDebug("Stream {stream id}: Ignored for closed stream", header.StreamID); + continue; } if (header is { Type: YamuxHeaderType.Data, Length: not 0 }) @@ -164,13 +161,8 @@ protected override async Task ConnectAsync(IChannel channel, IChannelFactory? ch await WriteGoAwayAsync(channel, SessionTerminationCode.Ok); - async Task HandleUpchannel(int streamId, YamuxHeaderFlags initiationFlag, IChannelRequest? channelRequest) + ChannelState CreateUpchannel(int streamId, YamuxHeaderFlags initiationFlag, IChannelRequest? channelRequest) { - if (channels[streamId].Channel is not null) - { - return; - } - bool isListenerChannel = isListener ^ (streamId % 2 == 0); _logger?.LogDebug("Stream {stream id}: Create up channel, {mode}", streamId, isListenerChannel ? "listen" : "dial"); @@ -183,12 +175,12 @@ async Task HandleUpchannel(int streamId, YamuxHeaderFlags initiationFlag, IChann else { IPeerContext dialContext = context.Fork(); - dialContext.SpecificProtocolRequest = channels[streamId].Request; + dialContext.SpecificProtocolRequest = channelRequest; upChannel = channelFactory.SubDial(dialContext); } - channels[streamId] = new(upChannel, channelRequest); - TaskCompletionSource? tcs = channels[streamId].Request?.CompletionSource; + ChannelState state = new(upChannel, channelRequest); + TaskCompletionSource? tcs = state.Request?.CompletionSource; upChannel.GetAwaiter().OnCompleted(() => { @@ -197,70 +189,75 @@ async Task HandleUpchannel(int streamId, YamuxHeaderFlags initiationFlag, IChann _logger?.LogDebug("Stream {stream id}: Closed", streamId); }); - try + Task.Run(async () => { - await WriteHeaderAsync(channel, - new YamuxHeader - { - Flags = initiationFlag, - Type = YamuxHeaderType.Data, - StreamID = streamId - }); - - if (initiationFlag == YamuxHeaderFlags.Syn) + try { - _logger?.LogDebug("Stream {stream id}: New stream request sent", streamId); + await WriteHeaderAsync(channel, + new YamuxHeader + { + Flags = initiationFlag, + Type = YamuxHeaderType.Data, + StreamID = streamId + }); + + if (initiationFlag == YamuxHeaderFlags.Syn) + { + _logger?.LogDebug("Stream {stream id}: New stream request sent", streamId); + } + else + { + _logger?.LogDebug("Stream {stream id}: New stream request acknowledged", streamId); + } + + await foreach (var upData in upChannel.ReadAllAsync()) + { + _logger?.LogDebug("Stream {stream id}: Receive from upchannel, length={length}", streamId, upData.Length); + + for (int i = 0; i < upData.Length;) + { + int sendingSize = Math.Min((int)upData.Length - i, state.WindowSize); + await WriteHeaderAsync(channel, + new YamuxHeader + { + Type = YamuxHeaderType.Data, + Length = sendingSize, + StreamID = streamId + }, upData.Slice(i, sendingSize)); + i += sendingSize; + } + } + + await WriteHeaderAsync(channel, + new YamuxHeader + { + Flags = YamuxHeaderFlags.Fin, + Type = YamuxHeaderType.WindowUpdate, + StreamID = streamId + }); + _logger?.LogDebug("Stream {stream id}: Upchannel finished writing", streamId); } - else + catch (ChannelClosedException e) { - _logger?.LogDebug("Stream {stream id}: New stream request acknowledged", streamId); + _logger?.LogDebug("Stream {stream id}: Closed due to transport disconnection", streamId); } - - await foreach (var upData in upChannel.ReadAllAsync()) + catch (Exception e) { - _logger?.LogDebug("Stream {stream id}: Receive from upchannel, length={length}", streamId, upData.Length); - - for (int i = 0; i < upData.Length;) - { - int sendingSize = Math.Min((int)upData.Length - i, channels[streamId].WindowSize); - await WriteHeaderAsync(channel, - new YamuxHeader - { - Type = YamuxHeaderType.Data, - Length = sendingSize, - StreamID = streamId - }, upData.Slice(i, sendingSize)); - i += sendingSize; - } + await WriteHeaderAsync(channel, + new YamuxHeader + { + Flags = YamuxHeaderFlags.Rst, + Type = YamuxHeaderType.WindowUpdate, + StreamID = streamId + }); + _ = upChannel.CloseAsync(); + channels.Remove(streamId); + + _logger?.LogDebug("Stream {stream id}: Unexpected error, closing: {error}", streamId, e.Message); } + }); - await WriteHeaderAsync(channel, - new YamuxHeader - { - Flags = YamuxHeaderFlags.Fin, - Type = YamuxHeaderType.WindowUpdate, - StreamID = streamId - }); - _logger?.LogDebug("Stream {stream id}: Upchannel finished writing", streamId); - } - catch (ChannelClosedException e) - { - _logger?.LogDebug("Stream {stream id}: Closed due to transport disconnection", streamId); - } - catch (Exception e) - { - await WriteHeaderAsync(channel, - new YamuxHeader - { - Flags = YamuxHeaderFlags.Rst, - Type = YamuxHeaderType.WindowUpdate, - StreamID = streamId - }); - _ = upChannel.CloseAsync(); - channels.Remove(streamId); - - _logger?.LogDebug("Stream {stream id}: Unexpected error, closing: {error}", streamId, e.Message); - } + return state; } } catch (ChannelClosedException ex) @@ -271,6 +268,7 @@ await WriteHeaderAsync(channel, { await WriteGoAwayAsync(channel, SessionTerminationCode.InternalError); _logger?.LogDebug("Closed with exception {exception}", ex.Message); + _logger?.LogTrace("{stackTrace}", ex.StackTrace); } foreach (ChannelState upChannel in channels.Values) @@ -290,7 +288,10 @@ private async Task ReadHeaderAsync(IReader reader, CancellationToke private async Task WriteHeaderAsync(IWriter writer, YamuxHeader header, ReadOnlySequence data = default) { byte[] headerBuffer = new byte[HeaderLength]; - header.Length = (int)data.Length; + if (header.Type == YamuxHeaderType.Data) + { + header.Length = (int)data.Length; + } YamuxHeader.ToBytes(headerBuffer, ref header); _logger?.LogTrace("Stream {stream id}: Send type={type} flags={flags} length={length}", header.StreamID, header.Type, header.Flags, header.Length); diff --git a/src/libp2p/Libp2p/MultiaddressBasedSelectorProtocol.cs b/src/libp2p/Libp2p/MultiaddressBasedSelectorProtocol.cs index 00f1482e..4e9bfda4 100644 --- a/src/libp2p/Libp2p/MultiaddressBasedSelectorProtocol.cs +++ b/src/libp2p/Libp2p/MultiaddressBasedSelectorProtocol.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +`// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: MIT using Microsoft.Extensions.Logging; @@ -21,17 +21,17 @@ protected override async Task ConnectAsync(IChannel _, IChannelFactory? channelF { IProtocol protocol = null!; // TODO: deprecate quic - if (context.LocalPeer.Address.Has()) + if (context.LocalPeer.Address.Has()) { - throw new ApplicationException("QUIC is not supported. Use QUICv1 instead."); + protocol = channelFactory!.SubProtocols.FirstOrDefault(proto => proto.Id == "quic-v1") ?? throw new ApplicationException("QUICv1 is not supported"); } - else if (context.LocalPeer.Address.Has()) + else if (context.LocalPeer.Address.Has()) { - protocol = channelFactory!.SubProtocols.FirstOrDefault(proto => proto.Id.Contains("quic")) ?? throw new ApplicationException("QUICv1 is not supported"); + protocol = channelFactory!.SubProtocols.FirstOrDefault(proto => proto.Id == "tcp") ?? throw new ApplicationException("TCP is not supported"); } - else if (context.LocalPeer.Address.Has()) + else if (context.LocalPeer.Address.Has()) { - protocol = channelFactory!.SubProtocols.FirstOrDefault(proto => proto.Id.Contains("tcp")) ?? throw new ApplicationException("TCP is not supported"); + throw new ApplicationException("QUIC is not supported. Use QUICv1 instead."); } else { diff --git a/src/samples/chat/Program.cs b/src/samples/chat/Program.cs index 8d473daa..8b0f9129 100644 --- a/src/samples/chat/Program.cs +++ b/src/samples/chat/Program.cs @@ -36,7 +36,7 @@ logger.LogInformation("Dialing {remote}", remoteAddr); IRemotePeer remotePeer = await localPeer.DialAsync(remoteAddr, ts.Token); - + await remotePeer.DialAsync(ts.Token); await remotePeer.DisconnectAsync(); } From feb7ec655e8cc0ac32a26d17b72311a97d6fca0a Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Mon, 22 Apr 2024 14:43:34 +0300 Subject: [PATCH 10/14] Improve quic a bit(still broken) --- .../CertificateHelper.cs | 2 +- .../Libp2p.Protocols.Quic/QuicProtocol.cs | 24 ++++++++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/libp2p/Libp2p.Protocols.Quic/CertificateHelper.cs b/src/libp2p/Libp2p.Protocols.Quic/CertificateHelper.cs index b26502c3..727e79c3 100644 --- a/src/libp2p/Libp2p.Protocols.Quic/CertificateHelper.cs +++ b/src/libp2p/Libp2p.Protocols.Quic/CertificateHelper.cs @@ -69,5 +69,5 @@ public static bool ValidateCertificate(X509Certificate2? certificate, string? pe } private static readonly byte[] SignaturePrefix = "libp2p-tls-handshake:"u8.ToArray(); - private static byte[] ContentToSignFromTlsPublicKey(byte[] keyInfo) => SignaturePrefix.Concat(keyInfo).ToArray(); + private static byte[] ContentToSignFromTlsPublicKey(byte[] keyInfo) => [.. SignaturePrefix, .. keyInfo]; } diff --git a/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs b/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs index 4f2813f1..763e42f5 100644 --- a/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs @@ -11,6 +11,7 @@ using System.Net.Quic; using System.Net.Security; using System.Net.Sockets; +using System.Runtime.CompilerServices; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; //using Nethermind.Libp2p.Protocols.Quic; @@ -100,16 +101,25 @@ public async Task ListenAsync(IChannel singalingChannel, IChannelFactory? channe _logger?.ReadyToHandleConnections(); context.ListenerReady(); + TaskAwaiter signalingWawaiter = singalingChannel.GetAwaiter(); - singalingChannel.GetAwaiter().OnCompleted(() => + signalingWawaiter.OnCompleted(() => { listener.DisposeAsync(); }); - for (; ; ) + while (!signalingWawaiter.IsCompleted) { - QuicConnection connection = await listener.AcceptConnectionAsync(); - _ = ProcessStreams(connection, context.Fork(), channelFactory); + try + { + QuicConnection connection = await listener.AcceptConnectionAsync(); + _ = ProcessStreams(connection, context.Fork(), channelFactory); + } + catch (Exception ex) + { + _logger?.LogDebug("Closed with exception {exception}", ex.Message); + _logger?.LogTrace("{stackTrace}", ex.StackTrace); + } } } @@ -146,8 +156,8 @@ public async Task DialAsync(IChannel singalingChannel, IChannelFactory? channelF LocalEndPoint = localEndpoint, DefaultStreamErrorCode = 0, // Protocol-dependent error code. DefaultCloseErrorCode = 1, // Protocol-dependent error code. - MaxInboundUnidirectionalStreams = 100, - MaxInboundBidirectionalStreams = 100, + MaxInboundUnidirectionalStreams = 256, + MaxInboundBidirectionalStreams = 256, ClientAuthenticationOptions = new SslClientAuthenticationOptions { TargetHost = null, @@ -175,6 +185,8 @@ private static bool VerifyRemoteCertificate(IPeer? remotePeer, X509Certificate c private async Task ProcessStreams(QuicConnection connection, IPeerContext context, IChannelFactory channelFactory, CancellationToken token = default) { + _logger?.LogDebug("New connection to {remote}", connection.RemoteEndPoint); + bool isIP4 = connection.LocalEndPoint.AddressFamily == AddressFamily.InterNetwork; Multiaddress localEndPointMultiaddress = new Multiaddress(); From 618dd126a579ea2c42b1904e3ade64bd70c3d452 Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Tue, 23 Apr 2024 12:00:57 +0300 Subject: [PATCH 11/14] Fix build --- src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs | 2 +- src/libp2p/Libp2p/MultiaddressBasedSelectorProtocol.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs b/src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs index 087951d4..610feca1 100644 --- a/src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs +++ b/src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs @@ -99,7 +99,7 @@ public async Task Test_Protocol_Communication() listenerUpchannelFactory.SubListen(Arg.Any(), Arg.Any()) .Returns(listenerUpChannel); - YamuxProtocol proto = new(new DebugLoggerFactory()); + YamuxProtocol proto = new(loggerFactory: new DebugLoggerFactory()); _ = proto.ListenAsync(listenerDownChannel, listenerUpchannelFactory, listenerPeerContext); diff --git a/src/libp2p/Libp2p/MultiaddressBasedSelectorProtocol.cs b/src/libp2p/Libp2p/MultiaddressBasedSelectorProtocol.cs index 4e9bfda4..3d7da7cc 100644 --- a/src/libp2p/Libp2p/MultiaddressBasedSelectorProtocol.cs +++ b/src/libp2p/Libp2p/MultiaddressBasedSelectorProtocol.cs @@ -1,4 +1,4 @@ -`// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: MIT using Microsoft.Extensions.Logging; From 7718652061820037c41216883f0627004679cf6a Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Tue, 23 Apr 2024 12:17:56 +0300 Subject: [PATCH 12/14] Fix tests --- .../Libp2p.Core.Tests/ReaderWriterTests.cs | 19 ++--- .../MultistreamProtocolTests.cs | 2 + .../YamuxProtocolTests.cs | 76 +------------------ 3 files changed, 15 insertions(+), 82 deletions(-) diff --git a/src/libp2p/Libp2p.Core.Tests/ReaderWriterTests.cs b/src/libp2p/Libp2p.Core.Tests/ReaderWriterTests.cs index 80c90568..9f5a110e 100644 --- a/src/libp2p/Libp2p.Core.Tests/ReaderWriterTests.cs +++ b/src/libp2p/Libp2p.Core.Tests/ReaderWriterTests.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: MIT +using Nethermind.Libp2p.Core.Exceptions; using System.Buffers; namespace Nethermind.Libp2p.Core.Tests; @@ -109,13 +110,13 @@ public async Task Test_ChannelWrites_Eof() Assert.That(await readerWriter.CanReadAsync(), Is.EqualTo(IOResult.Ok)); ReadOnlySequence res1 = await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAll).OrThrow(); - Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.DontWait).OrThrow()); + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.DontWait).OrThrow()); Assert.That(await readerWriter.CanReadAsync(), Is.EqualTo(IOResult.Ended)); - Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAny).OrThrow()); + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAny).OrThrow()); Assert.That(await readerWriter.CanReadAsync(), Is.EqualTo(IOResult.Ended)); - Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAll).OrThrow()); + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAll).OrThrow()); Assert.That(await readerWriter.CanReadAsync(), Is.EqualTo(IOResult.Ended)); } @@ -129,7 +130,7 @@ public async Task Test_ChannelWrites_CannotWriteAfterEof(byte[] toWrite) await readerWriter.WriteEofAsync(); Assert.That(await readerWriter.CanReadAsync(), Is.EqualTo(IOResult.Ended)); - Assert.ThrowsAsync(async () => await readerWriter.WriteAsync(new ReadOnlySequence(toWrite)).OrThrow()); + Assert.ThrowsAsync(async () => await readerWriter.WriteAsync(new ReadOnlySequence(toWrite)).OrThrow()); Assert.That(await readerWriter.CanReadAsync(), Is.EqualTo(IOResult.Ended)); } @@ -148,13 +149,13 @@ public async Task Test_ChannelWrites_CanReadAny() Assert.That(res1, Has.Length.EqualTo(3)); - Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.DontWait).OrThrow()); + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.DontWait).OrThrow()); Assert.That(await readerWriter.CanReadAsync(), Is.EqualTo(IOResult.Ended)); - Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAny).OrThrow()); + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAny).OrThrow()); Assert.That(await readerWriter.CanReadAsync(), Is.EqualTo(IOResult.Ended)); - Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAll).OrThrow()); + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(3, ReadBlockingMode.WaitAll).OrThrow()); Assert.That(await readerWriter.CanReadAsync(), Is.EqualTo(IOResult.Ended)); } @@ -169,7 +170,7 @@ public async Task Test_ChannelWrites_CannotReadAll_OnePacket() await readerWriter.WriteEofAsync(); }); - Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(5, ReadBlockingMode.WaitAll).OrThrow()); + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(5, ReadBlockingMode.WaitAll).OrThrow()); Assert.That(await readerWriter.CanReadAsync(), Is.EqualTo(IOResult.Ended)); } @@ -186,7 +187,7 @@ public async Task Test_ChannelWrites_CannotReadAll_Fragmented() await readerWriter.WriteEofAsync(); }); - Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(5, ReadBlockingMode.WaitAll).OrThrow()); + Assert.ThrowsAsync(async () => await readerWriter.ReadAsync(5, ReadBlockingMode.WaitAll).OrThrow()); Assert.That(await readerWriter.CanReadAsync(), Is.EqualTo(IOResult.Ended)); } } diff --git a/src/libp2p/Libp2p.Protocols.Multistream.Tests/MultistreamProtocolTests.cs b/src/libp2p/Libp2p.Protocols.Multistream.Tests/MultistreamProtocolTests.cs index 62edfb23..0a72fb7b 100644 --- a/src/libp2p/Libp2p.Protocols.Multistream.Tests/MultistreamProtocolTests.cs +++ b/src/libp2p/Libp2p.Protocols.Multistream.Tests/MultistreamProtocolTests.cs @@ -3,6 +3,8 @@ using Nethermind.Libp2p.Protocols; +namespace Libp2p.Protocols.Multistream.Tests; + [TestFixture] [Parallelizable(scope: ParallelScope.All)] public class MultistreamProtocolTests diff --git a/src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs b/src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs index 610feca1..e812d4f2 100644 --- a/src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs +++ b/src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs @@ -11,65 +11,18 @@ namespace Nethermind.Libp2p.Protocols.Noise.Tests; +// TODO: Add tests [TestFixture] public class YamuxProtocolTests { - // implement the following testcases: + // TODO: + // Implement the following test cases: // Establish connection, expect 0 stream // Close connection, expect goaway // Try speak a protocol // Exchange data // Expect error and react to it - [Test] - public async Task Test_Connection_Ackowledged() - { - IChannel downChannel = new TestChannel(); - IChannel downChannelFromProtocolPov = ((TestChannel)downChannel).Reverse(); - IChannelFactory channelFactory = Substitute.For(); - IPeerContext peerContext = Substitute.For(); - - IProtocol? proto1 = Substitute.For(); - proto1.Id.Returns("proto1"); - channelFactory.SubProtocols.Returns(new[] { proto1 }); - channelFactory.SubDialAndBind(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(Task.CompletedTask); - - YamuxProtocol proto = new(); - _ = proto.DialAsync(downChannelFromProtocolPov, channelFactory, peerContext); - await downChannel.WriteLineAsync(proto.Id); - await downChannel.WriteLineAsync("proto1"); - - Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo(proto.Id)); - Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo("proto1")); - channelFactory.Received().SubDialAndBind(downChannelFromProtocolPov, peerContext, proto1); - await downChannel.CloseAsync(); - } - - [Test] - public async Task Test_ConnectionClosed_ForBrokenHandshake() - { - IChannel downChannel = new TestChannel(); - IChannel downChannelFromProtocolPov = ((TestChannel)downChannel).Reverse(); - IChannelFactory channelFactory = Substitute.For(); - IPeerContext peerContext = Substitute.For(); - - IProtocol? proto1 = Substitute.For(); - proto1.Id.Returns("proto1"); - channelFactory.SubProtocols.Returns(new[] { proto1 }); - channelFactory.SubDialAndBind(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(Task.CompletedTask); - - NoiseProtocol proto = new(); - _ = proto.DialAsync(downChannelFromProtocolPov, channelFactory, peerContext); - await downChannel.WriteLineAsync(proto.Id); - await downChannel.WriteLineAsync("proto2"); - - Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo(proto.Id)); - Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo("proto1")); - channelFactory.DidNotReceive().SubDialAndBind(downChannelFromProtocolPov, peerContext, proto1); - } - [Test] public async Task Test_Protocol_Communication() { @@ -113,27 +66,4 @@ public async Task Test_Protocol_Communication() await Task.Delay(1000); } - - - class PingPongProtocol : IProtocol - { - public string Id => throw new NotImplementedException(); - - public async Task DialAsync(IChannel downChannel, IChannelFactory? upChannelFactory, IPeerContext context) - { - const string line = "hello"; - await downChannel.WriteLineAsync(line); - await downChannel.WriteEofAsync(); - string received = await downChannel.ReadLineAsync(); - Assert.That(received, Is.EqualTo(line)); - } - - public async Task ListenAsync(IChannel downChannel, IChannelFactory? upChannelFactory, IPeerContext context) - { - string line = await downChannel.ReadLineAsync(); - ReadOnlySequence? readAfter = await downChannel.ReadAsync(0, ReadBlockingMode.WaitAny).OrThrow(); - Assert.That(readAfter, Is.Null); - await downChannel.WriteLineAsync(line); - } - } } From ef143adab58ccefb682b73b5634b08f73be58b29 Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Tue, 23 Apr 2024 15:52:23 +0300 Subject: [PATCH 13/14] Protocol selector and QUIC fixes --- src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs | 13 +++++++++++-- src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs | 2 +- .../Libp2p/MultiaddressBasedSelectorProtocol.cs | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs b/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs index 763e42f5..a4552a82 100644 --- a/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs @@ -189,7 +189,7 @@ private async Task ProcessStreams(QuicConnection connection, IPeerContext contex bool isIP4 = connection.LocalEndPoint.AddressFamily == AddressFamily.InterNetwork; - Multiaddress localEndPointMultiaddress = new Multiaddress(); + Multiaddress localEndPointMultiaddress = new(); string strLocalEndpointAddress = connection.LocalEndPoint.Address.ToString(); localEndPointMultiaddress = isIP4 ? localEndPointMultiaddress.Add(strLocalEndpointAddress) : localEndPointMultiaddress.Add(strLocalEndpointAddress); localEndPointMultiaddress = localEndPointMultiaddress.Add(connection.LocalEndPoint.Port); @@ -201,7 +201,7 @@ private async Task ProcessStreams(QuicConnection connection, IPeerContext contex IPEndPoint remoteIpEndpoint = connection.RemoteEndPoint!; isIP4 = remoteIpEndpoint.AddressFamily == AddressFamily.InterNetwork; - Multiaddress remoteEndPointMultiaddress = new Multiaddress(); + Multiaddress remoteEndPointMultiaddress = new(); string strRemoteEndpointAddress = remoteIpEndpoint.Address.ToString(); remoteEndPointMultiaddress = isIP4 ? remoteEndPointMultiaddress.Add(strRemoteEndpointAddress) : remoteEndPointMultiaddress.Add(strRemoteEndpointAddress); remoteEndPointMultiaddress = remoteEndPointMultiaddress.Add(remoteIpEndpoint.Port); @@ -232,6 +232,13 @@ private async Task ProcessStreams(QuicConnection connection, IPeerContext contex private void ExchangeData(QuicStream stream, IChannel upChannel, TaskCompletionSource? tcs) { + upChannel.GetAwaiter().OnCompleted(() => + { + stream.Close(); + tcs?.SetResult(); + _logger?.LogDebug("Stream {stream id}: Closed", stream.Id); + }); + _ = Task.Run(async () => { try @@ -240,6 +247,7 @@ private void ExchangeData(QuicStream stream, IChannel upChannel, TaskCompletionS { await stream.WriteAsync(data.ToArray()); } + stream.CompleteWrites(); } catch (SocketException ex) { @@ -261,6 +269,7 @@ private void ExchangeData(QuicStream stream, IChannel upChannel, TaskCompletionS await upChannel.WriteAsync(new ReadOnlySequence(buf.AsMemory()[..len])); } } + await upChannel.WriteEofAsync(); } catch (SocketException ex) { diff --git a/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs b/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs index 3195007c..04c49c13 100644 --- a/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Yamux/YamuxProtocol.cs @@ -197,7 +197,7 @@ await WriteHeaderAsync(channel, new YamuxHeader { Flags = initiationFlag, - Type = YamuxHeaderType.Data, + Type = YamuxHeaderType.WindowUpdate, StreamID = streamId }); diff --git a/src/libp2p/Libp2p/MultiaddressBasedSelectorProtocol.cs b/src/libp2p/Libp2p/MultiaddressBasedSelectorProtocol.cs index 3d7da7cc..113354f6 100644 --- a/src/libp2p/Libp2p/MultiaddressBasedSelectorProtocol.cs +++ b/src/libp2p/Libp2p/MultiaddressBasedSelectorProtocol.cs @@ -27,7 +27,7 @@ protected override async Task ConnectAsync(IChannel _, IChannelFactory? channelF } else if (context.LocalPeer.Address.Has()) { - protocol = channelFactory!.SubProtocols.FirstOrDefault(proto => proto.Id == "tcp") ?? throw new ApplicationException("TCP is not supported"); + protocol = channelFactory!.SubProtocols.FirstOrDefault(proto => proto.Id == "ip-tcp") ?? throw new ApplicationException("TCP is not supported"); } else if (context.LocalPeer.Address.Has()) { From f5d7005844b58ff7a706122cd3e71de0c16bb04f Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Wed, 24 Apr 2024 14:08:16 +0300 Subject: [PATCH 14/14] Clean up usings; 2024 --- .editorconfig | 2 +- .gitmodules | 2 ++ .../DebugLoggerFactory.cs | 2 +- .../Libp2p.Core.TestsBase/TestChannel.cs | 1 - .../Libp2p.Core/Exceptions/Libp2pException.cs | 2 +- src/libp2p/Libp2p.Core/IOResult.cs | 2 +- src/libp2p/Libp2p.Core/MultiplexerSettings.cs | 2 +- .../Libp2p.Core/PeerFactoryBuilderBase.cs | 1 - src/libp2p/Libp2p.Core/ReadBlockingMode.cs | 2 +- src/libp2p/Libp2p.Core/ReadResult.cs | 2 +- src/libp2p/Libp2p.Core/Swarm.cs | 30 ------------------- .../Libp2p.Core/UnwarpResultExtensions.cs | 2 +- .../Libp2p.Protocols.Quic/QuicProtocol.cs | 1 - .../Libp2p.Protocols.Yamux.Tests/Usings.cs | 2 +- .../YamuxProtocolTests.cs | 2 +- .../SessionTerminationCode.cs | 2 +- src/samples/transport-interop/Program.cs | 2 +- 17 files changed, 14 insertions(+), 45 deletions(-) delete mode 100644 src/libp2p/Libp2p.Core/Swarm.cs diff --git a/.editorconfig b/.editorconfig index 54b362a7..936ab4db 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,7 +6,7 @@ root = true [*] charset = utf-8 end_of_line = lf -file_header_template = SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited\nSPDX-License-Identifier: MIT +file_header_template = SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited\nSPDX-License-Identifier: MIT indent_size = 2 indent_style = space insert_final_newline = true diff --git a/.gitmodules b/.gitmodules index 7cbcbb4c..700a54bb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,8 @@ [submodule "src/cs-multihash"] path = src/cs-multihash url = https://github.com/NethermindEth/cs-multihash.git + branch = master [submodule "src/cs-multiaddress"] path = src/cs-multiaddress url = https://github.com/NethermindEth/cs-multiaddress.git + branch = master diff --git a/src/libp2p/Libp2p.Core.TestsBase/DebugLoggerFactory.cs b/src/libp2p/Libp2p.Core.TestsBase/DebugLoggerFactory.cs index 21de3e2b..33bb3000 100644 --- a/src/libp2p/Libp2p.Core.TestsBase/DebugLoggerFactory.cs +++ b/src/libp2p/Libp2p.Core.TestsBase/DebugLoggerFactory.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: MIT using Microsoft.Extensions.Logging; diff --git a/src/libp2p/Libp2p.Core.TestsBase/TestChannel.cs b/src/libp2p/Libp2p.Core.TestsBase/TestChannel.cs index 24affca7..b6154014 100644 --- a/src/libp2p/Libp2p.Core.TestsBase/TestChannel.cs +++ b/src/libp2p/Libp2p.Core.TestsBase/TestChannel.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: MIT -using Newtonsoft.Json.Linq; using System.Buffers; using System.Runtime.CompilerServices; diff --git a/src/libp2p/Libp2p.Core/Exceptions/Libp2pException.cs b/src/libp2p/Libp2p.Core/Exceptions/Libp2pException.cs index 8774ea59..d36b4528 100644 --- a/src/libp2p/Libp2p.Core/Exceptions/Libp2pException.cs +++ b/src/libp2p/Libp2p.Core/Exceptions/Libp2pException.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: MIT namespace Nethermind.Libp2p.Core.Exceptions; diff --git a/src/libp2p/Libp2p.Core/IOResult.cs b/src/libp2p/Libp2p.Core/IOResult.cs index 63879a73..8268b4a1 100644 --- a/src/libp2p/Libp2p.Core/IOResult.cs +++ b/src/libp2p/Libp2p.Core/IOResult.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: MIT namespace Nethermind.Libp2p.Core; diff --git a/src/libp2p/Libp2p.Core/MultiplexerSettings.cs b/src/libp2p/Libp2p.Core/MultiplexerSettings.cs index ef1c3ad3..6c12af8b 100644 --- a/src/libp2p/Libp2p.Core/MultiplexerSettings.cs +++ b/src/libp2p/Libp2p.Core/MultiplexerSettings.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: MIT namespace Nethermind.Libp2p.Core; diff --git a/src/libp2p/Libp2p.Core/PeerFactoryBuilderBase.cs b/src/libp2p/Libp2p.Core/PeerFactoryBuilderBase.cs index 5df4a01c..7aa9d474 100644 --- a/src/libp2p/Libp2p.Core/PeerFactoryBuilderBase.cs +++ b/src/libp2p/Libp2p.Core/PeerFactoryBuilderBase.cs @@ -1,4 +1,3 @@ - // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: MIT diff --git a/src/libp2p/Libp2p.Core/ReadBlockingMode.cs b/src/libp2p/Libp2p.Core/ReadBlockingMode.cs index 9f435246..eed5a66a 100644 --- a/src/libp2p/Libp2p.Core/ReadBlockingMode.cs +++ b/src/libp2p/Libp2p.Core/ReadBlockingMode.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: MIT namespace Nethermind.Libp2p.Core; diff --git a/src/libp2p/Libp2p.Core/ReadResult.cs b/src/libp2p/Libp2p.Core/ReadResult.cs index 94e1251e..8397e786 100644 --- a/src/libp2p/Libp2p.Core/ReadResult.cs +++ b/src/libp2p/Libp2p.Core/ReadResult.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: MIT using System.Buffers; diff --git a/src/libp2p/Libp2p.Core/Swarm.cs b/src/libp2p/Libp2p.Core/Swarm.cs deleted file mode 100644 index 1e1877c8..00000000 --- a/src/libp2p/Libp2p.Core/Swarm.cs +++ /dev/null @@ -1,30 +0,0 @@ -/// -/// Summary description for Class1 -/// -//public class PeerStore -//{ -// class Peer -// { -// public Pubkey PubKey { get; set; } -// public List Addresses { get; set; } -// } - -// public Dictionary Peers { get; set; } = new(); - -//} - -//public class Swarm -//{ -// Task DialAsync(PeerId peerId) where TProtocol : IProtocol; -// Task DialAsync(MultiAddress addr) where TProtocol : IProtocol; - - -// Task DialAsync(PeerId peerId) where TProtocol : IProtocol; -// Task DialAsync(MultiAddress addr) where TProtocol : IProtocol; - -// Task DialAsync(MultiAddress addr, params MultiAddress[] altAddresses) where TProtocol : IProtocol; - -// Task ListenAsync(PeerId peerId); -// IAsyncEnumerable<> ListAsync(CancellationToken token) where TProtocol : where TProtocol : IProtocol; -//} - diff --git a/src/libp2p/Libp2p.Core/UnwarpResultExtensions.cs b/src/libp2p/Libp2p.Core/UnwarpResultExtensions.cs index 381fc347..0b795d98 100644 --- a/src/libp2p/Libp2p.Core/UnwarpResultExtensions.cs +++ b/src/libp2p/Libp2p.Core/UnwarpResultExtensions.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: MIT using Nethermind.Libp2p.Core.Exceptions; diff --git a/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs b/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs index fdabe804..cda729c2 100644 --- a/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs +++ b/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs @@ -15,7 +15,6 @@ using System.Runtime.Versioning; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; -//using Nethermind.Libp2p.Protocols.Quic; namespace Nethermind.Libp2p.Protocols; diff --git a/src/libp2p/Libp2p.Protocols.Yamux.Tests/Usings.cs b/src/libp2p/Libp2p.Protocols.Yamux.Tests/Usings.cs index e160e2a1..5fece5e6 100644 --- a/src/libp2p/Libp2p.Protocols.Yamux.Tests/Usings.cs +++ b/src/libp2p/Libp2p.Protocols.Yamux.Tests/Usings.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: MIT global using NUnit.Framework; diff --git a/src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs b/src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs index e812d4f2..305ea3e8 100644 --- a/src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs +++ b/src/libp2p/Libp2p.Protocols.Yamux.Tests/YamuxProtocolTests.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: MIT using Microsoft.Extensions.Logging; diff --git a/src/libp2p/Libp2p.Protocols.Yamux/SessionTerminationCode.cs b/src/libp2p/Libp2p.Protocols.Yamux/SessionTerminationCode.cs index 80556f38..7b30909d 100644 --- a/src/libp2p/Libp2p.Protocols.Yamux/SessionTerminationCode.cs +++ b/src/libp2p/Libp2p.Protocols.Yamux/SessionTerminationCode.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: MIT namespace Nethermind.Libp2p.Protocols; diff --git a/src/samples/transport-interop/Program.cs b/src/samples/transport-interop/Program.cs index 3858d389..a477a74d 100644 --- a/src/samples/transport-interop/Program.cs +++ b/src/samples/transport-interop/Program.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: MIT using Microsoft.Extensions.DependencyInjection;