Practical Rust P2P Network Application – File Sharing Project (Part 1)

Starting from this article, we will enter the practical project phase, a file-sharing project based on a P2P network. This project uses Kademlia and the request-response protocol to build a basic file-sharing application, which can locate and retrieve files by their names.

Basic Requirements

Assume there are three nodes, A, B, and C. A and B each provide a file FA and FB, while C retrieves a file. A and B declare themselves as the providers of their files on the DHT (a network composed of the Kademlia protocol) using the “libp2p-kad” protocol. Other nodes in the network connect to these two nodes through DHT.

Node C can find the providers of files FA or FB through DHT without needing to connect to a specific node that provides the file;

then node C connects to the node that provides the corresponding file and requests the file’s content using the “libp2p-request-response” protocol.

Creating the Project

cargo new file-sharing

Add the following dependencies in the Cargo.toml file:

[dependencies]
libp2p = { version = "0.46",  features = ["tcp-tokio"] }
tokio = { version = "1.19", features = ["full"] }
futures = "0.3.1"
clap = {version = "3.1.6", features = ["derive"]}
async-trait = "0.1"

Parsing Command-Line Arguments

We use the clap library to parse command-line arguments, with the command-line format as follows:

/// Start the network node that provides files
file-sharing --listen-address <listen address> \
               --secret-key-seed <seed for generating key pair> \
             provide \
             --path <full path to file> \
             --name <file name> 

/// Connect to the node providing the file and retrieve the file content
file-sharing --peer <node address/PeerId> \
             get \
             --name <file name>

Create the args.rs file in the src directory:

use std::path::PathBuf;

use clap::Parser;
use libp2p::Multiaddr;

#[derive(Debug, Parser)]
#[clap(name = "P2P File Sharing")]
pub struct Opt {
    // Seed for generating key pair
    #[clap(long)]
    pub secret_key_seed: Option<u8>,

    // Node address
    #[clap(long)]
    pub peer: Option<Multiaddr>,

    // Listen address
    #[clap(long)]
    pub listen_address: Option<Multiaddr>,

    // Subcommand
    #[clap(subcommand)]
    pub argument: CliArgument,
}

#[derive(Debug, Parser)]
pub enum CliArgument {
    // Provide file subcommand
    Provide {
        #[clap(long)]
        path: PathBuf,  // Full path to file
        #[clap(long)]
        name: String,   // File name
    },
    // Get file content subcommand
    Get {
        #[clap(long)]
        name: String,   // File name
    },
}

Write the following code in the main.rs file:

use std::error::Error;

use args::{Opt, CliArgument};
use clap::Parser;

mod args;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let opt = Opt::parse();

    process_args(opt).await?;

    Ok(())
}

// Parse command-line arguments
async fn process_args(opt: Opt) -> Result<(), Box<dyn Error>> {
    match opt.listen_address {
        Some(addr) => println!("{addr}"),
        None => println!("/ip4/0.0.0.0/tcp/0"),
    }

    if let Some(addr) = opt.peer {
        println!("{addr}");
    }

    match opt.argument {
        CliArgument::Provide{ path, name} => {
            println!("{:?}, {name}", path);
        },
        CliArgument::Get { name } => {
            println!("{name}");
        }
    }

    Ok(())
}

Use the following command for testing:

cargo run -- \
          --listen-address /ip4/127.0.0.1/tcp/40837 \
          --secret-key-seed 1 \
          provide \
          --path /Users/Justin/sharing_file.txt \
          --name sharing_file

Execution Result:

/ip4/127.0.0.1/tcp/40837"/Users/Justin/sharing_file.txt", sharing_filemaster:file_sharing_part_1 Justin$

Execute the command:

cargo run -- \
          --peer /ip4/127.0.0.1/tcp/40837/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X \
          get \
          --name sharing_file

Execution Result:

/ip4/0.0.0.0/tcp/0/ip4/127.0.0.1/tcp/40837/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6Xsharing_filemaster:file_sharing_part_1 Justin$

Command-line argument parsing was successful. In the next article, we will create a Client to send commands.

Full Code:

https://github.com/Justin02180218/p2p_learn

Leave a Comment