// Copyright 2021 Protocol Labs. // // 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. //! # File sharing example //! //! Basic file sharing application with peers either providing or locating and //! getting files by name. //! //! While obviously showcasing how to build a basic file sharing application, //! the actual goal of this example is **to show how to integrate rust-libp2p //! into a larger application**. //! //! ## Sample plot //! //! Assuming there are 3 nodes, A, B and C. A and B each provide a file while C //! retrieves a file. //! //! Provider nodes A and B each provide a file, file FA and FB respectively. //! They do so by advertising themselves as a provider for their file on a DHT //! via [`libp2p-kad`]. The two, among other nodes of the network, are //! interconnected via the DHT. //! //! Node C can locate the providers for file FA or FB on the DHT via //! [`libp2p-kad`] without being connected to the specific node providing the //! file, but any node of the DHT. Node C then connects to the corresponding //! node and requests the file content of the file via //! [`libp2p-request-response`]. //! //! ## Architectural properties //! //! - Clean clonable async/await interface ([`Client`](network::Client)) to interact with the //! network layer. //! //! - Single task driving the network layer, no locks required. //! //! ## Usage //! //! A two node setup with one node providing the file and one node requesting the file. //! //! 1. Run command below in one terminal. //! //! ```sh //! cargo run -- --listen-address /ip4/127.0.0.1/tcp/40837 \ //! --secret-key-seed 1 \ //! provide \ //! --path \ //! --name //! ``` //! //! 2. Run command below in another terminal. //! //! ```sh //! cargo run -- --peer /ip4/127.0.0.1/tcp/40837/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X \ //! get \ //! --name //! ``` //! //! Note: The client does not need to be directly connected to the providing //! peer, as long as both are connected to some node on the same DHT. mod network; use async_std::task::spawn; use clap::Parser; use futures::prelude::*; use futures::StreamExt; use libp2p::{core::Multiaddr, multiaddr::Protocol, PeerId}; use std::error::Error; use std::io::Write; use std::path::PathBuf; #[async_std::main] async fn main() -> Result<(), Box> { env_logger::init(); let opt = Opt::parse(); let (mut network_client, mut network_events, network_event_loop) = network::new(opt.secret_key_seed).await?; // Spawn the network task for it to run in the background. spawn(network_event_loop.run()); // In case a listen address was provided use it, otherwise listen on any // address. match opt.listen_address { Some(addr) => network_client .start_listening(addr) .await .expect("Listening not to fail."), None => network_client .start_listening("/ip4/0.0.0.0/tcp/0".parse()?) .await .expect("Listening not to fail."), }; // In case the user provided an address of a peer on the CLI, dial it. if let Some(addr) = opt.peer { let peer_id = match addr.iter().last() { Some(Protocol::P2p(hash)) => PeerId::from_multihash(hash).expect("Valid hash."), _ => return Err("Expect peer multiaddr to contain peer ID.".into()), }; network_client .dial(peer_id, addr) .await .expect("Dial to succeed"); } match opt.argument { // Providing a file. CliArgument::Provide { path, name } => { // Advertise oneself as a provider of the file on the DHT. network_client.start_providing(name.clone()).await; loop { match network_events.next().await { // Reply with the content of the file on incoming requests. Some(network::Event::InboundRequest { request, channel }) => { if request == name { network_client .respond_file(std::fs::read(&path)?, channel) .await; } } e => todo!("{:?}", e), } } } // Locating and getting a file. CliArgument::Get { name } => { // Locate all nodes providing the file. let providers = network_client.get_providers(name.clone()).await; if providers.is_empty() { return Err(format!("Could not find provider for file {name}.").into()); } // Request the content of the file from each node. let requests = providers.into_iter().map(|p| { let mut network_client = network_client.clone(); let name = name.clone(); async move { network_client.request_file(p, name).await }.boxed() }); // Await the requests, ignore the remaining once a single one succeeds. let file_content = futures::future::select_ok(requests) .await .map_err(|_| "None of the providers returned file.")? .0; std::io::stdout().write_all(&file_content)?; } } Ok(()) } #[derive(Parser, Debug)] #[clap(name = "libp2p file sharing example")] struct Opt { /// Fixed value to generate deterministic peer ID. #[clap(long)] secret_key_seed: Option, #[clap(long)] peer: Option, #[clap(long)] listen_address: Option, #[clap(subcommand)] argument: CliArgument, } #[derive(Debug, Parser)] enum CliArgument { Provide { #[clap(long)] path: PathBuf, #[clap(long)] name: String, }, Get { #[clap(long)] name: String, }, }