use crate::address::Address;
use crate::error::{Error, Result};
use crate::identity::{Identity, NodeId};
use crate::message::Headers;
use crate::raw::RawSocket;
use crate::server::Compression;
use crate::wire::{Method, Status};
use core::cell::Cell;
use core::ffi::{c_char, c_void, CStr};
use core::ptr;
use nwep_sys as sys;
use std::ffi::CString;
use std::rc::Rc;
#[derive(Clone, Copy, Debug, Default)]
pub struct ClientMetrics {
pub requests_inflight: u64,
pub requests_completed: u64,
pub requests_failed: u64,
pub smoothed_rtt_us: u64,
pub alive: bool,
}
pub struct Response {
msg: *mut sys::nwep_message,
}
impl Response {
pub fn status(&self) -> Option<Status> {
let p = unsafe { sys::nwep_message_get_status(self.msg) };
if p.is_null() {
return None;
}
let token = unsafe { CStr::from_ptr(p) }.to_str().unwrap_or("error");
Some(Status::from_token(token))
}
pub fn header(&self, name: &str) -> Option<&str> {
let cname = std::ffi::CString::new(name).ok()?;
let p = unsafe { sys::nwep_message_get_header(self.msg, cname.as_ptr()) };
if p.is_null() {
return None;
}
unsafe { CStr::from_ptr(p) }.to_str().ok()
}
pub fn body(&self) -> &[u8] {
let mut len = 0usize;
let p = unsafe { sys::nwep_message_get_body(self.msg, &mut len) };
if p.is_null() || len == 0 {
&[]
} else {
unsafe { core::slice::from_raw_parts(p, len) }
}
}
pub fn headers(&self) -> Headers<'_> {
Headers::new(self.msg)
}
pub fn into_body(self) -> Vec<u8> {
self.body().to_vec()
}
pub(crate) fn as_raw(&self) -> *const sys::nwep_message {
self.msg
}
pub(crate) fn from_raw(msg: *mut sys::nwep_message) -> Response {
Response { msg }
}
pub fn verify(&self, origin_pubkey: &[u8; 32], path: &str, now_secs: u64) -> Result<()> {
let cpath = CString::new(path).map_err(|_| Error::ProtoInvalidHeader)?;
Error::check(unsafe {
sys::nwep_response_verify(self.msg, origin_pubkey.as_ptr(), cpath.as_ptr(), now_secs)
})
}
}
impl Drop for Response {
fn drop(&mut self) {
unsafe { sys::nwep_message_free(self.msg) };
}
}
unsafe impl Send for Response {}
#[derive(Default)]
pub struct ClientBuilder {
identity: Option<Identity>,
}
impl ClientBuilder {
pub fn identity(mut self, identity: Identity) -> Self {
self.identity = Some(identity);
self
}
pub fn connect(self, target: &NodeId, addr: &Address) -> Result<Client> {
let identity = self.identity.ok_or(Error::ConfigMissing)?;
let mut raw: *mut sys::nwep_client = ptr::null_mut();
identity.with_keypair(|kp| {
Error::check(unsafe {
sys::nwep_client_connect(&mut raw, kp, &target.raw(), addr.as_raw())
})
})?;
Ok(Client::wrap(raw))
}
pub fn start_connect(self, target: &NodeId, addr: &Address) -> Result<Connecting> {
let identity = self.identity.ok_or(Error::ConfigMissing)?;
let mut raw: *mut sys::nwep_client = ptr::null_mut();
identity.with_keypair(|kp| {
Error::check(unsafe {
sys::nwep_client_connect_async(&mut raw, kp, &target.raw(), addr.as_raw())
})
})?;
Ok(Connecting { raw })
}
pub fn connect_fd(self, target: &NodeId, addr: &Address, fd: RawSocket) -> Result<Client> {
let identity = self.identity.ok_or(Error::ConfigMissing)?;
let fd = crate::raw::to_c(fd);
let mut raw: *mut sys::nwep_client = ptr::null_mut();
identity.with_keypair(|kp| {
Error::check(unsafe {
sys::nwep_client_connect_fd(&mut raw, kp, &target.raw(), addr.as_raw(), fd)
})
})?;
Ok(Client::wrap(raw))
}
pub fn start_connect_fd(
self,
target: &NodeId,
addr: &Address,
fd: RawSocket,
) -> Result<Connecting> {
let identity = self.identity.ok_or(Error::ConfigMissing)?;
let fd = crate::raw::to_c(fd);
let mut raw: *mut sys::nwep_client = ptr::null_mut();
identity.with_keypair(|kp| {
Error::check(unsafe {
sys::nwep_client_connect_fd_async(&mut raw, kp, &target.raw(), addr.as_raw(), fd)
})
})?;
Ok(Connecting { raw })
}
pub fn connect_by_node_id(
self,
target: &NodeId,
dht: &crate::Dht,
timeout: std::time::Duration,
) -> Result<Client> {
let identity = self.identity.ok_or(Error::ConfigMissing)?;
let ms = timeout.as_millis().min(u32::MAX as u128) as u32;
let mut raw: *mut sys::nwep_client = ptr::null_mut();
identity.with_keypair(|kp| {
Error::check(unsafe {
sys::nwep_client_connect_by_nodeid(&mut raw, kp, &target.raw(), dht.as_ptr(), ms)
})
})?;
Ok(Client::wrap(raw))
}
}
type DoneHook = Box<dyn FnMut(RequestId, Result<Response>)>;
pub struct Client {
raw: *mut sys::nwep_client,
cache: Cell<Option<Rc<crate::Cache>>>,
done: Cell<*mut DoneHook>,
}
impl Client {
pub fn builder() -> ClientBuilder {
ClientBuilder::default()
}
fn wrap(raw: *mut sys::nwep_client) -> Client {
Client {
raw,
cache: Cell::new(None),
done: Cell::new(ptr::null_mut()),
}
}
pub fn request(&self, method: Method, path: &str) -> RequestBuilder<'_> {
RequestBuilder {
client: self,
method,
path: path.to_owned(),
headers: Vec::new(),
body: Vec::new(),
}
}
pub fn send(&self, method: Method, path: &str, body: &[u8]) -> Result<Response> {
self.request(method, path).body(body).send()
}
pub fn open_stream(&self, method: Method, path: &str) -> Result<Stream<'_>> {
let cpath = CString::new(path).map_err(|_| Error::ProtoInvalidHeader)?;
let mut id = 0u64;
Error::check(unsafe {
sys::nwep_client_open_stream(
self.raw,
method.code(),
cpath.as_ptr(),
ptr::null(),
&mut id,
)
})?;
Ok(Stream { client: self, id })
}
pub fn verify_response(&self, response: &Response, path: &str, now_secs: u64) -> Result<()> {
let cpath = CString::new(path).map_err(|_| Error::ProtoInvalidHeader)?;
Error::check(unsafe {
sys::nwep_client_verify_response(self.raw, response.as_raw(), cpath.as_ptr(), now_secs)
})
}
pub fn is_alive(&self) -> bool {
unsafe { sys::nwep_client_is_alive(self.raw) == 1 }
}
pub fn compression(&self) -> Compression {
match unsafe { sys::nwep_client_compression(self.raw) } {
0 => Compression::None,
1 => Compression::Zstd,
_ => Compression::Unknown,
}
}
pub fn peer_pubkey(&self) -> Result<[u8; 32]> {
let mut out = [0u8; 32];
Error::check(unsafe { sys::nwep_client_peer_pubkey(self.raw, out.as_mut_ptr()) })?;
Ok(out)
}
pub fn metrics(&self) -> ClientMetrics {
let mut m = sys::nwep_client_metrics {
requests_inflight: 0,
requests_completed: 0,
requests_failed: 0,
smoothed_rtt_us: 0,
alive: 0,
};
unsafe { sys::nwep_client_metrics_get(self.raw, &mut m) };
ClientMetrics {
requests_inflight: m.requests_inflight,
requests_completed: m.requests_completed,
requests_failed: m.requests_failed,
smoothed_rtt_us: m.smoothed_rtt_us,
alive: m.alive == 1,
}
}
pub fn tick(&self, now_ms: i64) -> Result<()> {
Error::check(unsafe { sys::nwep_client_tick(self.raw, now_ms) })
}
pub fn fd(&self) -> RawSocket {
crate::raw::from_c(unsafe { sys::nwep_client_fd(self.raw) })
}
pub fn next_timeout(&self, now_ms: i64) -> Option<u32> {
let ms = unsafe { sys::nwep_client_next_timeout_ms(self.raw, now_ms) };
if ms < 0 {
None
} else {
Some(ms as u32)
}
}
pub fn set_cache(&self, cache: Option<Rc<crate::Cache>>) -> Result<()> {
let ptr = cache.as_ref().map_or(ptr::null_mut(), |c| c.as_ptr());
Error::check(unsafe { sys::nwep_client_set_cache(self.raw, ptr) })?;
self.cache.set(cache);
Ok(())
}
pub fn on_request_done(&self, hook: impl FnMut(RequestId, Result<Response>) + 'static) {
let prev = self.done.replace(ptr::null_mut());
if !prev.is_null() {
drop(unsafe { Box::from_raw(prev) });
}
let boxed: *mut DoneHook = Box::into_raw(Box::new(Box::new(hook)));
unsafe {
sys::nwep_client_set_request_done(self.raw, Some(done_trampoline), boxed as *mut c_void)
};
self.done.set(boxed);
}
pub fn poll_notify(&self) -> Option<Response> {
let msg = unsafe { sys::nwep_client_poll_notify(self.raw) };
if msg.is_null() {
None
} else {
Some(Response { msg })
}
}
#[cfg(feature = "runtime")]
pub(crate) fn submit_request(
&self,
method: Method,
path: &str,
headers: &[(String, String)],
body: &[u8],
) -> Result<RequestId> {
let cpath = CString::new(path).map_err(|_| Error::ProtoInvalidHeader)?;
let mut cstrings: Vec<(CString, CString)> = Vec::with_capacity(headers.len());
for (name, value) in headers {
let n = CString::new(name.as_str()).map_err(|_| Error::ProtoInvalidHeader)?;
let v = CString::new(value.as_str()).map_err(|_| Error::ProtoInvalidHeader)?;
cstrings.push((n, v));
}
let mut array: Vec<sys::nwep_header> = cstrings
.iter()
.map(|(n, v)| sys::nwep_header {
name: n.as_ptr(),
value: v.as_ptr(),
})
.collect();
array.push(sys::nwep_header {
name: ptr::null(),
value: ptr::null(),
});
let (bptr, blen) = if body.is_empty() {
(ptr::null(), 0)
} else {
(body.as_ptr(), body.len())
};
let mut id = 0u64;
Error::check(unsafe {
sys::nwep_client_request_submit(
self.raw,
method.code(),
cpath.as_ptr(),
array.as_ptr(),
bptr,
blen,
&mut id,
)
})?;
Ok(id)
}
#[cfg(feature = "runtime")]
pub(crate) fn poll_request(&self, id: RequestId) -> Result<Option<Response>> {
let mut out: *mut sys::nwep_message = ptr::null_mut();
let rc = unsafe { sys::nwep_client_request_poll(self.raw, id, &mut out) };
match rc {
0 => Ok(Some(Response { msg: out })),
c if c == Error::WouldBlock.code() => Ok(None),
other => Err(Error::from_code(other)),
}
}
pub fn as_ptr(&self) -> *mut sys::nwep_client {
self.raw
}
}
pub struct Stream<'c> {
client: &'c Client,
id: u64,
}
impl Stream<'_> {
pub fn response(&self) -> Result<Response> {
let mut resp: *mut sys::nwep_message = ptr::null_mut();
Error::check(unsafe {
sys::nwep_client_stream_response(self.client.raw, self.id, &mut resp)
})?;
Ok(Response { msg: resp })
}
pub fn recv(&self, buf: &mut [u8]) -> Result<(usize, bool)> {
let mut len = 0usize;
let mut ended: core::ffi::c_int = 0;
Error::check(unsafe {
sys::nwep_client_stream_recv(
self.client.raw,
self.id,
buf.as_mut_ptr(),
buf.len(),
&mut len,
&mut ended,
)
})?;
Ok((len, ended != 0))
}
pub fn verify(&self, pubkey: &[u8; 32]) -> Result<()> {
Error::check(unsafe {
sys::nwep_client_stream_verify(self.client.raw, self.id, pubkey.as_ptr())
})
}
}
impl Drop for Stream<'_> {
fn drop(&mut self) {
unsafe { sys::nwep_client_stream_close(self.client.raw, self.id) };
}
}
pub struct RequestBuilder<'c> {
client: &'c Client,
method: Method,
path: String,
headers: Vec<(String, String)>,
body: Vec<u8>,
}
impl<'c> RequestBuilder<'c> {
pub fn header(mut self, name: &str, value: &str) -> Self {
self.headers.push((name.to_owned(), value.to_owned()));
self
}
pub fn body(mut self, body: impl Into<Vec<u8>>) -> Self {
self.body = body.into();
self
}
pub fn send(self) -> Result<Response> {
let client = self.client;
let method = self.method.code();
self.with_c(|cpath, headers, bptr, blen| {
let mut resp: *mut sys::nwep_message = ptr::null_mut();
Error::check(unsafe {
sys::nwep_client_send(client.raw, method, cpath, headers, bptr, blen, &mut resp)
})?;
Ok(Response { msg: resp })
})
}
pub fn submit(self) -> Result<RequestHandle<'c>> {
let client = self.client;
let method = self.method.code();
let id = self.with_c(|cpath, headers, bptr, blen| {
let mut id = 0u64;
Error::check(unsafe {
sys::nwep_client_request_submit(
client.raw, method, cpath, headers, bptr, blen, &mut id,
)
})?;
Ok(id)
})?;
Ok(RequestHandle {
client,
id,
done: false,
})
}
fn with_c<R>(
self,
f: impl FnOnce(*const c_char, *const sys::nwep_header, *const u8, usize) -> Result<R>,
) -> Result<R> {
let cpath = CString::new(self.path).map_err(|_| Error::ProtoInvalidHeader)?;
let mut cstrings: Vec<(CString, CString)> = Vec::with_capacity(self.headers.len());
for (name, value) in self.headers {
let n = CString::new(name).map_err(|_| Error::ProtoInvalidHeader)?;
let v = CString::new(value).map_err(|_| Error::ProtoInvalidHeader)?;
cstrings.push((n, v));
}
let mut header_array: Vec<sys::nwep_header> = cstrings
.iter()
.map(|(n, v)| sys::nwep_header {
name: n.as_ptr(),
value: v.as_ptr(),
})
.collect();
header_array.push(sys::nwep_header {
name: ptr::null(),
value: ptr::null(),
});
let (bptr, blen) = if self.body.is_empty() {
(ptr::null(), 0)
} else {
(self.body.as_ptr(), self.body.len())
};
f(cpath.as_ptr(), header_array.as_ptr(), bptr, blen)
}
}
unsafe extern "C" fn done_trampoline(
_client: *mut sys::nwep_client,
id: u64,
status: core::ffi::c_int,
resp: *mut sys::nwep_message,
ud: *mut c_void,
) {
let hook = unsafe { &mut *(ud as *mut DoneHook) };
let result = if status == 0 {
Ok(Response { msg: resp })
} else {
Err(Error::from_code(status))
};
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| hook(id, result)));
}
impl Drop for Client {
fn drop(&mut self) {
unsafe { sys::nwep_client_close(self.raw) };
let done = self.done.replace(ptr::null_mut());
if !done.is_null() {
drop(unsafe { Box::from_raw(done) });
}
}
}
pub struct Connecting {
raw: *mut sys::nwep_client,
}
impl Connecting {
pub fn tick(&self, now_ms: i64) -> Result<()> {
Error::check(unsafe { sys::nwep_client_tick(self.raw, now_ms) })
}
pub fn poll(&self) -> Result<bool> {
match unsafe { sys::nwep_client_connect_poll(self.raw) } {
1 => Ok(true),
0 => Ok(false),
other => Err(Error::from_code(other)),
}
}
pub fn fd(&self) -> RawSocket {
crate::raw::from_c(unsafe { sys::nwep_client_fd(self.raw) })
}
pub fn next_timeout(&self, now_ms: i64) -> Option<u32> {
let ms = unsafe { sys::nwep_client_next_timeout_ms(self.raw, now_ms) };
if ms < 0 {
None
} else {
Some(ms as u32)
}
}
pub fn into_client(self) -> Client {
let raw = self.raw;
core::mem::forget(self);
Client::wrap(raw)
}
}
impl Drop for Connecting {
fn drop(&mut self) {
unsafe { sys::nwep_client_close(self.raw) };
}
}
pub type RequestId = u64;
pub struct RequestHandle<'c> {
client: &'c Client,
id: RequestId,
done: bool,
}
impl RequestHandle<'_> {
pub fn id(&self) -> RequestId {
self.id
}
pub fn poll(&mut self) -> Result<Option<Response>> {
let mut out: *mut sys::nwep_message = ptr::null_mut();
let rc = unsafe { sys::nwep_client_request_poll(self.client.raw, self.id, &mut out) };
match rc {
0 => {
self.done = true;
Ok(Some(Response { msg: out }))
}
c if c == Error::WouldBlock.code() => Ok(None),
other => {
self.done = true;
Err(Error::from_code(other))
}
}
}
pub fn cancel(mut self) {
self.cancel_inner();
}
fn cancel_inner(&mut self) {
if !self.done {
unsafe { sys::nwep_client_request_cancel(self.client.raw, self.id) };
self.done = true;
}
}
}
impl Drop for RequestHandle<'_> {
fn drop(&mut self) {
self.cancel_inner();
}
}