p2p/webrtc/signal.rs
1//! WebRTC Signaling Data Structures
2//!
3//! This module defines the core signaling data structures used in OpenMina's WebRTC
4//! peer-to-peer communication system. It provides the message types for WebRTC
5//! connection establishment, including offers, answers, and connection responses.
6//!
7//! ## Overview
8//!
9//! WebRTC requires a signaling mechanism to exchange connection metadata between peers
10//! before establishing a direct peer-to-peer connection. This module defines the
11//! data structures for:
12//!
13//! - **Offers**: Initial connection requests containing SDP data and peer information
14//! - **Answers**: Responses to offers containing SDP data and identity verification
15//! - **Connection Responses**: Acceptance, rejection, and error handling for connections
16//! - **Encryption Support**: Encrypted versions of signaling messages for security
17//!
18//! ## WebRTC Signaling Flow
19//!
20//! 1. Peer A creates an [`Offer`] with SDP data, chain ID, and target peer information
21//! 2. The offer is transmitted through a signaling method (HTTP, WebSocket, etc.)
22//! 3. Peer B receives and validates the offer (chain ID, peer ID, capacity)
23//! 4. Peer B responds with a [`P2pConnectionResponse`]:
24//! - [`P2pConnectionResponse::Accepted`] with an [`Answer`] containing SDP data
25//! - [`P2pConnectionResponse::Rejected`] with a [`RejectionReason`]
26//! - Error variants for decryption failures or internal errors
27//! 5. If accepted, both peers use the SDP data to establish the WebRTC connection
28//! 6. Connection authentication occurs using [`ConnectionAuth`] derived from SDP hashes
29//!
30//! ## Security Features
31//!
32//! - **Chain ID Verification**: Ensures peers are on the same blockchain network
33//! - **Identity Authentication**: Uses public key cryptography to verify peer identity
34//! - **Encryption Support**: Messages can be encrypted using [`EncryptedOffer`] and [`EncryptedAnswer`]
35//! - **Connection Authentication**: SDP hashes used for secure handshake verification
36
37use binprot_derive::{BinProtRead, BinProtWrite};
38use derive_more::From;
39use malloc_size_of_derive::MallocSizeOf;
40use openmina_core::ChainId;
41use serde::{Deserialize, Serialize};
42
43use crate::identity::{EncryptableType, PeerId, PublicKey};
44
45use super::{ConnectionAuth, Host};
46
47/// WebRTC connection offer containing SDP data and peer information.
48///
49/// An `Offer` represents the initial connection request in the WebRTC signaling process.
50/// It contains all necessary information for a peer to evaluate and potentially accept
51/// a WebRTC connection, including:
52///
53/// - **SDP (Session Description Protocol)** data describing the connection capabilities
54/// - **Chain ID** to ensure peers are on the same blockchain network
55/// - **Identity verification** through the offerer's public key
56/// - **Target peer identification** to ensure the offer reaches the intended recipient
57/// - **Signaling server information** for connection establishment
58///
59/// # Security Considerations
60///
61/// - The `chain_id` must match between peers to prevent cross-chain connections
62/// - The `identity_pub_key` is used for cryptographic verification of the offerer
63/// - The `target_peer_id` prevents offers from being accepted by unintended peers
64///
65/// # Example Flow
66///
67/// ```text
68/// Peer A creates Offer -> Signaling Method -> Peer B validates Offer
69/// ```
70#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, MallocSizeOf)]
71pub struct Offer {
72 /// Session Description Protocol (SDP) data describing the WebRTC connection
73 /// capabilities, including media formats, network information, and ICE candidates.
74 pub sdp: String,
75
76 /// Blockchain network identifier to ensure peers are on the same chain.
77 /// Prevents accidental connections between different blockchain networks.
78 #[ignore_malloc_size_of = "doesn't allocate"]
79 pub chain_id: ChainId,
80
81 /// Offerer's identity public key for cryptographic authentication.
82 /// Used to verify the identity of the peer making the connection offer.
83 #[ignore_malloc_size_of = "doesn't allocate"]
84 pub identity_pub_key: PublicKey,
85
86 /// Peer ID that the offerer wants to connect to.
87 /// Ensures offers are only accepted by the intended target peer.
88 pub target_peer_id: PeerId,
89
90 // TODO(binier): remove host and get ip from ice candidates instead
91 /// Host name or IP address of the signaling server of the offerer.
92 /// Used for signaling server discovery and connection establishment.
93 #[ignore_malloc_size_of = "neglectible"]
94 pub host: Host,
95
96 /// Port number of the signaling server of the offerer.
97 /// Optional port for signaling server connections.
98 pub listen_port: Option<u16>,
99}
100
101/// WebRTC connection answer responding to an offer.
102///
103/// An `Answer` is sent in response to an [`Offer`] when a peer accepts a WebRTC
104/// connection request. It contains the answering peer's SDP data and identity
105/// information necessary to complete the WebRTC connection establishment.
106///
107/// The answer includes:
108/// - **SDP data** from the answering peer describing their connection capabilities
109/// - **Identity verification** through the answerer's public key
110/// - **Target confirmation** ensuring the answer reaches the original offerer
111///
112/// # Connection Process
113///
114/// After an answer is received, both peers have exchanged SDP data and can proceed
115/// with ICE negotiation to establish the direct WebRTC connection. The SDP data
116/// from both the offer and answer is used to create connection authentication
117/// credentials via [`ConnectionAuth`].
118///
119/// # Example Flow
120///
121/// ```text
122/// Peer B receives Offer -> Validates -> Creates Answer -> Peer A receives Answer
123/// ```
124#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, MallocSizeOf)]
125pub struct Answer {
126 /// Session Description Protocol (SDP) data from the answering peer
127 /// describing their WebRTC connection capabilities and network information.
128 pub sdp: String,
129
130 /// Answering peer's identity public key for cryptographic authentication.
131 /// Used to verify the identity of the peer responding to the connection offer.
132 #[ignore_malloc_size_of = "doesn't allocate"]
133 pub identity_pub_key: PublicKey,
134
135 /// Peer ID of the original offerer that this answer is responding to.
136 /// Ensures the answer reaches the correct peer that initiated the connection.
137 pub target_peer_id: PeerId,
138}
139
140/// Union type for WebRTC signaling messages.
141///
142/// `Signal` represents the different types of signaling messages that can be
143/// exchanged during WebRTC connection establishment. It provides a unified
144/// interface for handling both connection offers and answers.
145///
146/// # Variants
147///
148/// - [`Signal::Offer`] - Initial connection request with SDP data and peer information
149/// - [`Signal::Answer`] - Response to an offer with answering peer's SDP data
150///
151/// This enum is typically used in signaling transport layers to handle different
152/// message types uniformly while preserving their specific data structures.
153#[derive(Serialize, Deserialize, From, Eq, PartialEq, Debug, Clone)]
154pub enum Signal {
155 /// A WebRTC connection offer containing SDP data and peer information.
156 Offer(Offer),
157 /// A WebRTC connection answer responding to an offer.
158 Answer(Answer),
159}
160
161/// Reasons why a WebRTC connection offer might be rejected.
162///
163/// `RejectionReason` provides detailed information about why a peer rejected
164/// a connection offer. This enables proper error handling and helps with
165/// debugging connection issues.
166///
167/// The rejection reasons are categorized into different types of validation
168/// failures that can occur during the offer evaluation process.
169///
170/// # Classification
171///
172/// Some rejection reasons are considered "bad" (potentially indicating malicious
173/// behavior or protocol violations) while others are normal operational conditions.
174/// Use [`RejectionReason::is_bad`] to determine if a rejection indicates a problem.
175#[derive(
176 Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Copy, thiserror::Error, MallocSizeOf,
177)]
178pub enum RejectionReason {
179 /// The offering peer is on a different blockchain network.
180 ///
181 /// This is a normal rejection reason that occurs when peers from different
182 /// blockchain networks attempt to connect. Not considered a "bad" rejection.
183 #[error("peer is on a different chain")]
184 ChainIdMismatch,
185
186 /// The peer ID doesn't match the peer's public key.
187 ///
188 /// This indicates a potential security issue or protocol violation where
189 /// the claimed peer identity doesn't match the cryptographic identity.
190 /// Considered a "bad" rejection that may indicate malicious behavior.
191 #[error("peer_id does not match peer's public key")]
192 PeerIdAndPublicKeyMismatch,
193
194 /// The target peer ID in the offer doesn't match the local node's peer ID.
195 ///
196 /// This indicates the offer was sent to the wrong peer or there was an
197 /// error in peer discovery. Considered a "bad" rejection.
198 #[error("target peer_id is not local node's peer_id")]
199 TargetPeerIdNotMe,
200
201 /// The local node has reached its maximum peer capacity.
202 ///
203 /// This is a normal operational condition when a node is at its connection
204 /// limit. Not considered a "bad" rejection.
205 #[error("too many peers")]
206 PeerCapacityFull,
207
208 /// A connection to this peer already exists.
209 ///
210 /// This prevents duplicate connections to the same peer. Considered a
211 /// "bad" rejection as it may indicate connection management issues.
212 #[error("peer already connected")]
213 AlreadyConnected,
214
215 /// The peer is attempting to connect to itself.
216 ///
217 /// This is a normal condition that can occur during peer discovery.
218 /// Not considered a "bad" rejection.
219 #[error("self connection detected")]
220 ConnectingToSelf,
221}
222
223/// Response to a WebRTC connection offer.
224///
225/// `P2pConnectionResponse` represents the different possible responses to a WebRTC
226/// connection offer. It encapsulates the outcome of offer validation and processing,
227/// providing detailed information about acceptance, rejection, or error conditions.
228///
229/// # Response Types
230///
231/// - **Accepted**: The offer was validated and accepted, includes an [`Answer`]
232/// - **Rejected**: The offer was rejected with a specific reason
233/// - **SignalDecryptionFailed**: The encrypted offer could not be decrypted
234/// - **InternalError**: An internal error occurred during offer processing
235///
236/// # Usage
237///
238/// This enum is typically used in signaling servers and peer connection handlers
239/// to communicate the result of offer processing back to the offering peer.
240///
241/// # Example Flow
242///
243/// ```text
244/// Offer received -> Validation -> P2pConnectionResponse sent back
245/// ```
246#[derive(Serialize, Deserialize, Debug, Clone)]
247pub enum P2pConnectionResponse {
248 /// The connection offer was accepted.
249 ///
250 /// Contains an [`Answer`] with the accepting peer's SDP data and identity
251 /// information. The boxed answer reduces memory usage for the enum.
252 Accepted(Box<Answer>),
253
254 /// The connection offer was rejected.
255 ///
256 /// Contains a [`RejectionReason`] providing specific details about why
257 /// the offer was rejected, enabling proper error handling and debugging.
258 Rejected(RejectionReason),
259
260 /// Failed to decrypt the signaling message.
261 ///
262 /// This occurs when an encrypted offer cannot be decrypted, potentially
263 /// due to incorrect encryption keys or corrupted data.
264 SignalDecryptionFailed,
265
266 /// An internal error occurred during offer processing.
267 ///
268 /// This is a catch-all for unexpected errors that occur during offer
269 /// validation or answer generation.
270 InternalError,
271}
272
273/// Computes SHA-256 hash of SDP (Session Description Protocol) data.
274///
275/// This function creates a cryptographic hash of SDP data that is used for
276/// connection authentication. The hash serves as a tamper-evident fingerprint
277/// of the connection parameters and is used in the WebRTC handshake process.
278///
279/// # Parameters
280///
281/// * `sdp` - The SDP string to hash
282///
283/// # Returns
284///
285/// A 32-byte SHA-256 hash of the SDP data
286///
287/// # Security
288///
289/// The SHA-256 hash ensures that any modification to the SDP data will be
290/// detected during the connection authentication process, preventing
291/// man-in-the-middle attacks on the WebRTC handshake.
292fn sdp_hash(sdp: &str) -> [u8; 32] {
293 use sha2::{Digest, Sha256};
294 let mut hasher = Sha256::new();
295 hasher.update(sdp);
296 hasher.finalize().into()
297}
298
299impl Offer {
300 /// Computes the SHA-256 hash of this offer's SDP data.
301 ///
302 /// This hash is used for connection authentication and tamper detection
303 /// during the WebRTC handshake process.
304 ///
305 /// # Returns
306 ///
307 /// A 32-byte SHA-256 hash of the offer's SDP data
308 pub fn sdp_hash(&self) -> [u8; 32] {
309 sdp_hash(&self.sdp)
310 }
311
312 /// Creates connection authentication data from this offer and an answer.
313 ///
314 /// This method combines the SDP data from both the offer and answer to
315 /// create [`ConnectionAuth`] credentials used for secure handshake verification.
316 /// The authentication data ensures both peers have the same view of the
317 /// connection parameters.
318 ///
319 /// # Parameters
320 ///
321 /// * `answer` - The answer responding to this offer
322 ///
323 /// # Returns
324 ///
325 /// [`ConnectionAuth`] containing encrypted authentication data derived
326 /// from both the offer and answer SDP hashes
327 pub fn conn_auth(&self, answer: &Answer) -> ConnectionAuth {
328 ConnectionAuth::new(self, answer)
329 }
330}
331
332impl Answer {
333 /// Computes the SHA-256 hash of this answer's SDP data.
334 ///
335 /// This hash is used for connection authentication and tamper detection
336 /// during the WebRTC handshake process, complementing the offer's SDP hash.
337 ///
338 /// # Returns
339 ///
340 /// A 32-byte SHA-256 hash of the answer's SDP data
341 pub fn sdp_hash(&self) -> [u8; 32] {
342 sdp_hash(&self.sdp)
343 }
344}
345
346impl RejectionReason {
347 /// Determines if this rejection reason indicates a potential problem.
348 ///
349 /// Some rejection reasons are normal operational conditions (like capacity
350 /// limits or chain ID mismatches), while others may indicate protocol
351 /// violations, security issues, or implementation bugs.
352 ///
353 /// # Returns
354 ///
355 /// * `true` if the rejection indicates a potentially problematic condition
356 /// * `false` if the rejection is a normal operational condition
357 ///
358 /// # "Bad" Rejection Reasons
359 ///
360 /// - [`PeerIdAndPublicKeyMismatch`] - Identity verification failure
361 /// - [`TargetPeerIdNotMe`] - Targeting error or discovery issue
362 /// - [`AlreadyConnected`] - Connection management issue
363 ///
364 /// # Normal Rejection Reasons
365 ///
366 /// - [`ChainIdMismatch`] - Cross-chain connection attempt
367 /// - [`PeerCapacityFull`] - Resource limitation
368 /// - [`ConnectingToSelf`] - Self-connection detection
369 ///
370 /// [`PeerIdAndPublicKeyMismatch`]: RejectionReason::PeerIdAndPublicKeyMismatch
371 /// [`TargetPeerIdNotMe`]: RejectionReason::TargetPeerIdNotMe
372 /// [`AlreadyConnected`]: RejectionReason::AlreadyConnected
373 /// [`ChainIdMismatch`]: RejectionReason::ChainIdMismatch
374 /// [`PeerCapacityFull`]: RejectionReason::PeerCapacityFull
375 /// [`ConnectingToSelf`]: RejectionReason::ConnectingToSelf
376 pub fn is_bad(&self) -> bool {
377 match self {
378 Self::ChainIdMismatch => false,
379 Self::PeerIdAndPublicKeyMismatch => true,
380 Self::TargetPeerIdNotMe => true,
381 Self::PeerCapacityFull => false,
382 Self::AlreadyConnected => true,
383 Self::ConnectingToSelf => false,
384 }
385 }
386}
387
388impl P2pConnectionResponse {
389 /// Returns the string representation of the internal error response.
390 ///
391 /// This is used for consistent error messaging across the system when
392 /// internal errors occur during connection processing.
393 pub fn internal_error_str() -> &'static str {
394 "InternalError"
395 }
396
397 /// Returns the JSON string representation of the internal error response.
398 ///
399 /// This provides a properly quoted JSON string for the internal error
400 /// response, used in JSON serialization contexts.
401 pub fn internal_error_json_str() -> &'static str {
402 "\"InternalError\""
403 }
404}
405
406/// Encrypted WebRTC offer for secure signaling.
407///
408/// `EncryptedOffer` wraps an [`Offer`] that has been encrypted for secure
409/// transmission through untrusted signaling channels. This provides confidentiality
410/// for the offer data including SDP information and peer identities.
411///
412/// The encrypted data is stored as a byte vector and can be transmitted through
413/// any signaling method while maintaining security properties.
414#[derive(BinProtWrite, BinProtRead, Serialize, Deserialize, From, Debug, Clone)]
415pub struct EncryptedOffer(Vec<u8>);
416
417/// Encrypted WebRTC connection response for secure signaling.
418///
419/// `EncryptedAnswer` wraps a [`P2pConnectionResponse`] that has been encrypted
420/// for secure transmission. This ensures that connection responses, including
421/// answers with SDP data, are protected during transmission through untrusted
422/// signaling channels.
423///
424/// The encrypted data maintains confidentiality of the response while allowing
425/// transmission through any signaling transport method.
426#[derive(BinProtWrite, BinProtRead, Serialize, Deserialize, From, Debug, Clone)]
427pub struct EncryptedAnswer(Vec<u8>);
428
429impl AsRef<[u8]> for EncryptedOffer {
430 /// Provides access to the underlying encrypted data as a byte slice.
431 fn as_ref(&self) -> &[u8] {
432 self.0.as_ref()
433 }
434}
435
436impl AsRef<[u8]> for EncryptedAnswer {
437 /// Provides access to the underlying encrypted data as a byte slice.
438 fn as_ref(&self) -> &[u8] {
439 self.0.as_ref()
440 }
441}
442
443impl EncryptableType for Offer {
444 /// Associates [`Offer`] with its encrypted counterpart [`EncryptedOffer`].
445 type Encrypted = EncryptedOffer;
446}
447
448impl EncryptableType for P2pConnectionResponse {
449 /// Associates [`P2pConnectionResponse`] with its encrypted counterpart [`EncryptedAnswer`].
450 type Encrypted = EncryptedAnswer;
451}