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}