diff --git a/src/app/docs/examples/gossip-chat/page.mdx b/src/app/docs/examples/gossip-chat/page.mdx new file mode 100644 index 00000000..4b0953a9 --- /dev/null +++ b/src/app/docs/examples/gossip-chat/page.mdx @@ -0,0 +1,279 @@ +import { YouTube } from '@/components/youtube' + +# Building a Peer-to-Peer Chat Application in Rust + + + +# Building a P2P Chat Application with Rust and Iroh + +This tutorial demonstrates how to build a peer-to-peer chat application from scratch using Rust and the Iroh library. While this implementation is simplified, it illustrates core concepts of P2P networking and the Iroh gossip protocol. + +## Prerequisites + +The tutorial assumes basic programming knowledge but no prior Rust experience. To begin, install Rust by following the instructions at [rust-lang.org](https://rust-lang.org). + +## Project Setup + +First, initialize a new Rust project: + +```bash +cargo init iroh-gossip-chat +cd iroh-gossip-chat +cargo run +``` + +Install the required dependencies: + +```bash +cargo add iroh tokio anyhow rand +``` + +## Basic Endpoint Configuration + +The first step is creating a basic endpoint configuration: + +```rust +use anyhow::Result; +use iroh::{SecretKey, Endpoint}; +use iroh::protocol::Router; + +#[tokio::main] +async fn main() -> Result<()> { + let secret_key = SecretKey::generate(rand::rngs::OsRng); + println!("> our secret key: {secret_key}"); + + let endpoint = Endpoint::builder() + .discovery_n0() + .bind() + .await?; + + println!("> our node id: {}", endpoint.node_id()); + + Ok(()) +} +``` + +## Adding Gossip Protocol Support + +Install the gossip protocol: + +```bash +cargo add iroh-gossip +``` + +Then update the code to implement basic gossip functionality: + +```rust +use anyhow::Result; +use iroh::protocol::Router; +use iroh::{Endpoint, SecretKey}; +use iroh_gossip::net::Gossip; + +#[tokio::main] +async fn main() -> Result<()> { + let secret_key = SecretKey::generate(rand::rngs::OsRng); + println!("> our secret key: {secret_key}"); + + let endpoint = Endpoint::builder() + .secret_key(secret_key) + .discovery_n0() + .bind() + .await?; + + println!("> our node id: {}", endpoint.node_id()); + let gossip = Gossip::builder().spawn(endpoint.clone()).await?; + + let router = Router::builder(endpoint.clone()) + .accept(iroh_gossip::ALPN, gossip.clone()) + .spawn() + .await?; + + router.shutdown().await?; + + Ok(()) +} +``` + +## Creating and Broadcasting to a Topic + +Topics are the fundamental unit of communication in the gossip protocol. Here's how to create a topic and broadcast a message: + +```rust +use anyhow::Result; +use iroh::protocol::Router; +use iroh::{Endpoint, SecretKey}; +use iroh_gossip::net::Gossip; +use iroh_gossip::proto::TopicId; + +#[tokio::main] +async fn main() -> Result<()> { + let secret_key = SecretKey::generate(rand::rngs::OsRng); + println!("> our secret key: {secret_key}"); + + let endpoint = Endpoint::builder().discovery_n0().bind().await?; + + println!("> our node id: {}", endpoint.node_id()); + let gossip = Gossip::builder().spawn(endpoint.clone()).await?; + + let router = Router::builder(endpoint.clone()) + .accept(iroh_gossip::ALPN, gossip.clone()) + .spawn() + .await?; + + let id = TopicId::from_bytes(rand::random()); + let peer_ids = vec![]; + let (sender, _receiver) = gossip.subscribe(id, peer_ids)?.split(); + sender.broadcast("sup".into()).await?; + + router.shutdown().await?; + + Ok(()) +} +``` + +## Implementing Message Reception + +Install the futures-lite crate to handle async streams: + +```bash +cargo add futures-lite +``` + +Then implement message reception: + +```rust +use anyhow::Result; +use iroh::{SecretKey, Endpoint}; +use iroh::protocol::Router; +use futures_lite::StreamExt; +use iroh_gossip::{Gossip, Event, TopicId}; + +#[tokio::main] +async fn main() -> Result<()> { + // Previous endpoint setup code... + + let (sender, mut receiver) = gossip.subscribe_and_join(id, peer_ids).await?.split(); + + tokio::spawn(async move || { + while let Some(event) = receiver.try_next().await? { + if let Event::Gossip(gossip_event) = event { + match gossip_event { + GossipEvent::Received(message) => println!("got message: {:?}", &message), + _ => {} + } + } + } + }); + + sender.broadcast(b"sup").await?; + + router.shutdown().await?; + + Ok(()) +} +``` + +## Implementing Signaling with Tickets + +To enable nodes to discover and join each other, implement ticket-based signaling: + +```bash +cargo add serde data_encoding +``` + +Add the ticket implementation: + +```rust +#[derive(Debug, Serialize, Deserialize)] +struct Ticket { + topic: TopicId, + peers: Vec, +} + +impl Ticket { + fn from_bytes(bytes: &[u8]) -> Result { + serde_json::from_slice(bytes).map_err(Into::into) + } + + pub fn to_bytes(&self) -> Vec { + serde_json::to_vec(self).expect("serde_json::to_vec is infallible") + } +} + +impl fmt::Display for Ticket { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut text = data_encoding::BASE32_NOPAD.encode(&self.to_bytes()[..]); + text.make_ascii_lowercase(); + write!(f, "{}", text) + } +} + +impl FromStr for Ticket { + type Err = anyhow::Error; + fn from_str(s: &str) -> Result { + let bytes = data_encoding::BASE32_NOPAD.decode(s.to_ascii_uppercase().as_bytes())?; + Self::from_bytes(&bytes) + } +} +``` + +## Creating a Command-Line Interface + +Install the clap crate for CLI argument parsing: + +```bash +cargo add clap --features derive +``` + +The final implementation includes a full command-line interface with commands for creating and joining chat rooms: + +```rust +use std::{ + collections::HashMap, + fmt, + net::{Ipv4Addr, SocketAddrV4}, + str::FromStr, +}; + +#[derive(Parser, Debug)] +struct Args { + #[clap(long)] + no_relay: bool, + #[clap(short, long)] + name: Option, + #[clap(subcommand)] + command: Command, +} + +#[derive(Parser, Debug)] +enum Command { + Open, + Join { + ticket: String, + }, +} + +// Main function implementation with CLI command handling... +``` + +## Running the Application + +To create a new chat room: + +```bash +cargo run -- --name user1 open +``` + +To join an existing chat room: + +```bash +cargo run -- --name user2 join +``` + +The application will now support basic chat functionality between connected peers, with messages broadcast to all participants in the room. + +## Notes on Security + +While this implementation demonstrates the basic concepts, a production system would need additional security measures. For example, the example in the Iroh gossip protocol repository includes message signing to prevent impersonation attacks. + +For more sophisticated implementations and security features, refer to the examples in the Iroh gossip protocol repository. diff --git a/src/components/Examples.jsx b/src/components/Examples.jsx index a97eb255..56d91b98 100644 --- a/src/components/Examples.jsx +++ b/src/components/Examples.jsx @@ -10,6 +10,20 @@ import {UsersIcon} from '@/components/icons/UsersIcon'; import { Tag } from './Tag'; const examples = [ + { + href: '/docs/examples/gossip-chat', + name: 'Gossip Chat', + description: + 'A simple chat app using iroh-net gossip connections.', + tags: ["gossip", "CLI"], + pattern: { + y: 16, + squares: [ + [0, 1], + [1, 3], + ], + }, + }, { // TODO: finish TODOs docs page, switch this href for "/docs/examples/todos" href: 'https://github.com/n0-computer/iroh-examples/tree/main/tauri-todos', diff --git a/src/components/youtube.jsx b/src/components/youtube.jsx new file mode 100644 index 00000000..121e981c --- /dev/null +++ b/src/components/youtube.jsx @@ -0,0 +1,15 @@ + + +export function YouTube({ src }) { + return ( +
+