···11+#![cfg(feature = "iroh")]
22+13//! Peer discovery via AT Protocol session records.
24//!
35//! Collaborators publish `sh.weaver.collab.session` records to their PDS
+84-63
crates/weaver-common/src/transport/messages.rs
···11//! Wire protocol for collaborative editing messages.
2233-use iroh::{PublicKey, SecretKey, Signature};
43use serde::{Deserialize, Serialize};
5465/// Messages exchanged between collaborators over gossip.
···6564 }
6665}
67666868-/// A signed message wrapper for authenticated transport.
6969-///
7070-/// Includes the sender's public key so receivers can verify without context.
7171-#[derive(Debug, Clone, Serialize, Deserialize)]
7272-pub struct SignedMessage {
7373- /// Sender's public key (also their EndpointId).
7474- pub from: PublicKey,
7575- /// The serialized TimestampedMessage (postcard bytes).
7676- pub data: Vec<u8>,
7777- /// Ed25519 signature over data.
7878- pub signature: Signature,
7979-}
6767+// ============================================================================
6868+// Signed message wrapper (requires iroh for crypto)
6969+// ============================================================================
80708181-/// Versioned wire format with timestamp.
8282-#[derive(Debug, Clone, Serialize, Deserialize)]
8383-enum WireMessage {
8484- V0 { timestamp: u64, message: CollabMessage },
8585-}
7171+#[cfg(feature = "iroh")]
7272+mod signed {
7373+ use super::*;
7474+ use iroh::{PublicKey, SecretKey, Signature};
86758787-/// A verified message with sender and timestamp info.
8888-#[derive(Debug, Clone)]
8989-pub struct ReceivedMessage {
9090- /// Sender's public key.
9191- pub from: PublicKey,
9292- /// When the message was sent (micros since epoch).
9393- pub timestamp: u64,
9494- /// The decoded message.
9595- pub message: CollabMessage,
9696-}
7676+ /// A signed message wrapper for authenticated transport.
7777+ #[derive(Debug, Clone, Serialize, Deserialize)]
7878+ pub struct SignedMessage {
7979+ /// Sender's public key (also their EndpointId).
8080+ pub from: PublicKey,
8181+ /// The serialized TimestampedMessage (postcard bytes).
8282+ pub data: Vec<u8>,
8383+ /// Ed25519 signature over data.
8484+ pub signature: Signature,
8585+ }
97869898-/// Error type for signed message operations.
9999-#[derive(Debug, thiserror::Error)]
100100-pub enum SignedMessageError {
101101- #[error("serialization failed: {0}")]
102102- Serialization(#[from] postcard::Error),
103103- #[error("signature verification failed")]
104104- InvalidSignature,
105105-}
8787+ /// Versioned wire format with timestamp.
8888+ #[derive(Debug, Clone, Serialize, Deserialize)]
8989+ enum WireMessage {
9090+ V0 { timestamp: u64, message: CollabMessage },
9191+ }
10692107107-impl SignedMessage {
108108- /// Sign a message and encode to bytes for wire transmission.
109109- pub fn sign_and_encode(secret_key: &SecretKey, message: &CollabMessage) -> Result<Vec<u8>, SignedMessageError> {
110110- use web_time::SystemTime;
9393+ /// A verified message with sender and timestamp info.
9494+ #[derive(Debug, Clone)]
9595+ pub struct ReceivedMessage {
9696+ /// Sender's public key.
9797+ pub from: PublicKey,
9898+ /// When the message was sent (micros since epoch).
9999+ pub timestamp: u64,
100100+ /// The decoded message.
101101+ pub message: CollabMessage,
102102+ }
111103112112- let timestamp = SystemTime::now()
113113- .duration_since(SystemTime::UNIX_EPOCH)
114114- .unwrap()
115115- .as_micros() as u64;
116116- let wire = WireMessage::V0 { timestamp, message: message.clone() };
117117- let data = postcard::to_stdvec(&wire)?;
118118- let signature = secret_key.sign(&data);
119119- let from = secret_key.public();
120120- let signed = Self { from, data, signature };
121121- Ok(postcard::to_stdvec(&signed)?)
104104+ /// Error type for signed message operations.
105105+ #[derive(Debug, thiserror::Error)]
106106+ pub enum SignedMessageError {
107107+ #[error("serialization failed: {0}")]
108108+ Serialization(#[from] postcard::Error),
109109+ #[error("signature verification failed")]
110110+ InvalidSignature,
122111 }
123112124124- /// Decode from bytes and verify signature.
125125- pub fn decode_and_verify(bytes: &[u8]) -> Result<ReceivedMessage, SignedMessageError> {
126126- let signed: Self = postcard::from_bytes(bytes)?;
127127- signed.from
128128- .verify(&signed.data, &signed.signature)
129129- .map_err(|_| SignedMessageError::InvalidSignature)?;
130130- let wire: WireMessage = postcard::from_bytes(&signed.data)?;
131131- let WireMessage::V0 { timestamp, message } = wire;
132132- Ok(ReceivedMessage {
133133- from: signed.from,
134134- timestamp,
135135- message,
136136- })
113113+ impl SignedMessage {
114114+ /// Sign a message and encode to bytes for wire transmission.
115115+ pub fn sign_and_encode(
116116+ secret_key: &SecretKey,
117117+ message: &CollabMessage,
118118+ ) -> Result<Vec<u8>, SignedMessageError> {
119119+ use web_time::SystemTime;
120120+121121+ let timestamp = SystemTime::now()
122122+ .duration_since(SystemTime::UNIX_EPOCH)
123123+ .unwrap()
124124+ .as_micros() as u64;
125125+ let wire = WireMessage::V0 {
126126+ timestamp,
127127+ message: message.clone(),
128128+ };
129129+ let data = postcard::to_stdvec(&wire)?;
130130+ let signature = secret_key.sign(&data);
131131+ let from = secret_key.public();
132132+ let signed = Self {
133133+ from,
134134+ data,
135135+ signature,
136136+ };
137137+ Ok(postcard::to_stdvec(&signed)?)
138138+ }
139139+140140+ /// Decode from bytes and verify signature.
141141+ pub fn decode_and_verify(bytes: &[u8]) -> Result<ReceivedMessage, SignedMessageError> {
142142+ let signed: Self = postcard::from_bytes(bytes)?;
143143+ signed
144144+ .from
145145+ .verify(&signed.data, &signed.signature)
146146+ .map_err(|_| SignedMessageError::InvalidSignature)?;
147147+ let wire: WireMessage = postcard::from_bytes(&signed.data)?;
148148+ let WireMessage::V0 { timestamp, message } = wire;
149149+ Ok(ReceivedMessage {
150150+ from: signed.from,
151151+ timestamp,
152152+ message,
153153+ })
154154+ }
137155 }
138156}
157157+158158+#[cfg(feature = "iroh")]
159159+pub use signed::{ReceivedMessage, SignedMessage, SignedMessageError};
139160140161#[cfg(test)]
141162mod tests {
+22-8
crates/weaver-common/src/transport/mod.rs
···11-//! Real-time collaboration transport layer using iroh P2P networking.
11+//! Real-time collaboration transport layer.
22//!
33-//! This module provides the infrastructure for real-time collaborative editing:
44-//! - `CollabNode`: iroh endpoint + gossip router (one per app instance)
55-//! - `CollabSession`: per-resource session management
66-//! - `CollabMessage`: wire protocol for CRDT updates, cursors, presence
77-//! - `discovery`: utilities for parsing NodeIds from session records
33+//! Core message types are always available. iroh-based networking
44+//! requires the `iroh` feature.
55+66+mod messages;
77+mod presence_types;
8899+#[cfg(feature = "iroh")]
910mod discovery;
1010-mod messages;
1111+#[cfg(feature = "iroh")]
1112mod node;
1313+#[cfg(feature = "iroh")]
1214mod presence;
1515+#[cfg(feature = "iroh")]
1316mod session;
14171818+// Always available - wire protocol
1919+pub use messages::CollabMessage;
2020+pub use presence_types::{CollaboratorInfo, PresenceSnapshot, RemoteCursorInfo};
2121+2222+// iroh feature - networking
2323+#[cfg(feature = "iroh")]
1524pub use discovery::{node_id_to_string, parse_node_id, DiscoveredPeer, DiscoveryError};
2525+#[cfg(feature = "iroh")]
1626pub use iroh::EndpointId;
1717-pub use messages::{CollabMessage, ReceivedMessage, SignedMessage, SignedMessageError};
2727+#[cfg(feature = "iroh")]
2828+pub use messages::{ReceivedMessage, SignedMessage, SignedMessageError};
2929+#[cfg(feature = "iroh")]
1830pub use node::{CollabNode, TransportError};
3131+#[cfg(feature = "iroh")]
1932pub use presence::{Collaborator, PresenceTracker, RemoteCursor};
3333+#[cfg(feature = "iroh")]
2034pub use session::{CollabSession, SessionError, SessionEvent, TopicId};
+2
crates/weaver-common/src/transport/node.rs
···11+#![cfg(feature = "iroh")]
22+13//! CollabNode - iroh endpoint with gossip router for real-time collaboration.
2435use iroh::Endpoint;
+2
crates/weaver-common/src/transport/presence.rs
···11+#![cfg(feature = "iroh")]
22+13//! Presence tracking for collaborative editing sessions.
24//!
35//! Tracks active collaborators, their cursor positions, and display info.