···001//! Peer discovery via AT Protocol session records.
2//!
3//! Collaborators publish `sh.weaver.collab.session` records to their PDS
···1+#![cfg(feature = "iroh")]
2+3//! Peer discovery via AT Protocol session records.
4//!
5//! Collaborators publish `sh.weaver.collab.session` records to their PDS
+84-63
crates/weaver-common/src/transport/messages.rs
···1//! Wire protocol for collaborative editing messages.
23-use iroh::{PublicKey, SecretKey, Signature};
4use serde::{Deserialize, Serialize};
56/// Messages exchanged between collaborators over gossip.
···65 }
66}
6768-/// A signed message wrapper for authenticated transport.
69-///
70-/// Includes the sender's public key so receivers can verify without context.
71-#[derive(Debug, Clone, Serialize, Deserialize)]
72-pub struct SignedMessage {
73- /// Sender's public key (also their EndpointId).
74- pub from: PublicKey,
75- /// The serialized TimestampedMessage (postcard bytes).
76- pub data: Vec<u8>,
77- /// Ed25519 signature over data.
78- pub signature: Signature,
79-}
8081-/// Versioned wire format with timestamp.
82-#[derive(Debug, Clone, Serialize, Deserialize)]
83-enum WireMessage {
84- V0 { timestamp: u64, message: CollabMessage },
85-}
8687-/// A verified message with sender and timestamp info.
88-#[derive(Debug, Clone)]
89-pub struct ReceivedMessage {
90- /// Sender's public key.
91- pub from: PublicKey,
92- /// When the message was sent (micros since epoch).
93- pub timestamp: u64,
94- /// The decoded message.
95- pub message: CollabMessage,
96-}
9798-/// Error type for signed message operations.
99-#[derive(Debug, thiserror::Error)]
100-pub enum SignedMessageError {
101- #[error("serialization failed: {0}")]
102- Serialization(#[from] postcard::Error),
103- #[error("signature verification failed")]
104- InvalidSignature,
105-}
106107-impl SignedMessage {
108- /// Sign a message and encode to bytes for wire transmission.
109- pub fn sign_and_encode(secret_key: &SecretKey, message: &CollabMessage) -> Result<Vec<u8>, SignedMessageError> {
110- use web_time::SystemTime;
000000111112- let timestamp = SystemTime::now()
113- .duration_since(SystemTime::UNIX_EPOCH)
114- .unwrap()
115- .as_micros() as u64;
116- let wire = WireMessage::V0 { timestamp, message: message.clone() };
117- let data = postcard::to_stdvec(&wire)?;
118- let signature = secret_key.sign(&data);
119- let from = secret_key.public();
120- let signed = Self { from, data, signature };
121- Ok(postcard::to_stdvec(&signed)?)
122 }
123124- /// Decode from bytes and verify signature.
125- pub fn decode_and_verify(bytes: &[u8]) -> Result<ReceivedMessage, SignedMessageError> {
126- let signed: Self = postcard::from_bytes(bytes)?;
127- signed.from
128- .verify(&signed.data, &signed.signature)
129- .map_err(|_| SignedMessageError::InvalidSignature)?;
130- let wire: WireMessage = postcard::from_bytes(&signed.data)?;
131- let WireMessage::V0 { timestamp, message } = wire;
132- Ok(ReceivedMessage {
133- from: signed.from,
134- timestamp,
135- message,
136- })
00000000000000000000000000000137 }
138}
000139140#[cfg(test)]
141mod tests {
···1//! Wire protocol for collaborative editing messages.
203use serde::{Deserialize, Serialize};
45/// Messages exchanged between collaborators over gossip.
···64 }
65}
6667+// ============================================================================
68+// Signed message wrapper (requires iroh for crypto)
69+// ============================================================================
0000000007071+#[cfg(feature = "iroh")]
72+mod signed {
73+ use super::*;
74+ use iroh::{PublicKey, SecretKey, Signature};
07576+ /// A signed message wrapper for authenticated transport.
77+ #[derive(Debug, Clone, Serialize, Deserialize)]
78+ pub struct SignedMessage {
79+ /// Sender's public key (also their EndpointId).
80+ pub from: PublicKey,
81+ /// The serialized TimestampedMessage (postcard bytes).
82+ pub data: Vec<u8>,
83+ /// Ed25519 signature over data.
84+ pub signature: Signature,
85+ }
8687+ /// Versioned wire format with timestamp.
88+ #[derive(Debug, Clone, Serialize, Deserialize)]
89+ enum WireMessage {
90+ V0 { timestamp: u64, message: CollabMessage },
91+ }
0009293+ /// A verified message with sender and timestamp info.
94+ #[derive(Debug, Clone)]
95+ pub struct ReceivedMessage {
96+ /// Sender's public key.
97+ pub from: PublicKey,
98+ /// When the message was sent (micros since epoch).
99+ pub timestamp: u64,
100+ /// The decoded message.
101+ pub message: CollabMessage,
102+ }
103104+ /// Error type for signed message operations.
105+ #[derive(Debug, thiserror::Error)]
106+ pub enum SignedMessageError {
107+ #[error("serialization failed: {0}")]
108+ Serialization(#[from] postcard::Error),
109+ #[error("signature verification failed")]
110+ InvalidSignature,
000111 }
112113+ impl SignedMessage {
114+ /// Sign a message and encode to bytes for wire transmission.
115+ pub fn sign_and_encode(
116+ secret_key: &SecretKey,
117+ message: &CollabMessage,
118+ ) -> Result<Vec<u8>, SignedMessageError> {
119+ use web_time::SystemTime;
120+121+ let timestamp = SystemTime::now()
122+ .duration_since(SystemTime::UNIX_EPOCH)
123+ .unwrap()
124+ .as_micros() as u64;
125+ let wire = WireMessage::V0 {
126+ timestamp,
127+ message: message.clone(),
128+ };
129+ let data = postcard::to_stdvec(&wire)?;
130+ let signature = secret_key.sign(&data);
131+ let from = secret_key.public();
132+ let signed = Self {
133+ from,
134+ data,
135+ signature,
136+ };
137+ Ok(postcard::to_stdvec(&signed)?)
138+ }
139+140+ /// Decode from bytes and verify signature.
141+ pub fn decode_and_verify(bytes: &[u8]) -> Result<ReceivedMessage, SignedMessageError> {
142+ let signed: Self = postcard::from_bytes(bytes)?;
143+ signed
144+ .from
145+ .verify(&signed.data, &signed.signature)
146+ .map_err(|_| SignedMessageError::InvalidSignature)?;
147+ let wire: WireMessage = postcard::from_bytes(&signed.data)?;
148+ let WireMessage::V0 { timestamp, message } = wire;
149+ Ok(ReceivedMessage {
150+ from: signed.from,
151+ timestamp,
152+ message,
153+ })
154+ }
155 }
156}
157+158+#[cfg(feature = "iroh")]
159+pub use signed::{ReceivedMessage, SignedMessage, SignedMessageError};
160161#[cfg(test)]
162mod tests {
+22-8
crates/weaver-common/src/transport/mod.rs
···1-//! Real-time collaboration transport layer using iroh P2P networking.
2//!
3-//! This module provides the infrastructure for real-time collaborative editing:
4-//! - `CollabNode`: iroh endpoint + gossip router (one per app instance)
5-//! - `CollabSession`: per-resource session management
6-//! - `CollabMessage`: wire protocol for CRDT updates, cursors, presence
7-//! - `discovery`: utilities for parsing NodeIds from session records
809mod discovery;
10-mod messages;
11mod node;
012mod presence;
013mod session;
1400000015pub use discovery::{node_id_to_string, parse_node_id, DiscoveredPeer, DiscoveryError};
016pub use iroh::EndpointId;
17-pub use messages::{CollabMessage, ReceivedMessage, SignedMessage, SignedMessageError};
0018pub use node::{CollabNode, TransportError};
019pub use presence::{Collaborator, PresenceTracker, RemoteCursor};
020pub use session::{CollabSession, SessionError, SessionEvent, TopicId};
···1+//! Real-time collaboration transport layer.
2//!
3+//! Core message types are always available. iroh-based networking
4+//! requires the `iroh` feature.
5+6+mod messages;
7+mod presence_types;
89+#[cfg(feature = "iroh")]
10mod discovery;
11+#[cfg(feature = "iroh")]
12mod node;
13+#[cfg(feature = "iroh")]
14mod presence;
15+#[cfg(feature = "iroh")]
16mod session;
1718+// Always available - wire protocol
19+pub use messages::CollabMessage;
20+pub use presence_types::{CollaboratorInfo, PresenceSnapshot, RemoteCursorInfo};
21+22+// iroh feature - networking
23+#[cfg(feature = "iroh")]
24pub use discovery::{node_id_to_string, parse_node_id, DiscoveredPeer, DiscoveryError};
25+#[cfg(feature = "iroh")]
26pub use iroh::EndpointId;
27+#[cfg(feature = "iroh")]
28+pub use messages::{ReceivedMessage, SignedMessage, SignedMessageError};
29+#[cfg(feature = "iroh")]
30pub use node::{CollabNode, TransportError};
31+#[cfg(feature = "iroh")]
32pub use presence::{Collaborator, PresenceTracker, RemoteCursor};
33+#[cfg(feature = "iroh")]
34pub use session::{CollabSession, SessionError, SessionEvent, TopicId};
+2
crates/weaver-common/src/transport/node.rs
···001//! CollabNode - iroh endpoint with gossip router for real-time collaboration.
23use iroh::Endpoint;
···1+#![cfg(feature = "iroh")]
2+3//! CollabNode - iroh endpoint with gossip router for real-time collaboration.
45use iroh::Endpoint;
+2
crates/weaver-common/src/transport/presence.rs
···001//! Presence tracking for collaborative editing sessions.
2//!
3//! Tracks active collaborators, their cursor positions, and display info.
···1+#![cfg(feature = "iroh")]
2+3//! Presence tracking for collaborative editing sessions.
4//!
5//! Tracks active collaborators, their cursor positions, and display info.