Chat file attachments, stage 1: protocol + data model + pure seams
First slice of in-chat file/photo sharing (dedicated file plane, images inline + file chips, session-only). This stage adds the wire types and the pure, unit-tested logic; no transport or UI yet. - protocol: new FILES_ALPN / FILES_PROTO (peerspeak/files/1) for the dedicated file-transfer plane. Bump GOSSIP_PROTO 1->2 + sig domain v2 (Chat gained an attachment field, so cross-version peers fail fast rather than half-work) and Cargo 0.2.0 -> 0.3.0 per VERSIONING.md. BREAKING wire change: all peers must run >= 0.3.0. - new src/files.rs: ChatAttachment descriptor (name/size/kind/id; bytes travel off-gossip), AttachmentKind, plus pure seams — sanitize_filename (path-traversal/control-char/length-safe), size_within_cap, image magic-byte sniffing + defensive limited decode (decode-bomb guard), 32-byte request parsing, human_size. 13 unit tests. - GossipMessage::Chat and RoomEvent::ChatMessage carry an optional ChatAttachment; send_chat takes Option<ChatAttachment>. Untrusted inbound descriptors are filename-sanitized + size-validated on ingest. serde(default) keeps the field forward-compatible at the JSON layer; +round-trip and pre-v2 back-compat tests. The attachment id is a random 32-byte handle (rand, already a dep), not a content hash — the fetch is authenticated + encrypted + member-gated, so no crypto-hash dep is needed. 349 lib tests pass, clippy clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+16
-3
@@ -57,7 +57,15 @@ pub enum RoomEvent {
|
||||
/// A peer sent a room text-chat message. Carries the sender's id, their
|
||||
/// display name (embedded so it shows even without a presence entry), the
|
||||
/// text, and a sender-stamped millisecond timestamp.
|
||||
ChatMessage { from: EndpointId, name: String, text: String, ts: u64 },
|
||||
ChatMessage {
|
||||
from: EndpointId,
|
||||
name: String,
|
||||
text: String,
|
||||
ts: u64,
|
||||
/// Optional file attachment descriptor; the bytes are fetched off-gossip
|
||||
/// on the file plane. Already filename-sanitized + size-capped on ingest.
|
||||
attachment: Option<crate::files::ChatAttachment>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Transport-level link state for a peer, surfaced so the UI can show when a
|
||||
@@ -206,8 +214,13 @@ pub trait RoomState: Send + Sync {
|
||||
fn mark_peer_disconnected(&self, peer_id: EndpointId);
|
||||
|
||||
/// Broadcasts a room text-chat message authored by us (our display name is
|
||||
/// taken from the current self-state).
|
||||
async fn send_chat(&self, text: String) -> Result<(), NetError>;
|
||||
/// taken from the current self-state), optionally carrying a file attachment
|
||||
/// descriptor whose bytes are served separately on the file plane.
|
||||
async fn send_chat(
|
||||
&self,
|
||||
text: String,
|
||||
attachment: Option<crate::files::ChatAttachment>,
|
||||
) -> Result<(), NetError>;
|
||||
|
||||
/// Leaves the room and announces departure.
|
||||
async fn leave(&self) -> Result<(), NetError>;
|
||||
|
||||
Reference in New Issue
Block a user