namespace DataTransfer.Client.Shared; /// Constants shared by the relay server and the browser client. public static class TransferProtocol { public const string HubPath = "/hubs/transfer"; /// HTTP endpoint returning the current . public const string StatusPath = "/api/status"; /// Plaintext size of one file chunk. Files are encrypted and relayed chunk by chunk. public const int FileChunkBytes = 256 * 1024; /// Upper bound of a single encrypted frame (largest chunk plus framing and AES-GCM overhead). public const int MaxFrameBytes = FileChunkBytes + 1024; /// /// SignalR limit for one incoming message: the largest frame base64 encoded inside JSON, plus room for the /// envelope. Kept tight because the server may buffer one such message per connected browser. /// public const long MaxHubMessageBytes = (MaxFrameBytes + 2) / 3 * 4 + 4 * 1024; public const long MaxFileBytes = 100L * 1024 * 1024; public const int MaxMessageLength = 10_000; /// /// Maximum number of messages and files one side may send per connection. The receiver keeps all of them until /// the session is reset and closes the connection when the peer exceeds it. /// public const int MaxItemsPerConnection = 1_000; /// Maximum total size of the files one side may send per connection; enforced like . public const long MaxFileBytesPerConnection = 1024L * 1024 * 1024; } /// /// Methods the browser invokes on the relay hub. Each client may invoke at most /// Transfer:MaxInvocationsPerSecond methods per second; further invocations are delayed. /// public interface ITransferHub { /// /// Returns a new proof-of-work challenge for , or null if the server doesn't /// require one. It replaces any previous challenge of the connection. /// Task GetBotCheckChallenge(); /// The number that solves the latest challenge; any value if none is required. Task CreateConnection(int botCheckSolution); Task JoinConnection(string code); /// /// Relays an opaque frame to the peer: first the plaintext key-exchange messages, then end-to-end encrypted /// frames. /// Task Send(byte[] frame); Task LeaveConnection(); } /// Methods the relay hub invokes on the browser. public interface ITransferHubClient { /// Someone joined the creator's connection; the key exchange follows via . Task PeerJoined(); Task Receive(byte[] frame); Task ConnectionClosed(ConnectionClosedReason reason); /// A queued create request got a free slot; the connection now exists. Task ConnectionCreated(CreateConnectionResult result); /// The 1-based queue position of a waiting create request changed. Task QueuePositionChanged(int position); } /// The connection code, if is . /// The connection is closed unless someone joins within this period. /// /// 1-based position in the queue, if is /// . /// public sealed record CreateConnectionResult( CreateStatus Status, string? Code, TimeSpan JoinTimeout, TimeSpan InactivityTimeout, int QueuePosition); public enum CreateStatus { Created, /// The connection limit is reached; the connection is created as soon as a slot becomes free. Queued, /// The connection limit is reached and the queue is full. ServerBusy, /// The network address of the browser already has the maximum number of connections or queued requests. TooManyConnections, /// The proof-of-work solution was missing, wrong, reused or too late. BotCheckFailed } /// /// Proof of work against creating connections in bulk: find the number between 0 and /// for which the lowercase hex SHA-256 of the UTF-8 string Salt + number equals . /// public sealed record BotCheckChallenge(string Salt, string Hash, int MaxNumber); /// Current load of the relay server. public sealed record ServerStatus(int ActiveConnections, int MaxConnections, int QueuedClients); public sealed record JoinConnectionResult(JoinStatus Status, TimeSpan InactivityTimeout); public enum JoinStatus { Joined, InvalidCode, NotFound, AlreadyInUse, /// Too many browsers from the same network address are connected to the server. TooManyConnections } public enum SendStatus { /// The frame was relayed to the peer. Delivered, WaitingForPeer, NotConnected, InvalidSize, /// The connection relayed its maximum amount of data and was closed. TransferLimitReached } public enum ConnectionClosedReason { PeerLeft, Inactivity, MaximumLifetime, TransferLimitReached }