storage-bindings 0.4.4

Rust bindings for Storage, the Decentralized Durability Engine
use crate::callback::{c_callback, with_libstorage_lock, CallbackFuture};
use crate::error::{Result, StorageError};
use crate::ffi::{storage_connect, string_to_c_string, SendSafeCString};
use crate::node::lifecycle::StorageNode;
use libc::c_char;

pub async fn connect(node: &StorageNode, peer_id: &str, peer_addresses: &[String]) -> Result<()> {
    if peer_id.is_empty() {
        return Err(StorageError::invalid_parameter(
            "peer_id",
            "Peer ID cannot be empty",
        ));
    }

    if peer_addresses.is_empty() {
        return Err(StorageError::invalid_parameter(
            "peer_addresses",
            "At least one peer address must be provided",
        ));
    }

    let future = CallbackFuture::new();
    let context_ptr = future.context_ptr();

    let c_peer_id = string_to_c_string(peer_id);

    let c_addresses: Vec<SendSafeCString> = peer_addresses
        .iter()
        .map(|addr| string_to_c_string(addr))
        .collect();

    let result = with_libstorage_lock(|| unsafe {
        node.with_ctx(|ctx| {
            storage_connect(
                ctx as *mut _,
                c_peer_id.as_ptr(),
                c_addresses.as_ptr() as *mut *const c_char,
                c_addresses.len(),
                Some(c_callback),
                context_ptr.as_ptr(),
            )
        })
    });

    if result != 0 {
        return Err(StorageError::p2p_error("Failed to connect to peer"));
    }

    future.await?;

    Ok(())
}

pub async fn connect_to_multiple(
    node: &StorageNode,
    peer_connections: Vec<(String, Vec<String>)>,
) -> Vec<Result<()>> {
    let mut results = Vec::with_capacity(peer_connections.len());

    for (peer_id, addresses) in peer_connections {
        let result = connect(node, &peer_id, &addresses).await;
        results.push(result);
    }

    results
}

pub fn validate_peer_id(peer_id: &str) -> Result<()> {
    if peer_id.is_empty() {
        return Err(StorageError::invalid_parameter(
            "peer_id",
            "Peer ID cannot be empty",
        ));
    }

    if peer_id.len() < 10 {
        return Err(StorageError::invalid_parameter(
            "peer_id",
            "Peer ID is too short",
        ));
    }

    if peer_id.len() > 100 {
        return Err(StorageError::invalid_parameter(
            "peer_id",
            "Peer ID is too long",
        ));
    }

    let valid_prefixes = ["12D3KooW", "Qm", "bafy", "bafk"];

    let has_valid_prefix = valid_prefixes
        .iter()
        .any(|&prefix| peer_id.starts_with(prefix));

    if !has_valid_prefix {
        return Err(StorageError::invalid_parameter(
            "peer_id",
            "Peer ID has invalid format or prefix",
        ));
    }

    Ok(())
}

pub fn validate_addresses(addresses: &[String]) -> Result<()> {
    if addresses.is_empty() {
        return Err(StorageError::invalid_parameter(
            "addresses",
            "At least one address must be provided",
        ));
    }

    for (i, address) in addresses.iter().enumerate() {
        if address.is_empty() {
            return Err(StorageError::invalid_parameter(
                format!("addresses[{}]", i),
                "Address cannot be empty",
            ));
        }

        if !address.starts_with('/') {
            return Err(StorageError::invalid_parameter(
                format!("addresses[{}]", i),
                "Address must start with '/'",
            ));
        }

        let valid_protocols = [
            "/ip4", "/ip6", "/dns4", "/dns6", "/dnsaddr", "/tcp", "/udp", "/quic", "/ws", "/wss",
            "/p2p", "/ipfs",
        ];

        let has_valid_protocol = valid_protocols
            .iter()
            .any(|&protocol| address.contains(protocol));

        if !has_valid_protocol {
            return Err(StorageError::invalid_parameter(
                format!("addresses[{}]", i),
                "Address contains invalid protocol",
            ));
        }
    }

    Ok(())
}