#![allow(clippy::missing_safety_doc)]
use std::os::raw::{c_char, c_int};
use std::panic::AssertUnwindSafe;
use std::path::Path;
use std::ptr;
use std::sync::Arc;
use super::blob::{blob_adapter_arc, MeshBlobAdapterHandle};
use super::mesh::{block_on, mesh_node_arc, write_string_out, MeshNodeHandle};
use crate::adapter::net::dataforts::blob::{BlobError, BlobRef, MeshBlobAdapter};
use crate::adapter::net::dataforts::{
fetch_dir as substrate_fetch_dir, store_dir as substrate_store_dir, DirError, DirManifest,
};
use crate::adapter::net::MeshNode;
pub const NET_TRANSPORT_OK: c_int = 0;
pub const NET_ERR_TRANSFER_NOT_FOUND: c_int = -200;
pub const NET_ERR_TRANSFER_HASH_MISMATCH: c_int = -201;
pub const NET_ERR_TRANSFER_ALL_PEERS_FAILED: c_int = -202;
pub const NET_ERR_TRANSFER_CANCELLED: c_int = -203;
pub const NET_ERR_TRANSFER_NULL_POINTER: c_int = -204;
pub const NET_ERR_TRANSFER_SHUTTING_DOWN: c_int = -205;
pub const NET_ERR_TRANSFER_ENGINE_NOT_INSTALLED: c_int = -206;
pub const NET_ERR_TRANSFER_BACKEND: c_int = -207;
pub const NET_ERR_TRANSFER_PANIC: c_int = -208;
pub const NET_ERR_TRANSFER_INVALID_ARGUMENT: c_int = -209;
pub const NET_ERR_DIR_INVALID_MANIFEST: c_int = -210;
pub const NET_ERR_DIR_PATH_INVALID: c_int = -211;
pub const NET_ERR_DIR_IO: c_int = -213;
fn blob_err_code(e: &BlobError) -> c_int {
match e {
BlobError::NotFound(_) => NET_ERR_TRANSFER_NOT_FOUND,
BlobError::HashMismatch { .. } => NET_ERR_TRANSFER_HASH_MISMATCH,
BlobError::Cancelled => NET_ERR_TRANSFER_CANCELLED,
BlobError::Backend(m) if m.contains("engine not installed") => {
NET_ERR_TRANSFER_ENGINE_NOT_INSTALLED
}
_ => NET_ERR_TRANSFER_BACKEND,
}
}
fn dir_err_code(e: &DirError) -> c_int {
match e {
DirError::Blob(b) => blob_err_code(b),
DirError::UnsafePath(_) => NET_ERR_DIR_PATH_INVALID,
DirError::Manifest(_) => NET_ERR_DIR_INVALID_MANIFEST,
DirError::Io(_) => NET_ERR_DIR_IO,
}
}
unsafe fn write_bytes_out(src: &[u8], out_ptr: *mut *mut u8, out_len: *mut usize) -> c_int {
let len = src.len();
if len == 0 {
unsafe {
*out_ptr = ptr::null_mut();
*out_len = 0;
}
return NET_TRANSPORT_OK;
}
let layout = match std::alloc::Layout::array::<u8>(len) {
Ok(l) => l,
Err(_) => return NET_ERR_TRANSFER_BACKEND,
};
let alloc_ptr = unsafe { std::alloc::alloc(layout) };
if alloc_ptr.is_null() {
std::alloc::handle_alloc_error(layout);
}
unsafe {
std::ptr::copy_nonoverlapping(src.as_ptr(), alloc_ptr, len);
*out_ptr = alloc_ptr;
*out_len = len;
}
NET_TRANSPORT_OK
}
unsafe fn read_hash(p: *const u8) -> [u8; 32] {
let mut h = [0u8; 32];
unsafe { std::ptr::copy_nonoverlapping(p, h.as_mut_ptr(), 32) };
h
}
unsafe fn read_blob_ref(ptr: *const u8, len: usize) -> Result<BlobRef, c_int> {
if len > isize::MAX as usize {
return Err(NET_ERR_TRANSFER_INVALID_ARGUMENT);
}
let bytes = unsafe { std::slice::from_raw_parts(ptr, len) };
match BlobRef::decode(bytes) {
Ok(Some(r)) => Ok(r),
Ok(None) => Err(NET_ERR_TRANSFER_INVALID_ARGUMENT),
Err(e) => Err(blob_err_code(&e)),
}
}
unsafe fn read_path<'a>(p: *const c_char) -> Result<&'a Path, c_int> {
if p.is_null() {
return Err(NET_ERR_TRANSFER_NULL_POINTER);
}
match unsafe { std::ffi::CStr::from_ptr(p) }.to_str() {
Ok(s) => Ok(Path::new(s)),
Err(_) => Err(NET_ERR_TRANSFER_INVALID_ARGUMENT),
}
}
async fn fetch_blob_bytes(
node: &Arc<MeshNode>,
source: u64,
blob_ref: &BlobRef,
) -> Result<Vec<u8>, BlobError> {
match blob_ref {
BlobRef::Small { hash, .. } => Ok(node.transfer_fetch_chunk(source, *hash).await?.to_vec()),
BlobRef::Manifest {
chunks, total_size, ..
} => {
let mut buf = Vec::with_capacity(*total_size as usize);
for chunk in chunks {
buf.extend_from_slice(&node.transfer_fetch_chunk(source, chunk.hash).await?);
}
Ok(buf)
}
BlobRef::Tree { .. } => Err(BlobError::Backend(
"transfer: BlobRef::Tree not supported by the transport FFI".into(),
)),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn net_serve_blob_transfer(
node: *const MeshNodeHandle,
adapter: *const MeshBlobAdapterHandle,
) -> c_int {
if node.is_null() || adapter.is_null() {
return NET_ERR_TRANSFER_NULL_POINTER;
}
let node_arc = match mesh_node_arc(unsafe { &*node }) {
Some(n) => n,
None => return NET_ERR_TRANSFER_SHUTTING_DOWN,
};
let adapter_arc: Arc<MeshBlobAdapter> = match blob_adapter_arc(unsafe { &*adapter }) {
Some(a) => a,
None => return NET_ERR_TRANSFER_SHUTTING_DOWN,
};
match std::panic::catch_unwind(AssertUnwindSafe(|| {
node_arc.serve_blob_transfer(adapter_arc);
})) {
Ok(()) => NET_TRANSPORT_OK,
Err(_) => NET_ERR_TRANSFER_PANIC,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn net_fetch_blob(
node: *const MeshNodeHandle,
holder_id: u64,
hash: *const u8,
out_bytes: *mut *mut u8,
out_len: *mut usize,
) -> c_int {
if node.is_null() || hash.is_null() || out_bytes.is_null() || out_len.is_null() {
return NET_ERR_TRANSFER_NULL_POINTER;
}
unsafe {
*out_bytes = ptr::null_mut();
*out_len = 0;
}
let node_arc = match mesh_node_arc(unsafe { &*node }) {
Some(n) => n,
None => return NET_ERR_TRANSFER_SHUTTING_DOWN,
};
let hash = unsafe { read_hash(hash) };
let outcome = std::panic::catch_unwind(AssertUnwindSafe(|| {
block_on(async move { node_arc.transfer_fetch_chunk(holder_id, hash).await })
}));
match outcome {
Err(_) => NET_ERR_TRANSFER_PANIC,
Ok(Ok(bytes)) => unsafe { write_bytes_out(&bytes, out_bytes, out_len) },
Ok(Err(e)) => blob_err_code(&e),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn net_fetch_blob_discovered(
node: *const MeshNodeHandle,
hash: *const u8,
out_bytes: *mut *mut u8,
out_len: *mut usize,
) -> c_int {
if node.is_null() || hash.is_null() || out_bytes.is_null() || out_len.is_null() {
return NET_ERR_TRANSFER_NULL_POINTER;
}
unsafe {
*out_bytes = ptr::null_mut();
*out_len = 0;
}
let node_arc = match mesh_node_arc(unsafe { &*node }) {
Some(n) => n,
None => return NET_ERR_TRANSFER_SHUTTING_DOWN,
};
let hash = unsafe { read_hash(hash) };
let outcome = std::panic::catch_unwind(AssertUnwindSafe(|| {
block_on(async move { node_arc.transfer_fetch_chunk_discovered(hash).await })
}));
match outcome {
Err(_) => NET_ERR_TRANSFER_PANIC,
Ok(Ok(bytes)) => unsafe { write_bytes_out(&bytes, out_bytes, out_len) },
Ok(Err(BlobError::NotFound(_))) => NET_ERR_TRANSFER_ALL_PEERS_FAILED,
Ok(Err(e)) => blob_err_code(&e),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn net_store_dir(
adapter: *const MeshBlobAdapterHandle,
root_path: *const c_char,
out_manifest_ref: *mut *mut u8,
out_len: *mut usize,
) -> c_int {
if adapter.is_null() || root_path.is_null() || out_manifest_ref.is_null() || out_len.is_null() {
return NET_ERR_TRANSFER_NULL_POINTER;
}
unsafe {
*out_manifest_ref = ptr::null_mut();
*out_len = 0;
}
let adapter_arc: Arc<MeshBlobAdapter> = match blob_adapter_arc(unsafe { &*adapter }) {
Some(a) => a,
None => return NET_ERR_TRANSFER_SHUTTING_DOWN,
};
let root = match unsafe { read_path(root_path) } {
Ok(p) => p.to_path_buf(),
Err(code) => return code,
};
let outcome = std::panic::catch_unwind(AssertUnwindSafe(|| {
block_on(async move { substrate_store_dir(&adapter_arc, &root).await })
}));
match outcome {
Err(_) => NET_ERR_TRANSFER_PANIC,
Ok(Ok(blob_ref)) => unsafe {
write_bytes_out(&blob_ref.encode(), out_manifest_ref, out_len)
},
Ok(Err(e)) => dir_err_code(&e),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn net_fetch_dir(
node: *const MeshNodeHandle,
source_id: u64,
manifest_ref: *const u8,
manifest_ref_len: usize,
dest_path: *const c_char,
out_files: *mut u64,
out_bytes: *mut u64,
) -> c_int {
if node.is_null() || manifest_ref.is_null() || dest_path.is_null() {
return NET_ERR_TRANSFER_NULL_POINTER;
}
if !out_files.is_null() {
unsafe { *out_files = 0 };
}
if !out_bytes.is_null() {
unsafe { *out_bytes = 0 };
}
let node_arc = match mesh_node_arc(unsafe { &*node }) {
Some(n) => n,
None => return NET_ERR_TRANSFER_SHUTTING_DOWN,
};
let blob_ref = match unsafe { read_blob_ref(manifest_ref, manifest_ref_len) } {
Ok(r) => r,
Err(code) => return code,
};
let dest = match unsafe { read_path(dest_path) } {
Ok(p) => p.to_path_buf(),
Err(code) => return code,
};
let outcome = std::panic::catch_unwind(AssertUnwindSafe(|| {
block_on(
async move { substrate_fetch_dir(&node_arc, source_id, &blob_ref, &dest, 0).await },
)
}));
match outcome {
Err(_) => NET_ERR_TRANSFER_PANIC,
Ok(Ok(stats)) => {
if !out_files.is_null() {
unsafe { *out_files = stats.files as u64 };
}
if !out_bytes.is_null() {
unsafe { *out_bytes = stats.bytes };
}
NET_TRANSPORT_OK
}
Ok(Err(e)) => dir_err_code(&e),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn net_dir_manifest_read(
node: *const MeshNodeHandle,
source_id: u64,
manifest_ref: *const u8,
manifest_ref_len: usize,
out_json: *mut *mut c_char,
out_len: *mut usize,
) -> c_int {
if node.is_null() || manifest_ref.is_null() || out_json.is_null() || out_len.is_null() {
return NET_ERR_TRANSFER_NULL_POINTER;
}
unsafe {
*out_json = ptr::null_mut();
*out_len = 0;
}
let node_arc = match mesh_node_arc(unsafe { &*node }) {
Some(n) => n,
None => return NET_ERR_TRANSFER_SHUTTING_DOWN,
};
let blob_ref = match unsafe { read_blob_ref(manifest_ref, manifest_ref_len) } {
Ok(r) => r,
Err(code) => return code,
};
let outcome = std::panic::catch_unwind(AssertUnwindSafe(|| {
block_on(async move { fetch_blob_bytes(&node_arc, source_id, &blob_ref).await })
}));
let bytes = match outcome {
Err(_) => return NET_ERR_TRANSFER_PANIC,
Ok(Ok(b)) => b,
Ok(Err(e)) => return blob_err_code(&e),
};
let manifest: DirManifest = match postcard::from_bytes(&bytes) {
Ok(m) => m,
Err(_) => return NET_ERR_DIR_INVALID_MANIFEST,
};
let json = match serde_json::to_string(&manifest) {
Ok(s) => s,
Err(_) => return NET_ERR_DIR_INVALID_MANIFEST,
};
write_string_out(json, out_json, out_len)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn net_transport_free_buffer(ptr: *mut u8, len: usize) {
if ptr.is_null() || len == 0 {
return;
}
let layout = match std::alloc::Layout::array::<u8>(len) {
Ok(l) => l,
Err(_) => return,
};
unsafe { std::alloc::dealloc(ptr, layout) };
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_codes_are_distinct_and_negative() {
let codes = [
NET_ERR_TRANSFER_NOT_FOUND,
NET_ERR_TRANSFER_HASH_MISMATCH,
NET_ERR_TRANSFER_ALL_PEERS_FAILED,
NET_ERR_TRANSFER_CANCELLED,
NET_ERR_TRANSFER_NULL_POINTER,
NET_ERR_TRANSFER_SHUTTING_DOWN,
NET_ERR_TRANSFER_ENGINE_NOT_INSTALLED,
NET_ERR_TRANSFER_BACKEND,
NET_ERR_TRANSFER_PANIC,
NET_ERR_TRANSFER_INVALID_ARGUMENT,
NET_ERR_DIR_INVALID_MANIFEST,
NET_ERR_DIR_PATH_INVALID,
NET_ERR_DIR_IO,
];
for (i, a) in codes.iter().enumerate() {
assert!(*a < 0, "code {a} must be negative");
for b in &codes[i + 1..] {
assert_ne!(a, b, "duplicate transport error code {a}");
}
}
}
#[test]
fn blob_errors_map_to_transport_codes() {
assert_eq!(
blob_err_code(&BlobError::NotFound("x".into())),
NET_ERR_TRANSFER_NOT_FOUND
);
assert_eq!(
blob_err_code(&BlobError::HashMismatch {
expected: [0u8; 32],
actual: [1u8; 32],
}),
NET_ERR_TRANSFER_HASH_MISMATCH
);
assert_eq!(
blob_err_code(&BlobError::Backend(
"blob transfer: engine not installed".into()
)),
NET_ERR_TRANSFER_ENGINE_NOT_INSTALLED
);
assert_eq!(
blob_err_code(&BlobError::Backend("some other failure".into())),
NET_ERR_TRANSFER_BACKEND
);
}
#[test]
fn null_pointers_are_rejected_without_deref() {
let rc = unsafe {
net_fetch_blob(
ptr::null(),
0,
ptr::null(),
ptr::null_mut(),
ptr::null_mut(),
)
};
assert_eq!(rc, NET_ERR_TRANSFER_NULL_POINTER);
}
}