nfs 0.1.0

A userspace NFSv3 and NFSv4 client library.
Documentation
#![cfg_attr(not(feature = "protocol"), allow(dead_code))]

use std::net::ToSocketAddrs;
use std::time::Duration;

use crate::error::{Error, Result};
use crate::rpc::{Auth, RpcClient};
use crate::xdr::{Decode, Decoder, Encode, Encoder};

pub const PMAP_PROGRAM: u32 = 100000;
pub const PMAP_VERSION: u32 = 2;
pub const PMAP_PORT: u16 = 111;
pub const IPPROTO_TCP: u32 = 6;
pub const IPPROTO_UDP: u32 = 17;

const PMAPPROC_GETPORT: u32 = 3;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Mapping {
    pub program: u32,
    pub version: u32,
    pub protocol: u32,
    pub port: u32,
}

impl Mapping {
    pub fn tcp(program: u32, version: u32) -> Self {
        Self {
            program,
            version,
            protocol: IPPROTO_TCP,
            port: 0,
        }
    }
}

impl Encode for Mapping {
    fn encode(&self, encoder: &mut Encoder) -> crate::xdr::Result<()> {
        encoder.write_u32(self.program);
        encoder.write_u32(self.version);
        encoder.write_u32(self.protocol);
        encoder.write_u32(self.port);
        Ok(())
    }
}

#[derive(Debug)]
pub struct PortmapperClient {
    rpc: RpcClient,
}

impl PortmapperClient {
    pub fn connect<A: ToSocketAddrs>(addr: A) -> Result<Self> {
        Self::connect_with_timeout(addr, None)
    }

    pub fn connect_with_timeout<A: ToSocketAddrs>(
        addr: A,
        timeout: Option<Duration>,
    ) -> Result<Self> {
        Ok(Self {
            rpc: RpcClient::connect_with_timeout(addr, Auth::none(), timeout)?,
        })
    }

    pub fn set_timeout(&self, timeout: Option<Duration>) -> Result<()> {
        self.rpc.set_timeout(timeout)
    }

    pub fn get_port(&mut self, mapping: Mapping) -> Result<u16> {
        let payload = self
            .rpc
            .call(PMAP_PROGRAM, PMAP_VERSION, PMAPPROC_GETPORT, &mapping)?;
        let mut decoder = Decoder::new(&payload);
        let port = u32::decode(&mut decoder)?;
        decoder.finish()?;
        u16::try_from(port).map_err(|_| {
            Error::Protocol(format!(
                "portmapper returned out-of-range port {port} for program {} version {}",
                mapping.program, mapping.version
            ))
        })
    }
}

pub fn get_tcp_port(host: &str, program: u32, version: u32) -> Result<u16> {
    get_tcp_port_with_timeout(host, program, version, None)
}

pub fn get_tcp_port_with_timeout(
    host: &str,
    program: u32,
    version: u32,
    timeout: Option<Duration>,
) -> Result<u16> {
    let mut client = PortmapperClient::connect_with_timeout((host, PMAP_PORT), timeout)?;
    let port = client.get_port(Mapping::tcp(program, version))?;
    if port == 0 {
        return Err(Error::PortUnavailable { program, version });
    }
    Ok(port)
}