Summary Update.

This commit is contained in:
2026-06-06 23:38:50 -04:00
parent dd75ca4b06
commit 2916d17868
30 changed files with 1231 additions and 21 deletions

View File

@@ -2,71 +2,159 @@ 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,
@@ -74,15 +162,37 @@ public enum SignalType
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
}