use alloc::string::{String, ToString};
use core::ffi::c_void;
use core::fmt;
use core::num::NonZero;
use core::pin::Pin;
use core::ptr::NonNull;
use core::task::{Context, Poll, Waker};
use crate::{
allocator::Box,
collections::Vec,
core::{Pool, Status},
ffi::{
ngx_addr_t, ngx_msec_t, ngx_resolve_name, ngx_resolve_start, ngx_resolver_ctx_t,
ngx_resolver_t, ngx_str_t,
},
};
use nginx_sys::{
NGX_RESOLVE_FORMERR, NGX_RESOLVE_NOTIMP, NGX_RESOLVE_NXDOMAIN, NGX_RESOLVE_REFUSED,
NGX_RESOLVE_SERVFAIL, NGX_RESOLVE_TIMEDOUT,
};
#[derive(Debug)]
pub enum Error {
NoResolver,
Resolver(ResolverError, String),
AllocationFailed,
Internal,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::NoResolver => write!(f, "No resolver configured"),
Error::Resolver(err, context) => write!(f, "{err}: resolving `{context}`"),
Error::AllocationFailed => write!(f, "Allocation failed"),
Error::Internal => write!(f, "Internal error"),
}
}
}
impl core::error::Error for Error {}
#[derive(Debug)]
pub enum ResolverError {
FormErr,
ServFail,
NXDomain,
NotImp,
Refused,
TimedOut,
Unknown(isize),
}
impl fmt::Display for ResolverError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ResolverError::FormErr => write!(f, "Format error"),
ResolverError::ServFail => write!(f, "Server Failure"),
ResolverError::NXDomain => write!(f, "Host not found"),
ResolverError::NotImp => write!(f, "Unimplemented"),
ResolverError::Refused => write!(f, "Refused"),
ResolverError::TimedOut => write!(f, "Timed out"),
ResolverError::Unknown(code) => write!(f, "Unknown NGX_RESOLVE error {code}"),
}
}
}
impl core::error::Error for ResolverError {}
impl From<NonZero<isize>> for ResolverError {
fn from(code: NonZero<isize>) -> ResolverError {
match code.get() as u32 {
NGX_RESOLVE_FORMERR => ResolverError::FormErr,
NGX_RESOLVE_SERVFAIL => ResolverError::ServFail,
NGX_RESOLVE_NXDOMAIN => ResolverError::NXDomain,
NGX_RESOLVE_NOTIMP => ResolverError::NotImp,
NGX_RESOLVE_REFUSED => ResolverError::Refused,
NGX_RESOLVE_TIMEDOUT => ResolverError::TimedOut,
_ => ResolverError::Unknown(code.get()),
}
}
}
type Res = Result<Vec<ngx_addr_t, Pool>, Error>;
pub struct Resolver {
resolver: NonNull<ngx_resolver_t>,
timeout: ngx_msec_t,
}
impl Resolver {
pub fn from_resolver(resolver: NonNull<ngx_resolver_t>, timeout: ngx_msec_t) -> Self {
Self { resolver, timeout }
}
pub async fn resolve_name(&self, name: &ngx_str_t, pool: &Pool) -> Res {
let mut resolver = Resolution::new(name, &ngx_str_t::empty(), self, pool)?;
resolver.as_mut().await
}
pub async fn resolve_service(&self, name: &ngx_str_t, service: &ngx_str_t, pool: &Pool) -> Res {
let mut resolver = Resolution::new(name, service, self, pool)?;
resolver.as_mut().await
}
}
struct Resolution<'a> {
complete: Option<Res>,
waker: Option<Waker>,
pool: &'a Pool,
ctx: Option<ResolverCtx>,
}
impl<'a> Resolution<'a> {
pub fn new(
name: &ngx_str_t,
service: &ngx_str_t,
resolver: &Resolver,
pool: &'a Pool,
) -> Result<Pin<Box<Self, Pool>>, Error> {
let mut this = Box::pin_in(
Resolution {
complete: None,
waker: None,
pool,
ctx: None,
},
pool.clone(),
);
let mut ctx = ResolverCtx::new(resolver.resolver)?;
ctx.name = *name;
ctx.service = *service;
ctx.timeout = resolver.timeout;
ctx.set_cancelable(1);
ctx.handler = Some(Self::handler);
{
let ptr: &mut Resolution = unsafe { Pin::into_inner_unchecked(this.as_mut()) };
ctx.data = ptr as *mut Resolution as *mut c_void;
}
let ctxp = ctx.0;
this.ctx = Some(ctx);
let ret = unsafe { Status(ngx_resolve_name(ctxp.as_ptr())) };
if !ret.is_ok() {
core::mem::forget(this.ctx.take());
return Err(Error::Internal);
}
Ok(this)
}
unsafe extern "C" fn handler(ctx: *mut ngx_resolver_ctx_t) {
let mut data = unsafe { NonNull::new_unchecked((*ctx).data as *mut Resolution) };
let this: &mut Resolution = unsafe { data.as_mut() };
if let Some(ctx) = this.ctx.take() {
this.complete = Some(ctx.into_result(this.pool));
}
if let Some(waker) = this.waker.take() {
waker.wake();
}
}
}
impl core::future::Future for Resolution<'_> {
type Output = Result<Vec<ngx_addr_t, Pool>, Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this: &mut Resolution = self.get_mut();
match this.complete.take() {
Some(res) => Poll::Ready(res),
None => {
match &mut this.waker {
None => {
this.waker = Some(cx.waker().clone());
}
Some(w) => w.clone_from(cx.waker()),
}
Poll::Pending
}
}
}
}
struct ResolverCtx(NonNull<ngx_resolver_ctx_t>);
impl core::ops::Deref for ResolverCtx {
type Target = ngx_resolver_ctx_t;
fn deref(&self) -> &Self::Target {
unsafe { self.0.as_ref() }
}
}
impl core::ops::DerefMut for ResolverCtx {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { self.0.as_mut() }
}
}
impl Drop for ResolverCtx {
fn drop(&mut self) {
unsafe {
nginx_sys::ngx_resolve_name_done(self.0.as_mut());
}
}
}
impl ResolverCtx {
pub fn new(resolver: NonNull<ngx_resolver_t>) -> Result<Self, Error> {
let ctx = unsafe { ngx_resolve_start(resolver.as_ptr(), core::ptr::null_mut()) };
NonNull::new(ctx).map(Self).ok_or(Error::AllocationFailed)
}
pub fn into_result(self, pool: &Pool) -> Result<Vec<ngx_addr_t, Pool>, Error> {
if let Some(e) = NonZero::new(self.state) {
return Err(Error::Resolver(
ResolverError::from(e),
self.name.to_string(),
));
}
if self.addrs.is_null() {
Err(Error::AllocationFailed)?;
}
let mut out = Vec::new_in(pool.clone());
if self.naddrs > 0 {
out.try_reserve_exact(self.naddrs)
.map_err(|_| Error::AllocationFailed)?;
for addr in unsafe { core::slice::from_raw_parts(self.addrs, self.naddrs) } {
out.push(copy_resolved_addr(addr, pool)?);
}
}
Ok(out)
}
}
fn copy_resolved_addr(
addr: &nginx_sys::ngx_resolver_addr_t,
pool: &Pool,
) -> Result<ngx_addr_t, Error> {
let sockaddr = pool.alloc(addr.socklen as usize) as *mut nginx_sys::sockaddr;
if sockaddr.is_null() {
Err(Error::AllocationFailed)?;
}
unsafe {
addr.sockaddr
.cast::<u8>()
.copy_to_nonoverlapping(sockaddr.cast(), addr.socklen as usize)
};
let name = unsafe { ngx_str_t::from_bytes(pool.as_ptr(), addr.name.as_bytes()) }
.ok_or(Error::AllocationFailed)?;
Ok(ngx_addr_t {
sockaddr,
socklen: addr.socklen,
name,
})
}