199 lines
8.3 KiB
C#
199 lines
8.3 KiB
C#
namespace RelayShared.Services;
|
|
|
|
//TODO: review name of file, potentially rename for Encryption services rather than sockets
|
|
|
|
/// <summary>
|
|
/// The "data plane" wire types for the WebSocket protocol.
|
|
///
|
|
/// Every type here carries a SignalType discriminator so a generic JsonDocument peek
|
|
/// can identify the variant. The server dispatches on SignalType in ChatSocketBehavior.OnMessage;
|
|
/// the client dispatches on it in RelaySocketClient.OnMessage.
|
|
///
|
|
/// Encrypted payloads share a uniform 4-tuple shape: (CipherText, Nonce, Tag, EncryptedKey).
|
|
/// That tuple is hybrid RSA+AES-GCM: EncryptedKey is the per-message AES key wrapped with the
|
|
/// recipient's RSA public key; CipherText/Nonce/Tag are the AES-GCM ciphertext, nonce, and
|
|
/// authentication tag for the actual JSON-serialised ChatMessageContent.
|
|
/// </summary>
|
|
public sealed class SocketRtcSignalMessage
|
|
{
|
|
/// <summary>Always SignalType.EncryptedSignal in flight.</summary>
|
|
public SignalType Type { get; set; }
|
|
|
|
/// <summary>Username of the user generating the SDP/ICE signal.</summary>
|
|
public string SenderUsername { get; set; } = string.Empty;
|
|
|
|
/// <summary>The voice channel this signal belongs to.</summary>
|
|
public string ChannelId { get; set; } = string.Empty;
|
|
|
|
/// <summary>Base64 AES-GCM ciphertext of the JSON-serialised RtcSignalMessage.</summary>
|
|
public string CipherText { get; set; } = string.Empty;
|
|
|
|
/// <summary>Base64 AES-GCM 96-bit nonce.</summary>
|
|
public string Nonce { get; set; } = string.Empty;
|
|
|
|
/// <summary>Base64 AES-GCM 128-bit authentication tag.</summary>
|
|
public string Tag { get; set; } = string.Empty;
|
|
|
|
/// <summary>Base64 RSA-OAEP-encrypted AES key (encrypted with recipient's public key).</summary>
|
|
public string EncryptedKey { get; set; } = string.Empty;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The workhorse envelope for chat messages and message lifecycle events.
|
|
/// Used for both directions and for new sends / edits / delete tombstones.
|
|
/// </summary>
|
|
public sealed class SocketEncryptedMessage
|
|
{
|
|
/// <summary>
|
|
/// EncryptedChat (server→client), ClientEncryptedChat (client→server new message),
|
|
/// ClientEditMessage / ClientDeleteMessage (client→server lifecycle), MessageEdited (server→client).
|
|
/// </summary>
|
|
public SignalType Type { get; set; } = SignalType.EncryptedChat;
|
|
|
|
/// <summary>Surreal record id (e.g. "channel_messages:abc"). Populated by the server on outbound delivery.</summary>
|
|
public string MessageId { get; set; } = string.Empty;
|
|
|
|
/// <summary>Who wrote the message.</summary>
|
|
public string SenderUsername { get; set; } = string.Empty;
|
|
|
|
/// <summary>Who this specific delivery is encrypted for. Different per recipient on the same logical message.</summary>
|
|
public string RecipientUsername { get; set; } = string.Empty;
|
|
|
|
/// <summary>The channel the message belongs to.</summary>
|
|
public string ChannelId { get; set; } = string.Empty;
|
|
|
|
/// <summary>Base64 AES-GCM ciphertext of the JSON-serialised ChatMessageContent. Empty on tombstone deliveries.</summary>
|
|
public string CipherText { get; set; } = string.Empty;
|
|
|
|
/// <summary>Base64 AES-GCM 96-bit nonce.</summary>
|
|
public string Nonce { get; set; } = string.Empty;
|
|
|
|
/// <summary>Base64 AES-GCM 128-bit authentication tag.</summary>
|
|
public string Tag { get; set; } = string.Empty;
|
|
|
|
/// <summary>Base64 RSA-OAEP-encrypted AES key (encrypted with recipient's public key on outbound, server's on inbound).</summary>
|
|
public string EncryptedKey { get; set; } = string.Empty;
|
|
|
|
/// <summary>True when this message has been edited at least once. Drives the (edited) footer in the bubble.</summary>
|
|
public bool IsEdited { get; set; }
|
|
|
|
/// <summary>True for tombstone deliveries (history only). Client renders a placeholder; no decryption is attempted.</summary>
|
|
public bool IsDeleted { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Server-broadcast tombstone fired the moment a message is deleted. Carries no content —
|
|
/// recipients use MessageId to find the existing bubble and swap it to a "deleted" placeholder.
|
|
/// </summary>
|
|
public sealed class SocketMessageDeletedEvent
|
|
{
|
|
public SignalType Type { get; set; } = SignalType.MessageDeleted;
|
|
|
|
/// <summary>The message being tombstoned.</summary>
|
|
public string MessageId { get; set; } = string.Empty;
|
|
|
|
/// <summary>Channel scope — clients that aren't viewing this channel can defer the bubble update.</summary>
|
|
public string ChannelId { get; set; } = string.Empty;
|
|
}
|
|
|
|
/// <summary>
|
|
/// "{Username} is typing…" hint. Server forwards to every connected member except the sender.
|
|
/// Client auto-clears the indicator 3 seconds after the last such event.
|
|
/// </summary>
|
|
public sealed class SocketTypingEvent
|
|
{
|
|
public SignalType Type { get; set; } = SignalType.TypingIndicator;
|
|
|
|
/// <summary>Who is typing.</summary>
|
|
public string Username { get; set; } = string.Empty;
|
|
|
|
/// <summary>Which channel they're typing in. Clients ignore events for channels they're not viewing.</summary>
|
|
public string ChannelId { get; set; } = string.Empty;
|
|
}
|
|
|
|
/// <summary>One historical version of an edited message, re-encrypted for the requester.</summary>
|
|
public sealed class SocketEditHistoryEntry
|
|
{
|
|
/// <summary>Base64 AES-GCM ciphertext of the JSON-serialised previous ChatMessageContent.</summary>
|
|
public string CipherText { get; set; } = string.Empty;
|
|
|
|
public string Nonce { get; set; } = string.Empty;
|
|
public string Tag { get; set; } = string.Empty;
|
|
|
|
/// <summary>Base64 RSA-OAEP-encrypted AES key (encrypted with requester's public key).</summary>
|
|
public string EncryptedKey { get; set; } = string.Empty;
|
|
|
|
/// <summary>When this version was the current text (i.e. when it was replaced).</summary>
|
|
public DateTime EditedAt { get; set; }
|
|
}
|
|
|
|
/// <summary>Server reply to a GetEditHistory request. Entries are ordered oldest→newest.</summary>
|
|
public sealed class SocketEditHistoryResponse
|
|
{
|
|
public SignalType Type { get; set; } = SignalType.EditHistory;
|
|
|
|
/// <summary>Which message this history is for.</summary>
|
|
public string MessageId { get; set; } = string.Empty;
|
|
|
|
/// <summary>Every previous version of the message. Empty if the message has never been edited.</summary>
|
|
public List<SocketEditHistoryEntry> Entries { get; set; } = [];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Server-to-client delivery of the server's public RSA key. Sent once per session in
|
|
/// response to WsAction.GetServerKey. Clients cache this for all outbound encryption.
|
|
/// </summary>
|
|
public sealed class ServerPublicKeyMessage
|
|
{
|
|
public SignalType Type { get; set; } = SignalType.ServerPublicKey;
|
|
|
|
/// <summary>Base64 SubjectPublicKeyInfo (DER) of the server's RSA public key.</summary>
|
|
public string PublicKey { get; set; } = string.Empty;
|
|
}
|
|
|
|
/// <summary>The wire discriminator for every data-plane Socket*Message.</summary>
|
|
public enum SignalType
|
|
{
|
|
// RTC SDP/ICE wire types (used by the WebView RTC engine, not handled directly here)
|
|
Offer,
|
|
Answer,
|
|
Candidate,
|
|
OfferUpdated,
|
|
AnswerUpdated,
|
|
CandidateAdded,
|
|
CallLeft,
|
|
|
|
/// <summary>Server→client: paginated channel list (SocketChannelList).</summary>
|
|
ChannelList,
|
|
|
|
/// <summary>Server→client: ServerPublicKeyMessage delivery.</summary>
|
|
ServerPublicKey,
|
|
|
|
/// <summary>Bidirectional: encrypted RTC SDP/ICE signal (SocketRtcSignalMessage).</summary>
|
|
EncryptedSignal,
|
|
|
|
/// <summary>Server→client: delivered chat message (SocketEncryptedMessage).</summary>
|
|
EncryptedChat,
|
|
|
|
/// <summary>Client→server: new chat message send (SocketEncryptedMessage).</summary>
|
|
ClientEncryptedChat,
|
|
|
|
/// <summary>Client→server: request to edit own message (SocketEncryptedMessage with new content).</summary>
|
|
ClientEditMessage,
|
|
|
|
/// <summary>Client→server: request to delete own message (SocketEncryptedMessage with only MessageId).</summary>
|
|
ClientDeleteMessage,
|
|
|
|
/// <summary>Server→clients: edit broadcast carrying re-encrypted new content (SocketEncryptedMessage).</summary>
|
|
MessageEdited,
|
|
|
|
/// <summary>Server→clients: deletion tombstone (SocketMessageDeletedEvent).</summary>
|
|
MessageDeleted,
|
|
|
|
/// <summary>Server→peers: typing indicator (SocketTypingEvent).</summary>
|
|
TypingIndicator,
|
|
|
|
/// <summary>Server→requester: edit-history response (SocketEditHistoryResponse).</summary>
|
|
EditHistory
|
|
}
|