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