using System.Collections.Concurrent; using System.Net.Sockets; using System.Security.Cryptography; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; using PCC.App.Networking; using PCC.App.Security; using PCC.Common.EventBus; namespace PCC.App; // Handshake 1 → Public Key SHA256 // Handshake 2 ← ACK for Yes or No(close connection) // *Complete Handshake // SendText 1 → Payload // SendText 2 ← ACK // ACK: SHA256(incoming payload) public abstract class TrustedPeerManager { public record TPM_EVT_CMD_INIT(byte[] MyPub, byte[] MyPri); public record TPM_EVT_CMD_SHUTDOWN; public record TPM_EVT_PEER_CX(string PeerId); public record TPM_EVT_PEER_IX(string PeerId); public record TPM_EVT_PEER_DX(string PeerId); public record TPM_EVT_PEER_RX(string PeerId, byte[] block); private readonly IEventBus _eventBus; private readonly ILogger<TrustedPeerManager> _logger; private readonly SocketConnectionContextFactory _connectionContextFactory; private RSA _myPrivateKey; private ReadOnlyMemory<byte> _myPeerId; private readonly ConcurrentDictionary<string, PeerInfo> _trustedPeers = new(); private readonly ConcurrentDictionary<string, EncryptedTcpPeer> _connectedPeers = new(); private readonly ConcurrentDictionary<string, EncryptedTcpPeer> _IncomePeers = new(); protected TrustedPeerManager(IEventBus eventBus, ILogger<TrustedPeerManager> logger) { _eventBus = eventBus; _logger = logger; _connectionContextFactory = new(new(), _logger); eventBus.Subscript<TPM_EVT_CMD_INIT>(Init); eventBus.Subscript<TPM_EVT_CMD_SHUTDOWN>(Shutdown); } private void Init(TPM_EVT_CMD_INIT obj) { _myPrivateKey = RsaUtility.FromPKCS1PrivateKey(obj.MyPri); _myPeerId = SHA256.HashData(obj.MyPub); } private void Shutdown(TPM_EVT_CMD_SHUTDOWN _) { foreach (var item in _connectedPeers.Values) { item.Disconnect(); } foreach (var item in _IncomePeers.Values) { item.Disconnect(); } } public string AddTrustPeer(byte[] tpk, string host, int port) { var peerId = Convert.ToHexString(SHA256.HashData(tpk)); var rsa = RsaUtility.FromPKCS1PublicKey(tpk); _trustedPeers[peerId] = new PeerInfo(rsa, host, port); return peerId; } public bool ConnectToPeerAsync(string peerId) { if (_trustedPeers.TryGetValue(peerId, out var peerInfo) == false) { return false; } _ = Task.Run(async () => { try { //Start Peer client // connect to server using var sck = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sck.ConnectAsync(peerInfo.host, peerInfo.port); var conn = _connectionContextFactory.Create(sck); var peer = new TcpPeer(conn, sck); // handshake as client var ep = new EncryptedTcpPeer(_myPrivateKey, peerInfo.PublicKey, peer); await ep.SendBlock(_myPeerId); //send handshake 1 var blockAck = await ep.RxBlockAsync(new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token); if (blockAck?.SequenceEqual(SHA256.HashData(_myPeerId.Span)) != true) { peer.Disconnect(); return; } // register as connected //TODO: replace exist connection _connectedPeers[peerId] = ep; _eventBus.Publish(new TPM_EVT_PEER_CX(peerId)); while (true) // start RxCycle { var rxb = await ep.RxBlockAsync(); if (rxb == null) break; _eventBus.Publish(new TPM_EVT_PEER_RX(peerId, rxb)); } } catch (Exception ex) { _logger.LogError(ex, nameof(ConnectToPeerAsync)); } _eventBus.Publish(new TPM_EVT_PEER_DX(peerId)); }); return true; } public async Task HandleIncomingPeerAsync(ConnectionContext connection) { string? peerId = null; try { var peer = new TcpPeer(connection, null); // handshake as server var eb = await peer.RxBlockAsync(new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token); if (eb == null) return; // extract peerId peerId = Convert.ToHexString(RsaUtility.Decrypt(_myPrivateKey, eb)); // Kick if non trusted if (_trustedPeers.TryGetValue(peerId, out var peerInfo) == false) return; // verify signature var data = RsaUtility.DecryptAndVerifySignature(_myPrivateKey, eb, peerInfo.PublicKey); var ep = new EncryptedTcpPeer(_myPrivateKey, peerInfo.PublicKey, peer); await ep.SendBlock(SHA256.HashData(data)); //ACK // register as income //TODO: replace exist connection _IncomePeers[peerId] = ep; // start RxCycle while (true) // start RxCycle { var rxb = await ep.RxBlockAsync(); if (rxb == null) break; _eventBus.Publish(new TPM_EVT_PEER_RX(peerId, rxb)); } } catch (Exception e) { _logger.LogError(e, nameof(HandleIncomingPeerAsync)); } if (peerId != null) _eventBus.Publish(new TPM_EVT_PEER_DX(peerId)); } public bool SendToPeer(string peerId, byte[] block) { if (_connectedPeers.TryGetValue(peerId, out var peerCon) == false && _IncomePeers.TryGetValue(peerId, out peerCon) == false) return false; _ = peerCon.SendBlock(block); return true; } private record PeerInfo(RSA PublicKey, string host, int port); }