-
Notifications
You must be signed in to change notification settings - Fork 303
feat(iroh): add initial scaffolding for WebRTC support #3440
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
fb83cc8
908eef3
676a6b3
54e9423
257bdb5
3496a19
a559b35
219915a
d4ce636
4a358f2
b87d3f8
7d4cfbd
dd02f37
b054362
829a419
f446b67
70aed13
42264d9
5aaf775
d5c14a5
b00e5ac
4540a68
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ use std::{collections::BTreeSet, net::SocketAddr}; | |
|
||
use serde::{Deserialize, Serialize}; | ||
|
||
use crate::webrtc_port::ChannelId; | ||
use crate::{NodeId, PublicKey, RelayUrl}; | ||
|
||
/// Network-level addressing information for an iroh node. | ||
|
@@ -42,8 +43,18 @@ pub struct NodeAddr { | |
pub node_id: NodeId, | ||
/// The node's home relay url. | ||
pub relay_url: Option<RelayUrl>, | ||
/// The node's channel_id port | ||
pub channel_id: Option<ChannelId>, | ||
/// Socket addresses where the peer might be reached directly. | ||
pub direct_addresses: BTreeSet<SocketAddr>, | ||
/// Static Webrtc connection information for the node | ||
pub webrtc_info: Option<WebRtcInfo>, | ||
} | ||
|
||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] | ||
pub struct WebRtcInfo { | ||
/// The hash of the certificate, prefixed with the algorithm, e.g./ "sha-256, B1:....:4E" | ||
pub cert_fingerprints: BTreeSet<String>, | ||
} | ||
|
||
impl NodeAddr { | ||
|
@@ -52,7 +63,9 @@ impl NodeAddr { | |
NodeAddr { | ||
node_id, | ||
relay_url: None, | ||
channel_id: None, | ||
direct_addresses: Default::default(), | ||
webrtc_info: None, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are these two separate fields? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs to be fixed. |
||
} | ||
} | ||
|
||
|
@@ -62,6 +75,12 @@ impl NodeAddr { | |
self | ||
} | ||
|
||
/// Adds a webrtc channel id | ||
pub fn with_channel_id(mut self, channel_id: ChannelId) -> Self { | ||
self.channel_id = Some(channel_id); | ||
self | ||
} | ||
|
||
/// Adds the given direct addresses. | ||
pub fn with_direct_addresses( | ||
mut self, | ||
|
@@ -81,6 +100,39 @@ impl NodeAddr { | |
node_id, | ||
relay_url, | ||
direct_addresses: direct_addresses.into_iter().collect(), | ||
channel_id: None, | ||
webrtc_info: None, | ||
} | ||
} | ||
|
||
/// Creates a new [`NodeAddr`] from its parts | ||
pub fn from_parts_with_channel( | ||
node_id: PublicKey, | ||
relay_url: Option<RelayUrl>, | ||
channel_id: Option<ChannelId>, | ||
direct_addresses: impl IntoIterator<Item = SocketAddr>, | ||
) -> Self { | ||
Self { | ||
node_id, | ||
relay_url, | ||
channel_id, | ||
direct_addresses: direct_addresses.into_iter().collect(), | ||
webrtc_info: None, | ||
} | ||
} | ||
/// Creates a new [`NodeAddr`] from its parts | ||
pub fn from_parts_with_webrtc_info( | ||
node_id: PublicKey, | ||
relay_url: Option<RelayUrl>, | ||
webrtc_info: Option<WebRtcInfo>, | ||
direct_addresses: impl IntoIterator<Item = SocketAddr>, | ||
) -> Self { | ||
Self { | ||
node_id, | ||
relay_url, | ||
channel_id: None, | ||
direct_addresses: direct_addresses.into_iter().collect(), | ||
webrtc_info, | ||
} | ||
} | ||
|
||
|
@@ -98,6 +150,16 @@ impl NodeAddr { | |
pub fn relay_url(&self) -> Option<&RelayUrl> { | ||
self.relay_url.as_ref() | ||
} | ||
|
||
/// Returns the WebRTC channel id for this peer | ||
pub fn channel_id(&self) -> Option<&ChannelId> { | ||
self.channel_id.as_ref() | ||
} | ||
|
||
/// Returns the WebRTC info | ||
pub fn webrtc_info(&self) -> Option<&WebRtcInfo> { | ||
self.webrtc_info.as_ref() | ||
} | ||
} | ||
|
||
impl From<(PublicKey, Option<RelayUrl>, &[SocketAddr])> for NodeAddr { | ||
|
@@ -107,6 +169,8 @@ impl From<(PublicKey, Option<RelayUrl>, &[SocketAddr])> for NodeAddr { | |
node_id, | ||
relay_url, | ||
direct_addresses: direct_addresses_iter.iter().copied().collect(), | ||
channel_id: None, | ||
webrtc_info: None, | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
//! WebRTC connection identification types. | ||
//! | ||
//! This module provides types for uniquely identifying WebRTC connections in the iroh network. | ||
//! A WebRTC connection is uniquely identified by the combination of a [`NodeId`] and a | ||
//! [`WebRtcPort`], represented by the [`WebRtcPort`] type. | ||
use crate::NodeId; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
/// A unique identifier for a WebRTC connection. | ||
/// | ||
/// In the iroh network, WebRTC connections are established between nodes and need to be | ||
/// uniquely identified to handle multiple concurrent connections. A [`WebRtcPort`] combines | ||
/// a [`NodeId`] (which identifies the peer node) with a [`WebRtcPort`] (which identifies | ||
/// the specific channel/connection to that node). | ||
/// | ||
/// This is particularly useful when: | ||
/// - A node needs to maintain multiple WebRTC connections to the same peer | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When would we need to maintain multiple WebRTC connections between the same two peers? My mental model was that a single WebRTC connection would transport QUIC datagrams, which can be for any number of QUIC connections. So I wasn't expecting to ever need more than one WebRTC connection between two nodes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are correct. I will fix this |
||
/// - Routing messages to specific WebRTC channels | ||
/// - Managing connection lifecycle and cleanup | ||
/// | ||
/// # Examples | ||
/// | ||
/// ```rust | ||
/// use iroh_base::{NodeId, WebRtcPort, WebRtcPort}; | ||
/// | ||
/// // Create a new WebRTC port identifier | ||
/// let node_id = NodeId::from([1u8; 32]); | ||
/// let channel_id = WebRtcPort::from(42); | ||
/// let webrtc_port = WebRtcPort::new(node_id, channel_id); | ||
/// | ||
/// println!("WebRTC connection: {}", webrtc_port); | ||
/// // Output: WebRtcPort(NodeId(...), ChannelId(42)) | ||
/// ``` | ||
#[derive(Debug, derive_more::Display, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)] | ||
#[display("WebRtcPort({}, {})", node_id, channel_id)] | ||
pub struct WebRtcPort { | ||
/// The identifier of the peer node in this WebRTC connection. | ||
pub node_id: NodeId, | ||
/// The specific channel identifier for this WebRTC connection. | ||
pub channel_id: ChannelId, | ||
} | ||
|
||
impl PartialEq<WebRtcPort> for &mut WebRtcPort { | ||
fn eq(&self, other: &WebRtcPort) -> bool { | ||
self.eq(&other) | ||
} | ||
} | ||
|
||
impl WebRtcPort { | ||
/// Creates a new [`WebRtcPort`] from a node ID and channel ID. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `node` - The [`NodeId`] of the peer node | ||
/// * `channel_id` - The [`WebRtcPort`] identifying the specific channel | ||
/// | ||
/// # Examples | ||
/// | ||
/// ```rust | ||
/// use iroh_base::{NodeId, WebRtcPort, WebRtcPort}; | ||
/// | ||
/// let node_id = NodeId::from([1u8; 32]); | ||
/// let channel_id = WebRtcPort::from(42); | ||
/// let port = WebRtcPort::new(node_id, channel_id); | ||
/// ``` | ||
pub fn new(node: NodeId, channel_id: ChannelId) -> Self { | ||
Self { | ||
node_id: node, | ||
channel_id, | ||
} | ||
} | ||
|
||
/// Returns the node ID of this WebRTC connection. | ||
pub fn node_id(&self) -> &NodeId { | ||
&self.node_id | ||
} | ||
|
||
/// Returns the channel ID of this WebRTC connection. | ||
pub fn channel_id(&self) -> ChannelId { | ||
self.channel_id | ||
} | ||
} | ||
|
||
/// A unique identifier for a WebRTC channel. | ||
/// | ||
/// [`WebRtcPort`] is used to distinguish between multiple WebRTC data channels or connections | ||
/// to the same peer node. It's a 16-bit unsigned integer, allowing for up to 65,536 unique | ||
/// channels per node pair. | ||
/// | ||
/// The channel ID space is managed by the WebRTC implementation and should be: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If managed by the WebRTC implementation why does the example show creating this from a static number? That's confusing to me. Or is it trying to show something else? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These comments need to be corrected. The main idea is that a port should be uniquely identified by both channel_id and node_id |
||
/// - Unique per node pair during the lifetime of connections | ||
/// - Reusable after connections are closed | ||
/// - Assigned in a way that avoids collisions | ||
/// | ||
/// # Examples | ||
/// | ||
/// ```rust | ||
/// use iroh_base::WebRtcPort; | ||
/// | ||
/// // Create a channel ID | ||
/// let channel = WebRtcPort::from(1234); | ||
/// println!("Channel: {}", channel); // Output: ChannelId(1234) | ||
/// | ||
/// // Channel IDs can be compared and ordered | ||
/// let channel_a = WebRtcPort::from(1); | ||
/// let channel_b = WebRtcPort::from(2); | ||
/// assert!(channel_a < channel_b); | ||
/// ``` | ||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Copy, PartialOrd, Ord)] | ||
pub struct ChannelId(u16); | ||
|
||
impl Default for ChannelId { | ||
fn default() -> Self { | ||
ChannelId(0) | ||
} | ||
} | ||
impl ChannelId { | ||
/// Creates a new [`WebRtcPort`] from a `u16` value. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `id` - The numeric channel identifier (0-65535) | ||
/// | ||
/// # Examples | ||
/// | ||
/// ```rust | ||
/// use iroh_base::WebRtcPort; | ||
/// | ||
/// let channel = WebRtcPort::new(42); | ||
/// assert_eq!(channel.as_u16(), 42); | ||
/// ``` | ||
pub fn new(id: u16) -> Self { | ||
Self(id) | ||
} | ||
|
||
/// Returns the numeric value of this channel ID. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ```rust | ||
/// use iroh_base::WebRtcPort; | ||
/// | ||
/// let channel = WebRtcPort::from(1234); | ||
/// assert_eq!(channel.as_u16(), 1234); | ||
/// ``` | ||
pub fn as_u16(self) -> u16 { | ||
self.0 | ||
} | ||
} | ||
|
||
impl From<u16> for ChannelId { | ||
/// Creates a [`WebRtcPort`] from a `u16` value. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ```rust | ||
/// use iroh_base::WebRtcPort; | ||
/// | ||
/// let channel = WebRtcPort::from(42u16); | ||
/// assert_eq!(channel.as_u16(), 42); | ||
/// ``` | ||
anchalshivank marked this conversation as resolved.
Show resolved
Hide resolved
|
||
fn from(id: u16) -> Self { | ||
Self::new(id) | ||
} | ||
} | ||
|
||
impl From<ChannelId> for u16 { | ||
/// Converts a [`WebRtcPort`] to its numeric `u16` value. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ```rust | ||
/// use iroh_base::WebRtcPort; | ||
/// | ||
/// let channel = WebRtcPort::from(42); | ||
/// let id: u16 = channel.into(); | ||
/// assert_eq!(id, 42); | ||
/// ``` | ||
fn from(channel: ChannelId) -> Self { | ||
channel.as_u16() | ||
} | ||
} | ||
|
||
impl std::fmt::Display for ChannelId { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
write!(f, "ChannelId({})", self.0) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding:
NodeAddr is used by the discovery system to locate nodes in the network. For discovery purposes, we only need the node_id to find a node. Once found, WebRTC connections are established through the standard offer/answer exchange process.
My concern:
Since WebRTC connections require dynamic offer/answer negotiation anyway, is it necessary to store static WebRTC information like channel_id and webrtc_info (certificate fingerprints) here?