rust-ipfs 0.15.0

IPFS node implementation
Documentation
use std::str::FromStr;

use clap::Parser;
use rust_ipfs::p2p::MultiaddrExt;
use rust_ipfs::{Ipfs, Multiaddr};

use rust_ipfs::builder::IpfsBuilder;
use rust_ipfs::Keypair;

#[derive(Debug, Parser)]
#[clap(name = "relay-client")]
struct Opt {
    relay: Vec<Multiaddr>,
    #[clap(long)]
    connect: Option<Multiaddr>,
    #[clap(long)]
    relay_select: Option<RelaySelect>,
}

#[derive(Default, Debug, Clone)]
enum RelaySelect {
    Random,
    First,
    Last,
    #[default]
    All,
}

impl FromStr for RelaySelect {
    type Err = std::io::Error;
    fn from_str(opt: &str) -> Result<Self, Self::Err> {
        let opt = match opt.to_lowercase().as_str() {
            "random" => Self::Random,
            "first" => Self::First,
            "last" => Self::Last,
            "all" => Self::All,
            _ => {
                return Err(std::io::Error::new(
                    std::io::ErrorKind::InvalidData,
                    "selection option invalid",
                ));
            }
        };

        Ok(opt)
    }
}

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

    tracing_subscriber::fmt::init();

    let keypair = Keypair::generate_ed25519();
    let local_peer_id = keypair.public().to_peer_id();

    // Initialize the repo and start a daemon
    let ipfs: Ipfs = IpfsBuilder::with_keypair(&keypair)?
        .with_identify(Default::default())
        .with_ping(Default::default())
        .set_default_listener()
        .enable_tcp()
        .with_relay(true)
        .with_custom_behaviour(move |_| Ok(ext_behaviour::Behaviour::new(local_peer_id)))
        .fd_limit(rust_ipfs::FDLimit::Max)
        .start()
        .await?;

    let mut relays_peer_id = vec![];

    for mut addr in opt.relay.clone() {
        let peer_id = addr
            .extract_peer_id()
            .expect("peerid required on multiaddr");
        relays_peer_id.push(peer_id);

        if let Err(e) = ipfs.add_relay(peer_id, addr.clone()).await {
            println!("error adding relay {addr}: {e}");
        }
    }

    let selection = opt.relay_select.unwrap_or_default();

    if !relays_peer_id.is_empty() {
        let list = match selection {
            RelaySelect::Random => vec![None],
            RelaySelect::First => vec![relays_peer_id.first().copied()],
            RelaySelect::Last => vec![relays_peer_id.last().copied()],
            RelaySelect::All => relays_peer_id.iter().copied().map(Some).collect(),
        };

        for peer_id in list {
            if let Err(e) = ipfs.enable_relay(peer_id).await {
                println!("error enabling relay: {e}");
            }
        }
    }

    if let Some(addr) = opt.connect {
        ipfs.connect(addr).await?;
    }

    tokio::signal::ctrl_c().await?;

    ipfs.exit_daemon().await;
    Ok(())
}

mod ext_behaviour {
    use connexa::dummy::DummyHandler;
    use connexa::prelude::swarm::derive_prelude::PortUse;
    use connexa::prelude::swarm::{
        ConnectionDenied, ExternalAddrExpired, FromSwarm, ListenerClosed, NewListenAddr, THandler,
        THandlerInEvent, THandlerOutEvent, ToSwarm,
    };
    use connexa::prelude::transport::Endpoint;
    use rust_ipfs::{ConnectionId, ListenerId, Multiaddr, NetworkBehaviour, PeerId};
    use std::convert::Infallible;
    use std::{
        collections::{HashMap, HashSet},
        task::{Context, Poll},
    };

    #[derive(Debug)]
    pub struct Behaviour {
        peer_id: PeerId,
        addrs: HashSet<Multiaddr>,
        listened: HashMap<ListenerId, HashSet<Multiaddr>>,
    }

    impl Behaviour {
        pub fn new(local_peer_id: PeerId) -> Self {
            println!("PeerID: {}", local_peer_id);
            Self {
                peer_id: local_peer_id,
                addrs: Default::default(),
                listened: Default::default(),
            }
        }
    }

    impl NetworkBehaviour for Behaviour {
        type ConnectionHandler = DummyHandler;
        type ToSwarm = Infallible;

        fn handle_pending_inbound_connection(
            &mut self,
            _: ConnectionId,
            _: &Multiaddr,
            _: &Multiaddr,
        ) -> Result<(), ConnectionDenied> {
            Ok(())
        }

        fn handle_pending_outbound_connection(
            &mut self,
            _: ConnectionId,
            _: Option<PeerId>,
            _: &[Multiaddr],
            _: Endpoint,
        ) -> Result<Vec<Multiaddr>, ConnectionDenied> {
            Ok(vec![])
        }

        fn handle_established_inbound_connection(
            &mut self,
            _: ConnectionId,
            _: PeerId,
            _: &Multiaddr,
            _: &Multiaddr,
        ) -> Result<THandler<Self>, ConnectionDenied> {
            Ok(DummyHandler)
        }

        fn handle_established_outbound_connection(
            &mut self,
            _: ConnectionId,
            _: PeerId,
            _: &Multiaddr,
            _: Endpoint,
            _: PortUse,
        ) -> Result<THandler<Self>, ConnectionDenied> {
            Ok(DummyHandler)
        }

        fn on_connection_handler_event(
            &mut self,
            _: PeerId,
            _: ConnectionId,
            _: THandlerOutEvent<Self>,
        ) {
        }

        fn on_swarm_event(&mut self, event: FromSwarm) {
            match event {
                FromSwarm::NewListenAddr(NewListenAddr {
                    listener_id, addr, ..
                }) => {
                    let addr = addr.clone();

                    let addr = addr.with_p2p(self.peer_id).unwrap();

                    if !self.addrs.insert(addr.clone()) {
                        return;
                    }

                    self.listened
                        .entry(listener_id)
                        .or_default()
                        .insert(addr.clone());

                    println!("Listening on {addr}");
                }

                FromSwarm::ExternalAddrConfirmed(ev) => {
                    let addr = ev.addr.clone();
                    let addr = addr.with_p2p(self.peer_id).unwrap();

                    if !self.addrs.insert(addr.clone()) {
                        return;
                    }

                    println!("Listening on {}", addr);
                }
                FromSwarm::ExternalAddrExpired(ExternalAddrExpired { addr }) => {
                    let addr = addr.clone();
                    let addr = addr.with_p2p(self.peer_id).unwrap();

                    if self.addrs.remove(&addr) {
                        println!("No longer listening on {addr}");
                    }
                }
                FromSwarm::ListenerClosed(ListenerClosed { listener_id, .. }) => {
                    if let Some(addrs) = self.listened.remove(&listener_id) {
                        for addr in addrs {
                            let addr = addr.with_p2p(self.peer_id).unwrap();
                            self.addrs.remove(&addr);
                            println!("No longer listening on {addr}");
                        }
                    }
                }
                _ => {}
            }
        }

        fn poll(&mut self, _: &mut Context) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> {
            Poll::Pending
        }
    }
}