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
}