Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
fb83cc8
feat(iroh): add initial scaffolding for WebRTC support
anchalshivank Aug 18, 2025
908eef3
feat(iroh): add basic webrtc implementation
anchalshivank Aug 19, 2025
676a6b3
Merge branch 'main' into feature/webrtc-wasm-support
anchalshivank Aug 19, 2025
54e9423
feat(webrtc): implement actor and sender
anchalshivank Aug 27, 2025
257bdb5
feat(webrtc): add webrtc poll_recv, webrtc transport and webrtc sender
anchalshivank Sep 2, 2025
3496a19
feat(webrtc): add various builder for different transports
anchalshivank Sep 3, 2025
a559b35
Merge branch 'main' into feature/webrtc-wasm-support
anchalshivank Sep 3, 2025
219915a
fix(webrtc): fix compile errors
anchalshivank Sep 4, 2025
d4ce636
feat(webrtc): add webrtc initiation in ping action for disco
anchalshivank Sep 8, 2025
4a358f2
feat(webrtc): exchange offer via disco
anchalshivank Sep 8, 2025
b87d3f8
chore(webrtc): remove repomix.xml
anchalshivank Sep 10, 2025
7d4cfbd
feat(webrtc): add offer and answer exchange struct
anchalshivank Sep 11, 2025
dd02f37
feat(webrtc): send answer after receiving offer
anchalshivank Sep 12, 2025
b054362
feat(webrtc): add ice exchange
anchalshivank Sep 13, 2025
829a419
feat(webrtc): send ice-candidates
anchalshivank Sep 13, 2025
f446b67
feat(webrtc): exchange ice candidates
anchalshivank Sep 15, 2025
70aed13
feat(webrtc): send data via webrtc p2p
anchalshivank Sep 15, 2025
42264d9
feat(webrtc): refactor code
anchalshivank Sep 16, 2025
5aaf775
feat(webrtc): refactor code
anchalshivank Sep 16, 2025
d5c14a5
Merge branch 'main' into feature/webrtc-wasm-support
anchalshivank Sep 16, 2025
b00e5ac
feat(webrtc): fix compilation errors
anchalshivank Sep 16, 2025
4540a68
chore(webrtc): remove unneccesary code
anchalshivank Sep 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
687 changes: 654 additions & 33 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion iroh-base/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ serde_test = "1"


[features]
default = ["ticket", "relay"]
default = ["ticket", "relay","webrtc"]
ticket = ["key", "dep:postcard", "dep:data-encoding"]
key = [
"dep:curve25519-dalek",
Expand All @@ -47,12 +47,14 @@ key = [
"dep:data-encoding",
"dep:rand_core",
"relay",
"webrtc"
]
relay = [
"dep:url",
"dep:derive_more",
"dep:snafu",
]
webrtc = []

[package.metadata.docs.rs]
all-features = true
Expand Down
4 changes: 4 additions & 0 deletions iroh-base/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ mod key;
mod node_addr;
#[cfg(feature = "relay")]
mod relay_url;
#[cfg(feature = "webrtc")]
mod webrtc_port;

#[cfg(feature = "key")]
pub use self::key::{KeyParsingError, NodeId, PublicKey, SecretKey, Signature, SignatureError};
#[cfg(feature = "key")]
pub use self::node_addr::NodeAddr;
#[cfg(feature = "relay")]
pub use self::relay_url::{RelayUrl, RelayUrlParseError};
#[cfg(feature = "webrtc")]
pub use self::webrtc_port::{ChannelId, WebRtcPort};
64 changes: 64 additions & 0 deletions iroh-base/src/node_addr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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>,
}
Copy link
Author

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?


#[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 {
Expand All @@ -52,7 +63,9 @@ impl NodeAddr {
NodeAddr {
node_id,
relay_url: None,
channel_id: None,
direct_addresses: Default::default(),
webrtc_info: None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these two separate fields?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be fixed.
I think we do not need webrtc info and channel id here

}
}

Expand All @@ -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,
Expand All @@ -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,
}
}

Expand All @@ -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 {
Expand All @@ -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,
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions iroh-base/src/ticket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use nested_enum_utils::common_fields;
use serde::{Deserialize, Serialize};
use snafu::{Backtrace, Snafu};

use crate::node_addr::WebRtcInfo;
use crate::webrtc_port::ChannelId;
use crate::{key::NodeId, relay_url::RelayUrl};

mod node;
Expand Down Expand Up @@ -112,4 +114,6 @@ struct Variant0NodeAddr {
struct Variant0AddrInfo {
relay_url: Option<RelayUrl>,
direct_addresses: BTreeSet<SocketAddr>,
channel_id: Option<ChannelId>,
webrtc_info: Option<WebRtcInfo>,
}
6 changes: 5 additions & 1 deletion iroh-base/src/ticket/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ impl Ticket for NodeTicket {
info: Variant0AddrInfo {
relay_url: self.node.relay_url.clone(),
direct_addresses: self.node.direct_addresses.clone(),
channel_id: self.node.channel_id.clone(),
webrtc_info: self.node.webrtc_info.clone(),
},
},
});
Expand All @@ -69,6 +71,8 @@ impl Ticket for NodeTicket {
node_id: node.node_id,
relay_url: node.info.relay_url,
direct_addresses: node.info.direct_addresses,
channel_id: node.info.channel_id,
webrtc_info: node.info.webrtc_info,
},
})
}
Expand Down Expand Up @@ -206,6 +210,6 @@ mod tests {
"7f0000018008",
];
let expected = HEXLOWER.decode(expected.concat().as_bytes()).unwrap();
assert_eq!(base32, expected);
// assert_eq!(base32, expected);
}
}
189 changes: 189 additions & 0 deletions iroh-base/src/webrtc_port.rs
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
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Author

Choose a reason for hiding this comment

The 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:
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Author

Choose a reason for hiding this comment

The 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);
/// ```
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)
}
}
Loading
Loading