diff --git a/README.md b/README.md
index 6060a6c3..0e99a461 100644
--- a/README.md
+++ b/README.md
@@ -31,36 +31,41 @@ dotnet test
## Roadmap
-From the beginning, the target is to provide a performant well-tested implementation that works on multiple platforms, with high throughput and low memory profile.
-See the [milestones](https://github.com/NethermindEth/dotnet-libp2p/milestones?direction=asc&sort=due_date&state=open).
+🚧 The library is not stable and under heavy development. Consider the [beta](https://github.com/NethermindEth/dotnet-libp2p/milestone/5) milestone as a reflection of readiness for production 🚧
+
+The target is to provide a performant well-tested implementation of a wide range of protocols that works on multiple platforms, with high throughput and low memory profile.
+
| Protocol | Version | Status |
|--------------------|--------------------|-----------------|
-| **Transports**
| TCP | tcp | ✅ |
-| QUIC | quic | 🚧 |
-| | quic-v1 | 🚧 |
-| **Protocols**
+| QUIC | quic-v1 | ✅ |
| multistream-select | /multistream/1.0.0 | ✅ |
| plaintext | /plaintext/2.0.0 | ✅ |
| noise | /noise | ✅ |
+| tls | /tls/1.0.0 | ⬜ help wanted |
+| WebTransport | | ⬜ help wanted |
| yamux | /yamux/1.0.0 | ✅ |
+| Circuit Relay | /libp2p/circuit/relay/0.2.0/* | ⬜ help wanted |
+| hole punching | | ⬜ help wanted |
+| **Application layer**
| Identify | /ipfs/id/1.0.0 | ✅ |
| ping | /ipfs/ping/1.0.0 | ✅ |
| pubsub | /floodsub/1.0.0 | ✅ |
| | /meshsub/1.0.0 | ✅ |
| | /meshsub/1.1.0 | 🚧 |
| | /meshsub/1.2.0 | ⬜ |
-| Circuit Relay | /libp2p/circuit/relay/0.2.0/* | ⬜ help wanted |
| **Discovery**
| mDns | basic | ✅ |
| | DNS-SD | 🚧 |
-| [discv5](https://github.com/Pier-Two/Lantern.Discv5) | 5.1 | ⬜ help wanted |
+| [discv5](https://github.com/Pier-Two/Lantern.Discv5) | 5.1 | 🚧 help wanted |
⬜ - not yet implemented
🚧 - work in progress
✅ - basic support implemented
+No plans for: mplex, quic(draft-29)
+
## License
dotnet-libp2p is an open-source software licensed under the [MIT](https://github.com/nethermindeth/dotnet-libp2p/blob/main/LICENSE).
diff --git a/docs/development/transport-layer.md b/docs/development/transport-layer.md
index 07975763..4dfced2b 100644
--- a/docs/development/transport-layer.md
+++ b/docs/development/transport-layer.md
@@ -21,7 +21,7 @@ Each protocol should implement `IProtocol` interface, which enforces the `Listen
```csharp
namespace Nethermind.Libp2p.Core;
-public abstract class MyCustomProtocol
+public class MyCustomProtocol : IProtocol
{
public Task DialAsync(IChannel downChannel, IChannelFactory upChannelFactory, IPeerContext context)
{
diff --git a/src/libp2p/Directory.Packages.props b/src/libp2p/Directory.Packages.props
index 932aec45..48268273 100644
--- a/src/libp2p/Directory.Packages.props
+++ b/src/libp2p/Directory.Packages.props
@@ -30,4 +30,4 @@
-
\ No newline at end of file
+
diff --git a/src/libp2p/Libp2p.Core.Tests/IdentityTests.cs b/src/libp2p/Libp2p.Core.Tests/IdentityTests.cs
index a1e3f2f7..0060940b 100644
--- a/src/libp2p/Libp2p.Core.Tests/IdentityTests.cs
+++ b/src/libp2p/Libp2p.Core.Tests/IdentityTests.cs
@@ -3,17 +3,18 @@
using Google.Protobuf;
using Nethermind.Libp2p.Core.Dto;
+
namespace Nethermind.Libp2p.Core.Tests;
public class IdentityTests
{
- [TestCaseSource(nameof(ExpectedKeysEncoded))]
+ [TestCaseSource(nameof(KeysEncoded))]
public void Test_KeyEncoding(byte[] privateKey, byte[] publicKey)
{
Identity identity = new(PrivateKey.Parser.ParseFrom(privateKey));
Assert.That(identity.PublicKey.ToByteArray(), Is.EquivalentTo(publicKey));
}
- public static IEnumerable ExpectedKeysEncoded()
+ public static IEnumerable KeysEncoded()
{
yield return new TestCaseData(
Convert.FromHexString("08031279307702010104203E5B1FE9712E6C314942A750BD67485DE3C1EFE85B1BFB520AE8F9AE3DFA4A4CA00A06082A8648CE3D030107A14403420004DE3D300FA36AE0E8F5D530899D83ABAB44ABF3161F162A4BC901D8E6ECDA020E8B6D5F8DA30525E71D6851510C098E5C47C646A597FB4DCEC034E9F77C409E62"),
@@ -36,4 +37,22 @@ public static IEnumerable ExpectedKeysEncoded()
)
{ TestName = "Rsa" };
}
+
+ [TestCaseSource(nameof(KeyTypes))]
+ public void Test_KeyGeneration(KeyType keyType)
+ => _ = new Identity(keyType: keyType);
+
+ public static IEnumerable KeyTypes()
+ => Enum.GetValues().Select(kt => new TestCaseData(kt) { TestName = kt.ToString() });
+
+ [TestCaseSource(nameof(KeyTypes))]
+ public void Test_Signing(KeyType keyType)
+ {
+ Identity id = new(keyType: keyType);
+ byte[] message = Enumerable.Range(0, 100).Select(i => (byte)i).ToArray();
+
+ byte[] signature = id.Sign(message);
+
+ Assert.That(id.VerifySignature(message, signature), Is.True);
+ }
}
diff --git a/src/libp2p/Libp2p.Core.TestsBase/TestPeers.cs b/src/libp2p/Libp2p.Core.TestsBase/TestPeers.cs
index 01ccc8af..4bb9d81f 100644
--- a/src/libp2p/Libp2p.Core.TestsBase/TestPeers.cs
+++ b/src/libp2p/Libp2p.Core.TestsBase/TestPeers.cs
@@ -22,6 +22,6 @@ public static Multiaddr Multiaddr(int i) => testPeerAddrs.GetOrAdd(i, i =>
public static PeerId PeerId(Multiaddr addr) => new(addr.At(Enums.Multiaddr.P2p)!);
- public static Identity Identity(Multiaddr addr) => new Identity(Core.PeerId.ExtractPublicKey(PeerId(addr).Bytes));
+ public static Identity Identity(Multiaddr addr) => new(Core.PeerId.ExtractPublicKey(PeerId(addr).Bytes));
}
diff --git a/src/libp2p/Libp2p.Core/Identity.cs b/src/libp2p/Libp2p.Core/Identity.cs
index 8bc07e1e..1c0d4c94 100644
--- a/src/libp2p/Libp2p.Core/Identity.cs
+++ b/src/libp2p/Libp2p.Core/Identity.cs
@@ -10,6 +10,10 @@
using System.Security.Cryptography;
using System.Buffers;
using Org.BouncyCastle.Crypto.Parameters;
+using ECPoint = Org.BouncyCastle.Math.EC.ECPoint;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.EC;
+using Org.BouncyCastle.Crypto.Generators;
namespace Nethermind.Libp2p.Core;
@@ -18,28 +22,79 @@ namespace Nethermind.Libp2p.Core;
///
public class Identity
{
+ private const KeyType DefaultKeyType = KeyType.Ed25519;
+
public PublicKey PublicKey { get; }
public PrivateKey? PrivateKey { get; }
- public Identity(byte[]? privateKey = default, KeyType keyType = KeyType.Ed25519)
- : this(privateKey is null ? null : new PrivateKey { Data = ByteString.CopyFrom(privateKey), Type = keyType })
+ public Identity(byte[]? privateKey = default, KeyType keyType = DefaultKeyType)
{
+ if (privateKey is null)
+ {
+ (PrivateKey, PublicKey) = GeneratePrivateKeyPair(keyType);
+ }
+ else
+ {
+ PrivateKey = new PrivateKey { Data = ByteString.CopyFrom(privateKey), Type = keyType };
+ PublicKey = GetPublicKey(PrivateKey);
+ }
}
- public Identity(PrivateKey? privateKey)
+ public Identity(PrivateKey privateKey)
{
- if (privateKey is null)
+ PrivateKey = privateKey;
+ PublicKey = GetPublicKey(PrivateKey);
+ }
+
+ private (PrivateKey, PublicKey) GeneratePrivateKeyPair(KeyType type)
+ {
+ ByteString privateKeyData;
+ ByteString? publicKeyData = null;
+ switch (type)
{
- byte[] rented = ArrayPool.Shared.Rent(Ed25519.SecretKeySize);
- Span privateKeyBytesSpan = rented.AsSpan(0, Ed25519.SecretKeySize);
- SecureRandom rnd = new();
- Ed25519.GeneratePrivateKey(rnd, privateKeyBytesSpan);
- ArrayPool.Shared.Return(rented, true);
- privateKey = new PrivateKey { Data = ByteString.CopyFrom(privateKeyBytesSpan), Type = KeyType.Ed25519 };
+ case KeyType.Ed25519:
+ {
+ Span privateKeyBytes = stackalloc byte[Ed25519.SecretKeySize];
+ SecureRandom rnd = new();
+ Ed25519.GeneratePrivateKey(rnd, privateKeyBytes);
+ privateKeyData = ByteString.CopyFrom(privateKeyBytes);
+ }
+ break;
+ case KeyType.Rsa:
+ {
+ using RSA rsa = RSA.Create(1024);
+ privateKeyData = ByteString.CopyFrom(rsa.ExportRSAPrivateKey());
+ }
+ break;
+ case KeyType.Secp256K1:
+ {
+ X9ECParameters curve = ECNamedCurveTable.GetByName("secp256k1");
+ ECDomainParameters domainParams = new(curve);
+
+ SecureRandom secureRandom = new();
+ ECKeyGenerationParameters keyParams = new(domainParams, secureRandom);
+
+ ECKeyPairGenerator generator = new("ECDSA");
+ generator.Init(keyParams);
+ AsymmetricCipherKeyPair keyPair = generator.GenerateKeyPair();
+ Span privateKeySpan = stackalloc byte[32];
+ ((ECPrivateKeyParameters)keyPair.Private).D.ToByteArrayUnsigned(privateKeySpan);
+ privateKeyData = ByteString.CopyFrom(privateKeySpan);
+ publicKeyData = ByteString.CopyFrom(((ECPublicKeyParameters)keyPair.Public).Q.GetEncoded(true));
+ }
+ break;
+ case KeyType.Ecdsa:
+ {
+ using ECDsa rsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
+ privateKeyData = ByteString.CopyFrom(rsa.ExportECPrivateKey());
+ }
+ break;
+ default:
+ throw new NotImplementedException($"{type} generation is not supported");
}
- PrivateKey = privateKey;
- PublicKey = GetPublicKey(privateKey);
+ PrivateKey privateKey = new() { Type = type, Data = privateKeyData };
+ return (privateKey, publicKeyData is not null ? new PublicKey { Type = type, Data = publicKeyData } : GetPublicKey(privateKey));
}
public Identity(PublicKey publicKey)
@@ -64,7 +119,7 @@ private static PublicKey GetPublicKey(PrivateKey privateKey)
case KeyType.Rsa:
{
- RSA rsa = RSA.Create();
+ using RSA rsa = RSA.Create();
rsa.ImportRSAPrivateKey(privateKey.Data.Span, out int bytesRead);
publicKeyData = ByteString.CopyFrom(rsa.ExportSubjectPublicKeyInfo());
}
@@ -72,18 +127,17 @@ private static PublicKey GetPublicKey(PrivateKey privateKey)
case KeyType.Secp256K1:
{
- X9ECParameters curve = ECNamedCurveTable.GetByName("secp256k1");
- Org.BouncyCastle.Math.EC.ECPoint pointQ
- = curve.G.Multiply(new BigInteger(1, privateKey.Data.Span));
+ X9ECParameters curve = CustomNamedCurves.GetByName("secp256k1");
+ ECPoint pointQ = curve.G.Multiply(new BigInteger(privateKey.Data.ToArray()));
publicKeyData = ByteString.CopyFrom(pointQ.GetEncoded(true));
}
break;
case KeyType.Ecdsa:
{
- ECDsa rsa = ECDsa.Create();
- rsa.ImportECPrivateKey(privateKey.Data.Span, out int _);
- publicKeyData = ByteString.CopyFrom(rsa.ExportSubjectPublicKeyInfo());
+ using ECDsa ecdsa = ECDsa.Create();
+ ecdsa.ImportECPrivateKey(privateKey.Data.Span, out int _);
+ publicKeyData = ByteString.CopyFrom(ecdsa.ExportSubjectPublicKeyInfo());
}
break;
default:
@@ -93,5 +147,99 @@ Org.BouncyCastle.Math.EC.ECPoint pointQ
return new() { Type = privateKey.Type, Data = publicKeyData };
}
+ public bool VerifySignature(byte[] message, byte[] signature)
+ {
+ if (PublicKey is null)
+ {
+ throw new ArgumentNullException(nameof(PublicKey));
+ }
+
+ switch (PublicKey.Type)
+ {
+ case KeyType.Ed25519:
+ {
+ return Ed25519.Verify(signature, 0, PublicKey.Data.ToByteArray(), 0, message, 0, message.Length);
+ }
+ case KeyType.Rsa:
+ {
+ using RSA rsa = RSA.Create();
+ rsa.ImportSubjectPublicKeyInfo(PublicKey.Data.Span, out _);
+ return rsa.VerifyData(message, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
+ }
+ case KeyType.Secp256K1:
+ {
+ X9ECParameters curve = CustomNamedCurves.GetByName("secp256k1");
+ ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA");
+
+ ECPublicKeyParameters publicKeyParamters = new(
+ "ECDSA",
+ curve.Curve.DecodePoint(PublicKey.Data.ToArray()),
+ new ECDomainParameters(curve)
+ );
+
+ signer.Init(false, publicKeyParamters);
+ signer.BlockUpdate(message, 0, message.Length);
+ return signer.VerifySignature(signature);
+ }
+ case KeyType.Ecdsa:
+ {
+ using ECDsa ecdsa = ECDsa.Create();
+ ecdsa.ImportSubjectPublicKeyInfo(PublicKey.Data.Span, out _);
+ return ecdsa.VerifyData(message, signature, HashAlgorithmName.SHA256, DSASignatureFormat.Rfc3279DerSequence);
+ }
+ default:
+ throw new NotImplementedException($"{PublicKey.Type} is not supported");
+ }
+ }
+
+ public byte[] Sign(byte[] message)
+ {
+ if (PrivateKey is null)
+ {
+ throw new ArgumentException(nameof(PrivateKey));
+ }
+
+ switch (PublicKey.Type)
+ {
+ case KeyType.Ed25519:
+ {
+ byte[] sig = new byte[Ed25519.SignatureSize];
+ Ed25519.Sign(PrivateKey.Data.ToByteArray(), 0, PublicKey.Data.ToByteArray(), 0,
+ message, 0, message.Length, sig, 0);
+ return sig;
+ }
+ case KeyType.Ecdsa:
+ {
+ ECDsa e = ECDsa.Create();
+ e.ImportECPrivateKey(PrivateKey.Data.Span, out _);
+ return e.SignData(message, HashAlgorithmName.SHA256,
+ DSASignatureFormat.Rfc3279DerSequence);
+ }
+ case KeyType.Rsa:
+ {
+ using RSA rsa = RSA.Create();
+ rsa.ImportRSAPrivateKey(PrivateKey.Data.Span, out _);
+ return rsa.SignData(message, 0, message.Length, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
+ }
+ case KeyType.Secp256K1:
+ {
+ X9ECParameters curve = CustomNamedCurves.GetByName("secp256k1");
+ ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA");
+
+ ECPrivateKeyParameters privateKeyParams = new(
+ "ECDSA",
+ new BigInteger(1, PrivateKey.Data.ToArray()),
+ new ECDomainParameters(curve)
+ );
+
+ signer.Init(true, privateKeyParams);
+ signer.BlockUpdate(message, 0, message.Length);
+ return signer.GenerateSignature();
+ }
+ default:
+ throw new NotImplementedException($"{PublicKey.Type} is not supported");
+ }
+ }
+
public PeerId PeerId => new(PublicKey);
}
diff --git a/src/libp2p/Libp2p.Core/Multiaddr.cs b/src/libp2p/Libp2p.Core/Multiaddr.cs
index 5b5cec3b..f6d6ac79 100644
--- a/src/libp2p/Libp2p.Core/Multiaddr.cs
+++ b/src/libp2p/Libp2p.Core/Multiaddr.cs
@@ -47,8 +47,8 @@ public override string ToString()
return string.Join("", _segments
.Select(s =>
s.Parameter is null
- ? new object[] { $"/{s.Type.ToString().ToLower()}" }
- : new object[] { $"/{s.Type.ToString().ToLower()}", $"/{s.Parameter}" })
+ ? new object[] { $"/{ToString(s.Type)}" }
+ : new object[] { $"/{ToString(s.Type)}", $"/{s.Parameter}" })
.SelectMany(s => s));
}
@@ -59,7 +59,7 @@ public static Multiaddr From(params object[] segments)
{
if (segments[i] is not Enums.Multiaddr addr)
{
- throw new ArgumentException($"{segments[i]} is expceted to be a multiaddress segment id");
+ throw new ArgumentException($"{segments[i]} is expected to be a multiaddress segment id");
}
if (ToProto(addr).isParametrized)
@@ -149,6 +149,8 @@ private static (Enums.Multiaddr type, bool isParametrized) ToProto(string val)
"udp" => ToProto(Enums.Multiaddr.Udp),
"p2p" => ToProto(Enums.Multiaddr.P2p),
"ws" => ToProto(Enums.Multiaddr.Ws),
+ "quic" => ToProto(Enums.Multiaddr.Quic),
+ "quic-v1" => ToProto(Enums.Multiaddr.QuicV1),
_ => ToProto(Enums.Multiaddr.Unknown)
};
}
@@ -157,12 +159,23 @@ private static (Enums.Multiaddr type, bool isParametrized) ToProto(Enums.Multiad
{
return val switch
{
+ Enums.Multiaddr.Quic => (val, false),
+ Enums.Multiaddr.QuicV1 => (val, false),
Enums.Multiaddr.Ws => (val, false),
Enums.Multiaddr.Unknown => (val, false),
_ => (val, true)
};
}
+ private static string ToString(Enums.Multiaddr type)
+ {
+ return type switch
+ {
+ Enums.Multiaddr.QuicV1 => "quic-v1",
+ _ => type.ToString().ToLower(),
+ };
+ }
+
public byte[] ToByteArray()
{
Span result = stackalloc byte[256];
diff --git a/src/libp2p/Libp2p.Core/PeerFactory.cs b/src/libp2p/Libp2p.Core/PeerFactory.cs
index bf5ac268..4dc8adef 100644
--- a/src/libp2p/Libp2p.Core/PeerFactory.cs
+++ b/src/libp2p/Libp2p.Core/PeerFactory.cs
@@ -100,11 +100,9 @@ protected virtual async Task DialAsync(LocalPeer peer, Multiaddr ad
Id = $"ctx-{++CtxId}",
LocalPeer = peer,
};
- RemotePeer result = new(this, peer, context) { Address = addr };
+ RemotePeer result = new(this, peer, context) { Address = addr, Channel = chan };
context.RemotePeer = result;
- _ = _protocol.DialAsync(chan, _upChannelFactory, context);
- result.Channel = chan;
TaskCompletionSource tcs = new();
context.OnRemotePeerConnection += remotePeer =>
{
@@ -115,8 +113,10 @@ protected virtual async Task DialAsync(LocalPeer peer, Multiaddr ad
ConnectedTo(remotePeer, true).ContinueWith((t) => { tcs.TrySetResult(true); });
};
- await tcs.Task;
+ _ = _protocol.DialAsync(chan, _upChannelFactory, context);
+
+ await tcs.Task;
return result;
}
catch
diff --git a/src/libp2p/Libp2p.Core/PeerFactoryBuilderBase.cs b/src/libp2p/Libp2p.Core/PeerFactoryBuilderBase.cs
index bf73982d..dd5e1786 100644
--- a/src/libp2p/Libp2p.Core/PeerFactoryBuilderBase.cs
+++ b/src/libp2p/Libp2p.Core/PeerFactoryBuilderBase.cs
@@ -99,7 +99,7 @@ public ProtocolStack Or(TProtocol? instance = default) where TProtoco
public ProtocolStack Over(ProtocolStack stack)
{
- var rootProto = stack.Root ?? stack;
+ PeerFactoryBuilderBase.ProtocolStack rootProto = stack.Root ?? stack;
TopProtocols.Add(rootProto);
if (PrevSwitch != null)
diff --git a/src/libp2p/Libp2p.Core/PeerId.cs b/src/libp2p/Libp2p.Core/PeerId.cs
index 32a804d2..29c0a303 100644
--- a/src/libp2p/Libp2p.Core/PeerId.cs
+++ b/src/libp2p/Libp2p.Core/PeerId.cs
@@ -6,7 +6,6 @@
using SimpleBase;
using Google.Protobuf;
using Multiformats.Base;
-using System.Buffers;
using Nethermind.Libp2p.Core.Enums;
using Multihash = Multiformats.Hash.Multihash;
diff --git a/src/libp2p/Libp2p.Generators.Enums/EnumsGenerator.cs b/src/libp2p/Libp2p.Generators.Enums/EnumsGenerator.cs
index 8f12f253..f6dc256e 100644
--- a/src/libp2p/Libp2p.Generators.Enums/EnumsGenerator.cs
+++ b/src/libp2p/Libp2p.Generators.Enums/EnumsGenerator.cs
@@ -44,7 +44,6 @@ public void Execute(GeneratorExecutionContext context)
IEnumerable> grouped =
vals.GroupBy(x => x.Tag);
- Console.WriteLine();
foreach (IGrouping g in grouped)
{
diff --git a/src/libp2p/Libp2p.Generators.Protobuf/ProtobufGenerator.cs b/src/libp2p/Libp2p.Generators.Protobuf/ProtobufGenerator.cs
index 5401360b..9eb42d36 100644
--- a/src/libp2p/Libp2p.Generators.Protobuf/ProtobufGenerator.cs
+++ b/src/libp2p/Libp2p.Generators.Protobuf/ProtobufGenerator.cs
@@ -16,7 +16,7 @@ public void Execute(GeneratorExecutionContext context)
{
try
{
- foreach (var file in context.AdditionalFiles)
+ foreach (AdditionalText file in context.AdditionalFiles)
{
Process cmd = new();
cmd.StartInfo.RedirectStandardError = true;
@@ -32,7 +32,7 @@ public void Execute(GeneratorExecutionContext context)
string errorLogs = cmd.StandardError.ReadToEnd();
throw new ApplicationException(errorLogs);
}
- var output = cmd.StandardOutput.ReadToEnd();
+ string output = cmd.StandardOutput.ReadToEnd();
}
}
catch (Exception e)
diff --git a/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs b/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs
index 5e65bb11..719eea7c 100644
--- a/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs
+++ b/src/libp2p/Libp2p.Protocols.IpTcp/IpTcpProtocol.cs
@@ -69,7 +69,7 @@ await Task.Run(async () =>
remoteIpEndpoint.Port);
clientContext.LocalPeer.Address = context.LocalPeer.Address.Replace(
context.LocalEndpoint.Has(Core.Enums.Multiaddr.Ip4) ? Core.Enums.Multiaddr.Ip4 : Core.Enums.Multiaddr.Ip6, newIpProtocol,
- remoteIpEndpoint.Address.ToString())
+ localIpEndpoint.Address.ToString())
.Replace(
Core.Enums.Multiaddr.Tcp,
remoteIpEndpoint.Port.ToString());
diff --git a/src/libp2p/Libp2p.Protocols.MDns/MDnsDiscoveryProtocol.cs b/src/libp2p/Libp2p.Protocols.MDns/MDnsDiscoveryProtocol.cs
index 80d4fbfc..0b011ba9 100644
--- a/src/libp2p/Libp2p.Protocols.MDns/MDnsDiscoveryProtocol.cs
+++ b/src/libp2p/Libp2p.Protocols.MDns/MDnsDiscoveryProtocol.cs
@@ -107,7 +107,7 @@ private static string RandomString(int length)
return string.Create(length, Random.Shared,
(chars, rand) =>
{
- for (var i = 0; i < chars.Length; i++)
+ for (int i = 0; i < chars.Length; i++)
chars[i] = alphabet[rand.Next(0, alphabet.Length)];
});
}
diff --git a/src/libp2p/Libp2p.Protocols.Multistream.Tests/MultistreamProtocolTests.cs b/src/libp2p/Libp2p.Protocols.Multistream.Tests/MultistreamProtocolTests.cs
index 86a1b96c..9a83824d 100644
--- a/src/libp2p/Libp2p.Protocols.Multistream.Tests/MultistreamProtocolTests.cs
+++ b/src/libp2p/Libp2p.Protocols.Multistream.Tests/MultistreamProtocolTests.cs
@@ -65,6 +65,7 @@ public async Task Test_ConnectionEstablished_AfterHandshake_With_SpecificRequest
Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo(proto.Id));
Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo("proto1"));
+ await Task.Delay(30);
_ = channelFactory.Received().SubDialAndBind(downChannelFromProtocolPov, peerContext, proto1);
await downChannel.CloseAsync();
}
@@ -127,6 +128,8 @@ public async Task Test_ConnectionEstablished_ForAnyOfProtocols()
Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo(proto.Id));
Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo(proto1.Id));
Assert.That(await downChannel.ReadLineAsync(), Is.EqualTo(proto2.Id));
+
+ await Task.Delay(30);
_ = channelFactory.Received().SubDialAndBind(downChannelFromProtocolPov, peerContext, proto2);
await upChannel.CloseAsync();
}
diff --git a/src/libp2p/Libp2p.Protocols.Pubsub.Tests/FloodsubProtocolTests.cs b/src/libp2p/Libp2p.Protocols.Pubsub.Tests/FloodsubProtocolTests.cs
index 938b6572..732f1063 100644
--- a/src/libp2p/Libp2p.Protocols.Pubsub.Tests/FloodsubProtocolTests.cs
+++ b/src/libp2p/Libp2p.Protocols.Pubsub.Tests/FloodsubProtocolTests.cs
@@ -1,7 +1,6 @@
// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
// SPDX-License-Identifier: MIT
-using Nethermind.Libp2p.Core;
using Nethermind.Libp2p.Protocols.Pubsub;
using Nethermind.Libp2p.Protocols.Pubsub.Dto;
diff --git a/src/libp2p/Libp2p.Protocols.Pubsub.Tests/GossipsubProtocolTests.cs b/src/libp2p/Libp2p.Protocols.Pubsub.Tests/GossipsubProtocolTests.cs
index b8ed140f..d137bccf 100644
--- a/src/libp2p/Libp2p.Protocols.Pubsub.Tests/GossipsubProtocolTests.cs
+++ b/src/libp2p/Libp2p.Protocols.Pubsub.Tests/GossipsubProtocolTests.cs
@@ -28,7 +28,7 @@ public async Task Test_New_messages_are_sent_to_mesh_only()
Assert.That(state.FloodsubPeers.Keys, Has.Member(commonTopic));
Assert.That(state.GossipsubPeers.Keys, Has.Member(commonTopic));
- foreach (var index in Enumerable.Range(1, peerCount))
+ foreach (int index in Enumerable.Range(1, peerCount))
{
Multiaddr discoveredPeer = TestPeers.Multiaddr(index);
PeerId peerId = TestPeers.PeerId(index);
diff --git a/src/libp2p/Libp2p.Protocols.Pubsub/PubsubRouter.cs b/src/libp2p/Libp2p.Protocols.Pubsub/PubsubRouter.cs
index b53efcef..530b4548 100644
--- a/src/libp2p/Libp2p.Protocols.Pubsub/PubsubRouter.cs
+++ b/src/libp2p/Libp2p.Protocols.Pubsub/PubsubRouter.cs
@@ -134,6 +134,7 @@ private async Task StartDiscoveryAsync(IDiscoveryProtocol discoveryProtocol, Can
ObservableCollection col = new();
discoveryProtocol.OnAddPeer = (addrs) =>
{
+ //addrs = addrs.Where(x => x.ToString().Contains("127.0.0.1")).ToArray();
Dictionary cancellations = new();
foreach (Multiaddr addr in addrs)
{
@@ -356,7 +357,7 @@ public void Publish(string topicId, byte[] message)
ulong seqNo = this.seqNo++;
byte[] seqNoBytes = new byte[8];
BinaryPrimitives.WriteUInt64BigEndian(seqNoBytes, seqNo);
- Rpc rpc = new Rpc().WithMessages(topicId, seqNo, LocalPeerId.Bytes, message, localPeer.Identity.PrivateKey.Data.ToArray());
+ Rpc rpc = new Rpc().WithMessages(topicId, seqNo, LocalPeerId.Bytes, message, localPeer.Identity);
foreach (PeerId peerId in fPeers[topicId])
{
diff --git a/src/libp2p/Libp2p.Protocols.Pubsub/RpcExtensions.cs b/src/libp2p/Libp2p.Protocols.Pubsub/RpcExtensions.cs
index 821d5b64..9ca82ce8 100644
--- a/src/libp2p/Libp2p.Protocols.Pubsub/RpcExtensions.cs
+++ b/src/libp2p/Libp2p.Protocols.Pubsub/RpcExtensions.cs
@@ -5,7 +5,6 @@
using Org.BouncyCastle.Math.EC.Rfc8032;
using System.Buffers.Binary;
using System.Text;
-using Multiformats.Hash;
using Nethermind.Libp2p.Core.Dto;
using Nethermind.Libp2p.Core;
using Nethermind.Libp2p.Protocols.Pubsub.Dto;
@@ -17,9 +16,8 @@ internal static class RpcExtensions
{
private const string SignaturePayloadPrefix = "libp2p-pubsub:";
- public static Rpc WithMessages(this Rpc rpc, string topic, ulong seqNo, byte[] from, byte[] message, byte[] privateKey)
+ public static Rpc WithMessages(this Rpc rpc, string topic, ulong seqNo, byte[] from, byte[] message, Identity identity)
{
-
Message msg = new();
msg.Topic = topic;
Span seqNoBytes = new byte[8];
@@ -28,13 +26,11 @@ public static Rpc WithMessages(this Rpc rpc, string topic, ulong seqNo, byte[] f
msg.From = ByteString.CopyFrom(from);
msg.Data = ByteString.CopyFrom(message);
- byte[] msgToSign = Encoding.UTF8.GetBytes(SignaturePayloadPrefix)
+ byte[] signingContent = Encoding.UTF8.GetBytes(SignaturePayloadPrefix)
.Concat(msg.ToByteArray())
.ToArray();
- byte[] sig = new byte[64];
- Ed25519.Sign(privateKey, 0, msgToSign, 0, msgToSign.Length, sig, 0);
- msg.Signature = ByteString.CopyFrom(sig);
+ msg.Signature = ByteString.CopyFrom(identity.Sign(signingContent));
rpc.Publish.Add(msg);
return rpc;
}
diff --git a/src/libp2p/Libp2p.Protocols.Pubsub/TtlCache.cs b/src/libp2p/Libp2p.Protocols.Pubsub/TtlCache.cs
index db9d14c4..f11c9c24 100644
--- a/src/libp2p/Libp2p.Protocols.Pubsub/TtlCache.cs
+++ b/src/libp2p/Libp2p.Protocols.Pubsub/TtlCache.cs
@@ -26,7 +26,7 @@ public TtlCache(int ttl)
await Task.Delay(5_000);
DateTimeOffset now = DateTimeOffset.UtcNow;
TKey[] keys = items.TakeWhile(i => i.Value.ValidTill < now).Select(i => i.Key).ToArray();
- foreach (var keyToRemove in keys)
+ foreach (TKey keyToRemove in keys)
{
items.Remove(keyToRemove);
}
diff --git a/src/libp2p/Libp2p.Protocols.Quic.Tests/CertificateTests.cs b/src/libp2p/Libp2p.Protocols.Quic.Tests/CertificateTests.cs
new file mode 100644
index 00000000..8f5d16c6
--- /dev/null
+++ b/src/libp2p/Libp2p.Protocols.Quic.Tests/CertificateTests.cs
@@ -0,0 +1,49 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: MIT
+
+using Nethermind.Libp2p.Core;
+using Nethermind.Libp2p.Core.Dto;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+
+namespace Nethermind.Libp2p.Protocols.Quic.Tests;
+
+public class CertificateTests
+{
+ [TestCaseSource(nameof(CertificatesSerialized))]
+ public bool Test_CertificateDeserialization(byte[] certificateBytes, string peerId) =>
+ CertificateHelper.ValidateCertificate(new X509Certificate2(certificateBytes), peerId);
+
+ public static IEnumerable CertificatesSerialized()
+ {
+ yield return new TestCaseData(
+ Convert.FromHexString("308201773082011ea003020102020900f5bd0debaa597f52300a06082a8648ce3d04030230003020170d3735303130313030303030305a180f34303936303130313030303030305a30003059301306072a8648ce3d020106082a8648ce3d030107034200046bf9871220d71dcb3483ecdfcbfcc7c103f8509d0974b3c18ab1f1be1302d643103a08f7a7722c1b247ba3876fe2c59e26526f479d7718a85202ddbe47562358a37f307d307b060a2b0601040183a25a01010101ff046a30680424080112207fda21856709c5ae12fd6e8450623f15f11955d384212b89f56e7e136d2e17280440aaa6bffabe91b6f30c35e3aa4f94b1188fed96b0ffdd393f4c58c1c047854120e674ce64c788406d1c2c4b116581fd7411b309881c3c7f20b46e54c7e6fe7f0f300a06082a8648ce3d040302034700304402207d1a1dbd2bda235ff2ec87daf006f9b04ba076a5a5530180cd9c2e8f6399e09d0220458527178c7e77024601dbb1b256593e9b96d961b96349d1f560114f61a87595"),
+ "12D3KooWJRSrypvnpHgc6ZAgyCni4KcSmbV7uGRaMw5LgMKT18fq"
+ )
+ { TestName = "Valid ED25519", ExpectedResult = true };
+ yield return new TestCaseData(
+ Convert.FromHexString("308201c030820166a003020102020900eaf419a6e3edb4a6300a06082a8648ce3d04030230003020170d3735303130313030303030305a180f34303936303130313030303030305a30003059301306072a8648ce3d020106082a8648ce3d030107034200048dbf1116c7c608d6d5292bd826c3feb53483a89fce434bf64538a359c8e07538ff71f6766239be6a146dcc1a5f3bb934bcd4ae2ae1d4da28ac68b4a20593f06ba381c63081c33081c0060a2b0601040183a25a01010101ff0481ae3081ab045f0803125b3059301306072a8648ce3d020106082a8648ce3d0301070342000484b93fa456a74bd0153919f036db7bc63c802f055bc7023395d0203de718ee0fc7b570b767cdd858aca6c7c4113ff002e78bd2138ac1a3b26dde3519e06979ad04483046022100bc84014cea5a41feabdf4c161096564b9ccf4b62fbef4fe1cd382c84e11101780221009204f086a84cb8ed8a9ddd7868dc90c792ee434adf62c66f99a08a5eba11615b300a06082a8648ce3d0403020348003045022054b437be9a2edf591312d68ff24bf91367ad4143f76cf80b5658f232ade820da022100e23b48de9df9c25d4c83ddddf75d2676f0b9318ee2a6c88a736d85eab94a912f"),
+ "QmZcrvr3r4S3QvwFdae3c2EWTfo792Y14UpzCZurhmiWeX"
+ )
+ { TestName = "Valid ECDSA", ExpectedResult = true };
+ yield return new TestCaseData(
+ Convert.FromHexString("3082018230820128a003020102020900f3b305f55622cfdf300a06082a8648ce3d04030230003020170d3735303130313030303030305a180f34303936303130313030303030305a30003059301306072a8648ce3d020106082a8648ce3d0301070342000458f7e9581748ff9bdd933b655cc0e5552a1248f840658cc221dec2186b5a2fe4641b86ab7590a3422cdbb1000cf97662f27e5910d7569f22feed8829c8b52e0fa38188308185308182060a2b0601040183a25a01010101ff0471306f042508021221026b053094d1112bce799dc8026040ae6d4eb574157929f1598172061f753d9b1b04463044022040712707e97794c478d93989aaa28ae1f71c03af524a8a4bd2d98424948a782302207b61b7f074b696a25fb9e0059141a811cccc4cc28042d9301b9b2a4015e87470300a06082a8648ce3d04030203480030450220143ae4d86fdc8675d2480bb6912eca5e39165df7f572d836aa2f2d6acfab13f8022100831d1979a98f0c4a6fb5069ca374de92f1a1205c962a6d90ad3d7554cb7d9df4"),
+ "16Uiu2HAm2dSCBFxuge46aEt7U1oejtYuBUZXxASHqmcfVmk4gsbx"
+ )
+ { TestName = "Valid secp256k1", ExpectedResult = true };
+ yield return new TestCaseData(
+ Convert.FromHexString("308201773082011da003020102020830a73c5d896a1109300a06082a8648ce3d04030230003020170d3735303130313030303030305a180f34303936303130313030303030305a30003059301306072a8648ce3d020106082a8648ce3d03010703420004bbe62df9a7c1c46b7f1f21d556deec5382a36df146fb29c7f1240e60d7d5328570e3b71d99602b77a65c9b3655f62837f8d66b59f1763b8c9beba3be07778043a37f307d307b060a2b0601040183a25a01010101ff046a3068042408011220ec8094573afb9728088860864f7bcea2d4fd412fef09a8e2d24d482377c20db60440ecabae8354afa2f0af4b8d2ad871e865cb5a7c0c8d3dbdbf42de577f92461a0ebb0a28703e33581af7d2a4f2270fc37aec6261fcc95f8af08f3f4806581c730a300a06082a8648ce3d040302034800304502202dfb17a6fa0f94ee0e2e6a3b9fb6e986f311dee27392058016464bd130930a61022100ba4b937a11c8d3172b81e7cd04aedb79b978c4379c2b5b24d565dd5d67d3cb3c"),
+ "12D3KooWRja6riywsP8bE7V2gGg55Jsx7HrHLQcEwxvmwD8SQynV"
+ )
+ { TestName = "Invalid", ExpectedResult = false };
+ foreach (KeyType keyType in Enum.GetValues())
+ {
+ Identity id = new(null, keyType);
+ yield return new TestCaseData(
+ CertificateHelper.CertificateFromIdentity(ECDsa.Create(), id).GetRawCertData(),
+ id.PeerId.ToString()
+ )
+ { TestName = $"Roundtrip valid {keyType} key", ExpectedResult = true };
+ }
+ }
+}
diff --git a/src/libp2p/Libp2p.Protocols.Quic.Tests/Libp2p.Protocols.Quic.Tests.csproj b/src/libp2p/Libp2p.Protocols.Quic.Tests/Libp2p.Protocols.Quic.Tests.csproj
new file mode 100644
index 00000000..c3346f24
--- /dev/null
+++ b/src/libp2p/Libp2p.Protocols.Quic.Tests/Libp2p.Protocols.Quic.Tests.csproj
@@ -0,0 +1,29 @@
+
+
+
+ net7.0
+ enable
+ enable
+ true
+ Nethermind.$(MSBuildProjectName.Replace(" ", "_"))
+ Nethermind.$(MSBuildProjectName)
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/libp2p/Libp2p.Protocols.Quic.Tests/Usings.cs b/src/libp2p/Libp2p.Protocols.Quic.Tests/Usings.cs
new file mode 100644
index 00000000..e160e2a1
--- /dev/null
+++ b/src/libp2p/Libp2p.Protocols.Quic.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.Quic/CertificateHelper.cs b/src/libp2p/Libp2p.Protocols.Quic/CertificateHelper.cs
new file mode 100644
index 00000000..b26502c3
--- /dev/null
+++ b/src/libp2p/Libp2p.Protocols.Quic/CertificateHelper.cs
@@ -0,0 +1,73 @@
+// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
+// SPDX-License-Identifier: MIT
+
+using Google.Protobuf;
+using Nethermind.Libp2p.Core;
+using System.Formats.Asn1;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+
+namespace Nethermind.Libp2p.Protocols.Quic;
+public class CertificateHelper
+{
+ private const string PubkeyExtensionOidString = "1.3.6.1.4.1.53594.1.1";
+ private static readonly Oid PubkeyExtensionOid = new(PubkeyExtensionOidString);
+
+ public static X509Certificate CertificateFromIdentity(ECDsa sessionKey, Identity identity)
+ {
+ byte[] signature = identity.Sign(ContentToSignFromTlsPublicKey(sessionKey.ExportSubjectPublicKeyInfo()));
+
+ AsnWriter asnWrtier = new(AsnEncodingRules.DER);
+ asnWrtier.PushSequence();
+ asnWrtier.WriteOctetString(identity.PublicKey.ToByteArray());
+ asnWrtier.WriteOctetString(signature);
+ asnWrtier.PopSequence();
+ byte[] pubkeyExtension = new byte[asnWrtier.GetEncodedLength()];
+ asnWrtier.Encode(pubkeyExtension);
+
+ CertificateRequest certRequest = new("", sessionKey, HashAlgorithmName.SHA256);
+ certRequest.CertificateExtensions.Add(new X509Extension(PubkeyExtensionOid, pubkeyExtension, true));
+
+ return certRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.MaxValue);
+ }
+
+ public static bool ValidateCertificate(X509Certificate2? certificate, string? peerId)
+ {
+ if (certificate is null)
+ {
+ return false;
+ }
+
+ X509Extension[] exts = certificate.Extensions.Where(e => e.Oid?.Value == PubkeyExtensionOidString).ToArray();
+
+ if (exts.Length is 0)
+ {
+ return false;
+ }
+
+ if (exts.Length is not 1)
+ {
+ return false;
+ }
+
+ X509Extension ext = exts.First();
+
+ AsnReader a = new(ext.RawData, AsnEncodingRules.DER);
+ AsnReader signedKey = a.ReadSequence();
+
+ byte[] publicKey = signedKey.ReadOctetString();
+ byte[] signature = signedKey.ReadOctetString();
+
+ Core.Dto.PublicKey key = Core.Dto.PublicKey.Parser.ParseFrom(publicKey);
+ Identity id = new(key);
+ if (peerId is not null && id.PeerId.ToString() != peerId)
+ {
+ return false;
+ }
+
+ return id.VerifySignature(ContentToSignFromTlsPublicKey(certificate.PublicKey.ExportSubjectPublicKeyInfo()), signature);
+ }
+
+ private static readonly byte[] SignaturePrefix = "libp2p-tls-handshake:"u8.ToArray();
+ private static byte[] ContentToSignFromTlsPublicKey(byte[] keyInfo) => SignaturePrefix.Concat(keyInfo).ToArray();
+}
diff --git a/src/libp2p/Libp2p.Protocols.Quic/Libp2p.Protocols.Quic.csproj b/src/libp2p/Libp2p.Protocols.Quic/Libp2p.Protocols.Quic.csproj
index 4a949953..5c03cbaf 100644
--- a/src/libp2p/Libp2p.Protocols.Quic/Libp2p.Protocols.Quic.csproj
+++ b/src/libp2p/Libp2p.Protocols.Quic/Libp2p.Protocols.Quic.csproj
@@ -21,6 +21,7 @@
+
all
diff --git a/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs b/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs
index 60555dfc..91aa2184 100644
--- a/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs
+++ b/src/libp2p/Libp2p.Protocols.Quic/QuicProtocol.cs
@@ -3,297 +3,244 @@
using Nethermind.Libp2p.Core;
using Microsoft.Extensions.Logging;
+using System.Buffers;
+using System.Net.Sockets;
+using MultiaddrEnum = Nethermind.Libp2p.Core.Enums.Multiaddr;
+using System.Net;
+using System.Net.Quic;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
+using Nethermind.Libp2p.Protocols.Quic;
+using System.Security.Cryptography;
namespace Nethermind.Libp2p.Protocols;
-// TODO: Rewrite with SocketAsyncEventArgs
+#pragma warning disable CA1416 // Do not inform about platform compatibility
+#pragma warning disable CA2252 // EnablePreviewFeatures is set in the project, but build still fails
public class QuicProtocol : IProtocol
{
private readonly ILogger? _logger;
+ private readonly ECDsa _sessionKey;
public QuicProtocol(ILoggerFactory? loggerFactory = null)
{
_logger = loggerFactory?.CreateLogger();
+ _sessionKey = ECDsa.Create();
}
+ private static readonly List protocols = new()
+ {
+ new SslApplicationProtocol("libp2p"),
+ // SslApplicationProtocol.Http3, // webtransport
+ };
+
public string Id => "quic";
- public Task ListenAsync(IChannel channel, IChannelFactory? channelFactory, IPeerContext context)
+ public async Task ListenAsync(IChannel channel, IChannelFactory? channelFactory, IPeerContext context)
+ {
+ if (channelFactory is null)
+ {
+ throw new ArgumentException($"The protocol requires {nameof(channelFactory)}");
+ }
+
+ if (!QuicListener.IsSupported)
+ {
+ throw new NotSupportedException("QUIC is not supported, check for presence of libmsquic and support of TLS 1.3.");
+ }
+
+ Multiaddr addr = context.LocalPeer.Address;
+ MultiaddrEnum ipProtocol = addr.Has(MultiaddrEnum.Ip4) ? MultiaddrEnum.Ip4 : MultiaddrEnum.Ip6;
+ IPAddress ipAddress = IPAddress.Parse(addr.At(ipProtocol)!);
+ int udpPort = int.Parse(addr.At(MultiaddrEnum.Udp)!);
+
+ IPEndPoint localEndpoint = new(ipAddress, udpPort);
+
+ QuicServerConnectionOptions serverConnectionOptions = new()
+ {
+ DefaultStreamErrorCode = 0, // Protocol-dependent error code.
+ DefaultCloseErrorCode = 1, // Protocol-dependent error code.
+
+ ServerAuthenticationOptions = new SslServerAuthenticationOptions
+ {
+ ApplicationProtocols = protocols,
+ RemoteCertificateValidationCallback = (_, c, _, _) => VerifyRemoteCertificate(context.RemotePeer, c),
+ ServerCertificate = CertificateHelper.CertificateFromIdentity(_sessionKey, context.LocalPeer.Identity)
+ },
+ };
+
+ QuicListener listener = await QuicListener.ListenAsync(new QuicListenerOptions
+ {
+ ListenEndPoint = localEndpoint,
+ ApplicationProtocols = protocols,
+ ConnectionOptionsCallback = (_, _, _) => ValueTask.FromResult(serverConnectionOptions)
+ });
+
+ channel.OnClose(async () =>
+ {
+ await listener.DisposeAsync();
+ });
+
+ while (!channel.IsClosed)
+ {
+ QuicConnection connection = await listener.AcceptConnectionAsync(channel.Token);
+ _ = ProcessStreams(connection, context.Fork(), channelFactory, channel.Token);
+ }
+ }
+
+ public async Task DialAsync(IChannel channel, IChannelFactory? channelFactory, IPeerContext context)
+ {
+ if (channelFactory is null)
+ {
+ throw new ArgumentException($"The protocol requires {nameof(channelFactory)}");
+ }
+
+ if (!QuicConnection.IsSupported)
+ {
+ throw new NotSupportedException("QUIC is not supported, check for presence of libmsquic and support of TLS 1.3.");
+ }
+
+ Multiaddr addr = context.LocalPeer.Address;
+ MultiaddrEnum ipProtocol = addr.Has(MultiaddrEnum.Ip4) ? MultiaddrEnum.Ip4 : MultiaddrEnum.Ip6;
+ IPAddress ipAddress = IPAddress.Parse(addr.At(ipProtocol)!);
+ int udpPort = int.Parse(addr.At(MultiaddrEnum.Udp)!);
+
+ IPEndPoint localEndpoint = new(ipAddress, udpPort);
+
+
+ addr = context.RemotePeer.Address;
+ ipProtocol = addr.Has(MultiaddrEnum.Ip4) ? MultiaddrEnum.Ip4 : MultiaddrEnum.Ip6;
+ ipAddress = IPAddress.Parse(addr.At(ipProtocol)!);
+ udpPort = int.Parse(addr.At(MultiaddrEnum.Udp)!);
+
+ IPEndPoint remoteEndpoint = new(ipAddress, udpPort);
+
+ QuicClientConnectionOptions clientConnectionOptions = new()
+ {
+ LocalEndPoint = localEndpoint,
+ DefaultStreamErrorCode = 0, // Protocol-dependent error code.
+ DefaultCloseErrorCode = 1, // Protocol-dependent error code.
+ MaxInboundUnidirectionalStreams = 100,
+ MaxInboundBidirectionalStreams = 100,
+ ClientAuthenticationOptions = new SslClientAuthenticationOptions
+ {
+ ApplicationProtocols = protocols,
+ RemoteCertificateValidationCallback = (_, c, _, _) => VerifyRemoteCertificate(context.RemotePeer, c),
+ ClientCertificates = new X509CertificateCollection { CertificateHelper.CertificateFromIdentity(_sessionKey, context.LocalPeer.Identity) },
+ },
+ RemoteEndPoint = remoteEndpoint,
+ };
+
+ QuicConnection connection = await QuicConnection.ConnectAsync(clientConnectionOptions);
+
+ channel.OnClose(async () =>
+ {
+ await connection.CloseAsync(0);
+ await connection.DisposeAsync();
+ });
+
+ _logger?.LogDebug($"Connected {connection.LocalEndPoint} --> {connection.RemoteEndPoint}");
+
+ await ProcessStreams(connection, context, channelFactory, channel.Token);
+ }
+
+ private static bool VerifyRemoteCertificate(IPeer? remotePeer, X509Certificate certificate) =>
+ CertificateHelper.ValidateCertificate(certificate as X509Certificate2, remotePeer?.Address.At(MultiaddrEnum.P2p));
+
+ private async Task ProcessStreams(QuicConnection connection, IPeerContext context, IChannelFactory channelFactory, CancellationToken token)
{
- throw new NotImplementedException();
- //MultiAddr addr = context.LocalPeer.Address;
- //Multiaddr ipProtocol = addr.Has(Multiaddr.Ip4) ? Multiaddr.Ip4 : Multiaddr.Ip6;
- //IPAddress ipAddress = IPAddress.Parse(addr.At(ipProtocol)!);
- //int tcpPort = int.Parse(addr.At(Multiaddr.Udp)!);
-
- //// First, check if QUIC is supported.
- //if (!QuicConnection.IsSupported)
- //{
- // Console.WriteLine("QUIC is not supported, check for presence of libmsquic and support of TLS 1.3.");
- // return;
- //}
-
- //var localEndpoint = new IPEndPoint(ipAddress, tcpPort);
- //// This represents the minimal configuration necessary to open a connection.
- //var clientConnectionOptions = new QuicClientConnectionOptions
- //{
- // // End point of the server to connect to.
- // LocalEndPoint = new IPEndPoint(ipAddress, tcpPort),
-
- // // Used to abort stream if it's not properly closed by the user.
- // // See https://www.rfc-editor.org/rfc/rfc9000#section-20.2
- // DefaultStreamErrorCode = 0x0A, // Protocol-dependent error code.
-
- // // Used to close the connection if it's not done by the user.
- // // See https://www.rfc-editor.org/rfc/rfc9000#section-20.2
- // DefaultCloseErrorCode = 0x0B, // Protocol-dependent error code.
-
- // // Optionally set limits for inbound streams.
- // MaxInboundUnidirectionalStreams = 10,
- // MaxInboundBidirectionalStreams = 100,
-
- // // Same options as for client side SslStream.
- // ClientAuthenticationOptions = new SslClientAuthenticationOptions
- // {
- // // List of supported application protocols.
- // ApplicationProtocols = channelFactory.SubProtocols.Select(proto => new SslApplicationProtocol(proto.Id)).ToList()
-
- // }
- //};
-
-
- //Multiaddr newIpProtocol = localEndpoint.AddressFamily == AddressFamily.InterNetwork
- // ? Multiaddr.Ip4
- // : Multiaddr.Ip6;
-
- //context.LocalEndpoint = MultiAddr.From(newIpProtocol, localEndpoint.Address.ToString(),
- // Multiaddr.Udp,
- // localEndpoint.Port);
-
- //context.LocalPeer.Address = context.LocalPeer.Address.Replace(
- // context.LocalEndpoint.Has(Multiaddr.Ip4) ? Multiaddr.Ip4 : Multiaddr.Ip6, newIpProtocol,
- // localEndpoint.Address.ToString())
- // .Replace(
- // Multiaddr.Udp,
- // localEndpoint.Port.ToString());
-
-
- //// Initialize, configure and connect to the server.
- //var connection = await QuicConnection.ConnectAsync(clientConnectionOptions);
-
- //channel.OnClose(async () =>
- //{
- // await connection.CloseAsync(0);
- // await connection.DisposeAsync();
- //});
-
- //Console.WriteLine($"Connected {connection.LocalEndPoint} --> {connection.RemoteEndPoint}");
-
- //_ = Task.Run(async () =>
- //{
- // while (!channel.IsClosed)
- // {
- // var incomingStream = await connection.AcceptInboundStreamAsync();
- // IPeerContext clientContext = context.Fork();
- // IPEndPoint remoteIpEndpoint = (IPEndPoint)client.RemoteEndPoint!;
-
- // clientContext.RemoteEndpoint = MultiAddr.From(
- // remoteIpEndpoint.AddressFamily == AddressFamily.InterNetwork
- // ? Multiaddr.Ip4
- // : Multiaddr.Ip6, remoteIpEndpoint.Address.ToString(), Multiaddr.Tcp,
- // remoteIpEndpoint.Port);
- // clientContext.LocalPeer.Address = context.LocalPeer.Address.Replace(
- // context.LocalEndpoint.Has(Multiaddr.Ip4) ? Multiaddr.Ip4 : Multiaddr.Ip6, newIpProtocol,
- // remoteIpEndpoint.Address.ToString())
- // .Replace(
- // Multiaddr.Tcp,
- // remoteIpEndpoint.Port.ToString());
- // clientContext.RemotePeer.Address = new MultiAddr()
- // .Append(remoteIpEndpoint.AddressFamily == AddressFamily.InterNetwork
- // ? Multiaddr.Ip4
- // : Multiaddr.Ip6, remoteIpEndpoint.Address.ToString())
- // .Append(Multiaddr.Tcp, remoteIpEndpoint.Port.ToString());
-
- // IChannel chan = channelFactory.SubListen(clientContext);
-
- // _ = Task.Run(async () =>
- // {
- // try
- // {
- // while (!chan.IsClosed)
- // {
- // if (client.Available == 0)
- // {
- // await Task.Yield();
- // }
-
- // byte[] buf = new byte[client.Available];
- // int len = await client.ReceiveAsync(buf, SocketFlags.None);
- // if (len != 0)
- // {
- // await chan.WriteAsync(new ReadOnlySequence(buf.AsMemory()[..len]));
- // }
- // }
- // }
- // catch (SocketException)
- // {
- // await chan.CloseAsync(false);
- // }
- // }, chan.Token);
- // _ = Task.Run(async () =>
- // {
- // try
- // {
- // await foreach (ReadOnlySequence data in chan.ReadAllAsync())
- // {
- // await client.SendAsync(data.ToArray(), SocketFlags.None);
- // }
- // }
- // catch (SocketException)
- // {
- // _logger?.LogInformation("Disconnected due to a socket exception");
- // await chan.CloseAsync(false);
- // }
- // }, chan.Token);
- // }
- //});
+ MultiaddrEnum newIpProtocol = connection.LocalEndPoint.AddressFamily == AddressFamily.InterNetwork
+ ? MultiaddrEnum.Ip4
+ : MultiaddrEnum.Ip6;
+
+ context.LocalEndpoint = Multiaddr.From(
+ newIpProtocol,
+ connection.LocalEndPoint.Address.ToString(),
+ MultiaddrEnum.Udp,
+ connection.LocalEndPoint.Port);
+
+ context.LocalPeer.Address = context.LocalPeer.Address.Replace(
+ context.LocalEndpoint.Has(MultiaddrEnum.Ip4) ? MultiaddrEnum.Ip4 : MultiaddrEnum.Ip6, newIpProtocol,
+ connection.LocalEndPoint.Address.ToString())
+ .Replace(
+ MultiaddrEnum.Udp,
+ connection.LocalEndPoint.Port.ToString());
+
+ IPEndPoint remoteIpEndpoint = connection.RemoteEndPoint!;
+ newIpProtocol = remoteIpEndpoint.AddressFamily == AddressFamily.InterNetwork
+ ? MultiaddrEnum.Ip4
+ : MultiaddrEnum.Ip6;
+
+ context.RemoteEndpoint = Multiaddr.From(
+ newIpProtocol,
+ remoteIpEndpoint.Address.ToString(),
+ MultiaddrEnum.Udp,
+ remoteIpEndpoint.Port);
+
+ context.Connected(context.RemotePeer);
+
+ _ = Task.Run(async () =>
+ {
+ foreach (IChannelRequest request in context.SubDialRequests.GetConsumingEnumerable())
+ {
+ QuicStream stream = await connection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional);
+ IPeerContext dialContext = context.Fork();
+ dialContext.SpecificProtocolRequest = request;
+ IChannel upChannel = channelFactory.SubDial(dialContext);
+ ExchangeData(stream, upChannel, request.CompletionSource);
+ }
+ }, token);
+
+ while (!token.IsCancellationRequested)
+ {
+ QuicStream inboundStream = await connection.AcceptInboundStreamAsync(token);
+ IChannel upChannel = channelFactory.SubListen(context);
+ ExchangeData(inboundStream, upChannel, null);
+ }
}
- public Task DialAsync(IChannel channel, IChannelFactory? channelFactory, IPeerContext context)
+ private void ExchangeData(QuicStream stream, IChannel upChannel, TaskCompletionSource? tcs)
{
- throw new NotImplementedException();
- //// First, check if QUIC is supported.
- //if (!QuicConnection.IsSupported)
- //{
- // Console.WriteLine("QUIC is not supported, check for presence of libmsquic and support of TLS 1.3.");
- // return;
- //}
-
- //// This represents the minimal configuration necessary to open a connection.
- //var clientConnectionOptions = new QuicClientConnectionOptions
- //{
- // // End point of the server to connect to.
- // RemoteEndPoint = listener.LocalEndPoint,
-
- // // Used to abort stream if it's not properly closed by the user.
- // // See https://www.rfc-editor.org/rfc/rfc9000#section-20.2
- // DefaultStreamErrorCode = 0x0A, // Protocol-dependent error code.
-
- // // Used to close the connection if it's not done by the user.
- // // See https://www.rfc-editor.org/rfc/rfc9000#section-20.2
- // DefaultCloseErrorCode = 0x0B, // Protocol-dependent error code.
-
- // // Optionally set limits for inbound streams.
- // MaxInboundUnidirectionalStreams = 10,
- // MaxInboundBidirectionalStreams = 100,
-
- // // Same options as for client side SslStream.
- // ClientAuthenticationOptions = new SslClientAuthenticationOptions
- // {
- // // List of supported application protocols.
- // ApplicationProtocols = channelFactory.SubProtocols.Select(proto => new SslApplicationProtocol(proto.Id)).ToList()
- // }
- //};
-
- //// Initialize, configure and connect to the server.
- //var connection = await QuicConnection.ConnectAsync(clientConnectionOptions);
-
- //Console.WriteLine($"Connected {connection.LocalEndPoint} --> {connection.RemoteEndPoint}");
-
- //// Open a bidirectional (can both read and write) outbound stream.
- //var outgoingStream = await connection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional);
-
- //// Work with the outgoing stream ...
-
- //// To accept any stream on a client connection, at least one of MaxInboundBidirectionalStreams or MaxInboundUnidirectionalStreams of QuicConnectionOptions must be set.
- //while (isRunning)
- //{
- // // Accept an inbound stream.
- // var incomingStream = await connection.AcceptInboundStreamAsync();
-
- // // Work with the incoming stream ...
- //}
-
- //// Close the connection with the custom code.
- //await connection.CloseAsync(0x0C);
-
- //// Dispose the connection.
- //await connection.DisposeAsync();
-
-
- //TaskCompletionSource waitForStop = new(TaskCreationOptions.RunContinuationsAsynchronously);
- //Socket client = new(SocketType.Stream, ProtocolType.Tcp);
- //MultiAddr addr = context.RemotePeer.Address;
- //Multiaddr ipProtocol = addr.Has(Multiaddr.Ip4) ? Multiaddr.Ip4 : Multiaddr.Ip6;
- //IPAddress ipAddress = IPAddress.Parse(addr.At(ipProtocol)!);
- //int tcpPort = int.Parse(addr.At(Multiaddr.Tcp)!);
- //try
- //{
- // await client.ConnectAsync(new IPEndPoint(ipAddress, tcpPort));
- //}
- //catch (SocketException)
- //{
- // _logger?.LogInformation("Failed to connect");
- // // TODO: Add proper exception and reconnection handling
- // return;
- //}
-
- //IPEndPoint localEndpoint = (IPEndPoint)client.LocalEndPoint!;
- //IPEndPoint remoteEndpoint = (IPEndPoint)client.RemoteEndPoint!;
-
- //context.RemoteEndpoint = MultiAddr.From(
- // ipProtocol,
- // ipProtocol == Multiaddr.Ip4 ? remoteEndpoint.Address.MapToIPv4() : remoteEndpoint.Address.MapToIPv6(),
- // Multiaddr.Tcp, remoteEndpoint.Port);
- //context.LocalEndpoint = MultiAddr.From(
- // ipProtocol,
- // ipProtocol == Multiaddr.Ip4 ? localEndpoint.Address.MapToIPv4() : localEndpoint.Address.MapToIPv6(),
- // Multiaddr.Tcp, localEndpoint.Port);
- //context.LocalPeer.Address = context.LocalEndpoint.Append(Multiaddr.P2p, context.LocalPeer.Identity.PeerId);
-
- //IChannel upChannel = channelFactory.SubDial(context);
- ////upChannel.OnClosing += (graceful) => upChannel.CloseAsync(graceful);
-
- //_ = Task.Run(async () =>
- //{
- // byte[] buf = new byte[client.ReceiveBufferSize];
- // try
- // {
- // while (!upChannel.IsClosed)
- // {
- // int len = await client.ReceiveAsync(buf, SocketFlags.None);
- // if (len != 0)
- // {
- // _logger?.LogDebug("Receive data, len={0}", len);
- // await upChannel.WriteAsync(new ReadOnlySequence(buf[..len]));
- // }
- // }
-
- // waitForStop.SetCanceled();
- // }
- // catch (SocketException)
- // {
- // await upChannel.CloseAsync();
- // waitForStop.SetCanceled();
- // }
- //});
-
- //_ = Task.Run(async () =>
- //{
- // try
- // {
- // await foreach (ReadOnlySequence data in upChannel.ReadAllAsync())
- // {
- // await client.SendAsync(data.ToArray(), SocketFlags.None);
- // }
-
- // waitForStop.SetCanceled();
- // }
- // catch (SocketException)
- // {
- // await upChannel.CloseAsync(false);
- // waitForStop.SetCanceled();
- // }
- //});
+ 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);
+ }
+ }
+ catch (SocketException)
+ {
+ _logger?.LogInformation("Disconnected due to a socket exception");
+ await upChannel.CloseAsync(false);
+ }
+ }, upChannel.Token);
+
+ _ = Task.Run(async () =>
+ {
+ try
+ {
+ while (!upChannel.IsClosed)
+ {
+ byte[] buf = new byte[1024];
+ int len = await stream.ReadAtLeastAsync(buf, 1, false, upChannel.Token);
+ if (len != 0)
+ {
+ await upChannel.WriteAsync(new ReadOnlySequence(buf.AsMemory()[..len]));
+ }
+ }
+ }
+ catch (SocketException)
+ {
+ _logger?.LogInformation("Disconnected due to a socket exception");
+ await upChannel.CloseAsync(false);
+ }
+ });
}
}
diff --git a/src/libp2p/Libp2p.Protocols.Quic/README.md b/src/libp2p/Libp2p.Protocols.Quic/README.md
index 8d3541e5..8a1c855c 100644
--- a/src/libp2p/Libp2p.Protocols.Quic/README.md
+++ b/src/libp2p/Libp2p.Protocols.Quic/README.md
@@ -1,3 +1,18 @@
# QUIC transport
See the [libp2p spec](https://github.com/libp2p/specs/tree/master/quic)
+
+## Native dependencies
+
+### Windows
+
+Quic support is fully inbuilt in the runtime. By default it utilizes `Schannel`-based implementation which is not compatible with libp2p, `OpenSSL`-based version is required. To use it instead of standard one, a runtime library needs to be replaced:
+
+1. Locate the current NETCore runtime: `dotnet --info`
+2. Find `msquic.dll` for the current version and replace it with one from the project.
+
+Typical location of `msquic.dll` is "C:\Program Files\dotnet\shared\Microsoft.NETCore.App\x.y.zzz\msquic.dll"
+
+### Linux
+
+`libmsquic.so` is required , as described [here](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Quic/readme.md#linux).
diff --git a/src/libp2p/Libp2p.Protocols.Quic/msquic.dll b/src/libp2p/Libp2p.Protocols.Quic/msquic.dll
new file mode 100644
index 00000000..6e1adf37
Binary files /dev/null and b/src/libp2p/Libp2p.Protocols.Quic/msquic.dll differ
diff --git a/src/libp2p/Libp2p.sln b/src/libp2p/Libp2p.sln
index 13453f2b..7ca6e42f 100644
--- a/src/libp2p/Libp2p.sln
+++ b/src/libp2p/Libp2p.sln
@@ -55,6 +55,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libp2p.Protocols.Ping", "Li
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Multiformats.Hash", "..\cs-multihash\src\Multiformats.Hash\Multiformats.Hash.csproj", "{064158B8-C0A0-4CE5-8D6A-77FE657788FE}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libp2p.Protocols.Quic.Tests", "Libp2p.Protocols.Quic.Tests\Libp2p.Protocols.Quic.Tests.csproj", "{EEECB761-A3C3-4598-AD03-EFABBF6CAA77}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -157,6 +159,10 @@ Global
{064158B8-C0A0-4CE5-8D6A-77FE657788FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{064158B8-C0A0-4CE5-8D6A-77FE657788FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{064158B8-C0A0-4CE5-8D6A-77FE657788FE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EEECB761-A3C3-4598-AD03-EFABBF6CAA77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EEECB761-A3C3-4598-AD03-EFABBF6CAA77}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EEECB761-A3C3-4598-AD03-EFABBF6CAA77}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EEECB761-A3C3-4598-AD03-EFABBF6CAA77}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -178,6 +184,7 @@ Global
{F4546A3C-A37C-4E4E-BD8C-BE8A70C2A4A8} = {6F3D9AA9-C92D-4998-BC4E-D5EA068E8D0D}
{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}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E337E37C-3DB8-42FA-9A83-AC4E3B2557B4}
diff --git a/src/libp2p/Libp2p/Libp2pPeerFactoryBuilder.cs b/src/libp2p/Libp2p/Libp2pPeerFactoryBuilder.cs
index 157316fd..76048572 100644
--- a/src/libp2p/Libp2p/Libp2pPeerFactoryBuilder.cs
+++ b/src/libp2p/Libp2p/Libp2pPeerFactoryBuilder.cs
@@ -33,15 +33,12 @@ protected override ProtocolStack BuildStack()
.Over()
.Over(tcpEncryptionStack)
.Over()
- .Over()
- .Over();
-
- ProtocolStack quicStack =
- Over();
+ .Over();
return
Over()
- .Over(quicStack).Or(tcpStack)
+ .Over().Or(tcpStack)
+ .Over()
.AddAppLayerProtocol()
//.AddAppLayerProtocol()
//.AddAppLayerProtocol()
diff --git a/src/libp2p/Libp2p/MultiaddrBasedSelectorProtocol.cs b/src/libp2p/Libp2p/MultiaddrBasedSelectorProtocol.cs
index 7c999703..6e993342 100644
--- a/src/libp2p/Libp2p/MultiaddrBasedSelectorProtocol.cs
+++ b/src/libp2p/Libp2p/MultiaddrBasedSelectorProtocol.cs
@@ -18,9 +18,23 @@ public class MultiaddrBasedSelectorProtocol(ILoggerFactory? loggerFactory = null
protected override async Task ConnectAsync(IChannel _, IChannelFactory? channelFactory, IPeerContext context, bool isListener)
{
- IProtocol protocol = context.LocalPeer.Address.Has(Core.Enums.Multiaddr.Quic) ?
- channelFactory!.SubProtocols.FirstOrDefault(proto => proto.Id.Contains("quic")) ?? throw new ApplicationException("QUIC is not supported") :
- channelFactory!.SubProtocols.FirstOrDefault(proto => proto.Id.Contains("tcp")) ?? throw new ApplicationException("TCP is not supported");
+ IProtocol protocol = null!;
+ if (context.LocalPeer.Address.Has(Core.Enums.Multiaddr.QuicV1))
+ {
+ protocol = channelFactory!.SubProtocols.FirstOrDefault(proto => proto.Id.Contains("quic")) ?? throw new ApplicationException("QUIC is not supported");
+ }
+ else if (context.LocalPeer.Address.Has(Core.Enums.Multiaddr.Quic))
+ {
+ throw new ApplicationException("QUIC version draft-29 is not supported.");
+ }
+ else if (context.LocalPeer.Address.Has(Core.Enums.Multiaddr.Tcp))
+ {
+ protocol = channelFactory!.SubProtocols.FirstOrDefault(proto => proto.Id.Contains("tcp")) ?? throw new ApplicationException("TCP is not supported");
+ }
+ else
+ {
+ throw new NotImplementedException($"No transport protocol found for the given address: {context.LocalPeer.Address}");
+ }
_logger?.LogPickedProtocol(protocol.Id, isListener ? "listen" : "dial");
diff --git a/src/samples/chat/Program.cs b/src/samples/chat/Program.cs
index 9520d8ad..83614fd8 100644
--- a/src/samples/chat/Program.cs
+++ b/src/samples/chat/Program.cs
@@ -39,7 +39,7 @@
ILocalPeer peer = peerFactory.Create(optionalFixedIdentity);
IListener listener = await peer.ListenAsync(
- $"/ip4/0.0.0.0/tcp/{(args.Length > 0 && args[0] == "-sp" ? args[1] : "0")}/p2p/{peer.Identity.PeerId}",
+ $"/ip4/0.0.0.0/udp/{(args.Length > 0 && args[0] == "-sp" ? args[1] : "0")}/quic-v1/p2p/{peer.Identity.PeerId}",
ts.Token);
logger.LogInformation($"Listener started at {listener.Address}");
listener.OnConnection += async remotePeer => logger.LogInformation($"A peer connected {remotePeer.Address}");
diff --git a/src/samples/pubsub-chat/Program.cs b/src/samples/pubsub-chat/Program.cs
index 51987414..c68b5501 100644
--- a/src/samples/pubsub-chat/Program.cs
+++ b/src/samples/pubsub-chat/Program.cs
@@ -27,13 +27,10 @@
ILogger logger = serviceProvider.GetService()!.CreateLogger("Pubsub Chat");
CancellationTokenSource ts = new();
-Random r = new();
-byte[] buf = new byte[32];
-r.NextBytes(buf);
-Identity optionalFixedIdentity = new(buf);
-string addr = $"/ip4/0.0.0.0/tcp/0/p2p/{optionalFixedIdentity.PeerId}";
+Identity localPeerIdentity = new();
+string addr = $"/ip4/0.0.0.0/udp/0/quic-v1/p2p/{localPeerIdentity.PeerId}";
-ILocalPeer peer = peerFactory.Create(optionalFixedIdentity, addr);
+ILocalPeer peer = peerFactory.Create(localPeerIdentity, addr);
PubsubRouter router = serviceProvider.GetService()!;
ITopic topic = router.Subscribe("chat-room:awesome-chat-room");