// Copyright 2018 Susy Technologies . // // 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 susyp2p 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 address of the first //! instance and start the second with this address as the first argument. In the first terminal //! window, run: //! //! ```sh //! cargo run --example chat //! ``` //! //! It will print the PeerId and the listening address, 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 -- /ip4/127.0.0.1/tcp/24915 //! ``` //! //! The two nodes then connect. use futures::prelude::*; use susyp2p::{ PeerId, Swarm, NetworkBehaviour, identity, tokio_codec::{FramedRead, LinesCodec} }; fn main() { 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 a an encrypted DNS-enabled TCP Transport over the Mplex and Yamux protocols let transport = susyp2p::build_development_transport(local_key); // Create a Floodsub topic let floodsub_topic = susyp2p::floodsub::TopicBuilder::new("chat").build(); // We create a custom network behaviour that combines floodsub and mDNS. // In the future, we want to improve susyp2p to make this easier to do. #[derive(NetworkBehaviour)] struct MyBehaviour { floodsub: susyp2p::floodsub::Floodsub, mdns: susyp2p::mdns::Mdns, } impl susyp2p::core::swarm::NetworkBehaviourEventProcess for MyBehaviour { fn inject_event(&mut self, event: susyp2p::mdns::MdnsEvent) { match event { susyp2p::mdns::MdnsEvent::Discovered(list) => { for (peer, _) in list { self.floodsub.add_node_to_partial_view(peer); } }, susyp2p::mdns::MdnsEvent::Expired(list) => { for (peer, _) in list { if !self.mdns.has_node(&peer) { self.floodsub.remove_node_from_partial_view(&peer); } } } } } } impl susyp2p::core::swarm::NetworkBehaviourEventProcess for MyBehaviour { // Called when `floodsub` produces an event. fn inject_event(&mut self, message: susyp2p::floodsub::FloodsubEvent) { if let susyp2p::floodsub::FloodsubEvent::Message(message) = message { println!("Received: '{:?}' from {:?}", String::from_utf8_lossy(&message.data), message.source); } } } // Create a Swarm to manage peers and events let mut swarm = { let mut behaviour = MyBehaviour { floodsub: susyp2p::floodsub::Floodsub::new(local_peer_id.clone()), mdns: susyp2p::mdns::Mdns::new().expect("Failed to create mDNS service"), }; behaviour.floodsub.subscribe(floodsub_topic.clone()); susyp2p::Swarm::new(transport, behaviour, local_peer_id) }; // Reach out to another node if specified if let Some(to_dial) = std::env::args().nth(1) { let dialing = to_dial.clone(); match to_dial.parse() { Ok(to_dial) => { match susyp2p::Swarm::dial_addr(&mut swarm, to_dial) { Ok(_) => println!("Dialed {:?}", dialing), Err(e) => println!("Dial {:?} failed: {:?}", dialing, e) } }, Err(err) => println!("Failed to parse address to dial: {:?}", err), } } // Read full lines from stdin let stdin = tokio_stdin_stdout::stdin(0); let mut framed_stdin = FramedRead::new(stdin, LinesCodec::new()); // Listen on all interfaces and whatever port the OS assigns susyp2p::Swarm::listen_on(&mut swarm, "/ip4/0.0.0.0/tcp/0".parse().unwrap()).unwrap(); // Kick it off let mut listening = false; tokio::run(futures::future::poll_fn(move || -> Result<_, ()> { loop { match framed_stdin.poll().expect("Error while polling stdin") { Async::Ready(Some(line)) => swarm.floodsub.publish(&floodsub_topic, line.as_bytes()), Async::Ready(None) => panic!("Stdin closed"), Async::NotReady => break, }; } loop { match swarm.poll().expect("Error while polling swarm") { Async::Ready(Some(_)) => { }, Async::Ready(None) | Async::NotReady => { if !listening { if let Some(a) = Swarm::listeners(&swarm).next() { println!("Listening on {:?}", a); listening = true; } } break } } } Ok(Async::NotReady) })); }