// Copyright 2018 Parity Technologies (UK) Ltd. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. //! A basic chat application demonstrating libp2p and the mDNS and floodsub protocols. //! //! Using two terminal windows, start two instances. If you local network allows mDNS, //! they will automatically connect. Type a message in either terminal and hit return: the //! message is sent and printed in the other terminal. Close with Ctrl-c. //! //! You can of course open more terminal windows and add more participants. //! Dialing any of the other peers will propagate the new participant to all //! chat members and everyone will receive all messages. //! //! # If they don't automatically connect //! //! If the nodes don't automatically connect, take note of the listening addresses of the first //! instance and start the second with one of the addresses as the first argument. In the first //! terminal window, run: //! //! ```sh //! cargo run --example chat --features=full //! ``` //! //! It will print the PeerId and the listening addresses, e.g. `Listening on //! "/ip4/0.0.0.0/tcp/24915"` //! //! In the second terminal window, start a new instance of the example with: //! //! ```sh //! cargo run --example chat --features=full -- /ip4/127.0.0.1/tcp/24915 //! ``` //! //! The two nodes then connect. use async_std::io; use futures::{ prelude::{stream::StreamExt, *}, select, }; use libp2p::{ floodsub::{self, Floodsub, FloodsubEvent}, identity, mdns, swarm::{NetworkBehaviour, SwarmEvent}, Multiaddr, PeerId, Swarm, }; use std::error::Error; #[async_std::main] async fn main() -> Result<(), Box> { env_logger::init(); // Create a random PeerId let local_key = identity::Keypair::generate_ed25519(); let local_peer_id = PeerId::from(local_key.public()); println!("Local peer id: {local_peer_id:?}"); // Set up an encrypted DNS-enabled TCP Transport over the Mplex and Yamux protocols let transport = libp2p::development_transport(local_key).await?; // Create a Floodsub topic let floodsub_topic = floodsub::Topic::new("chat"); // We create a custom network behaviour that combines floodsub and mDNS. // Use the derive to generate delegating NetworkBehaviour impl. #[derive(NetworkBehaviour)] #[behaviour(out_event = "OutEvent")] struct MyBehaviour { floodsub: Floodsub, mdns: mdns::async_io::Behaviour, } #[allow(clippy::large_enum_variant)] #[derive(Debug)] enum OutEvent { Floodsub(FloodsubEvent), Mdns(mdns::Event), } impl From for OutEvent { fn from(v: mdns::Event) -> Self { Self::Mdns(v) } } impl From for OutEvent { fn from(v: FloodsubEvent) -> Self { Self::Floodsub(v) } } // Create a Swarm to manage peers and events let mut swarm = { let mdns = mdns::async_io::Behaviour::new(mdns::Config::default(), local_peer_id)?; let mut behaviour = MyBehaviour { floodsub: Floodsub::new(local_peer_id), mdns, }; behaviour.floodsub.subscribe(floodsub_topic.clone()); Swarm::with_threadpool_executor(transport, behaviour, local_peer_id) }; // Reach out to another node if specified if let Some(to_dial) = std::env::args().nth(1) { let addr: Multiaddr = to_dial.parse()?; swarm.dial(addr)?; println!("Dialed {to_dial:?}") } // Read full lines from stdin let mut stdin = io::BufReader::new(io::stdin()).lines().fuse(); // Listen on all interfaces and whatever port the OS assigns swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?; // Kick it off loop { select! { line = stdin.select_next_some() => swarm .behaviour_mut() .floodsub .publish(floodsub_topic.clone(), line.expect("Stdin not to close").as_bytes()), event = swarm.select_next_some() => match event { SwarmEvent::NewListenAddr { address, .. } => { println!("Listening on {address:?}"); } SwarmEvent::Behaviour(OutEvent::Floodsub( FloodsubEvent::Message(message) )) => { println!( "Received: '{:?}' from {:?}", String::from_utf8_lossy(&message.data), message.source ); } SwarmEvent::Behaviour(OutEvent::Mdns( mdns::Event::Discovered(list) )) => { for (peer, _) in list { swarm .behaviour_mut() .floodsub .add_node_to_partial_view(peer); } } SwarmEvent::Behaviour(OutEvent::Mdns(mdns::Event::Expired( list ))) => { for (peer, _) in list { if !swarm.behaviour_mut().mdns.has_node(&peer) { swarm .behaviour_mut() .floodsub .remove_node_from_partial_view(&peer); } } }, _ => {} } } } }