use std::ffi::{c_char, c_int, c_void, CStr, CString};
use std::marker::PhantomData;
use std::mem::size_of;
use std::net::{SocketAddr, TcpStream};
use std::os::unix::io::FromRawFd;
use std::ptr;
use std::ptr::{null, null_mut};
use std::time::SystemTime;
use crate::ffi::{
vrt_ctx, vsa_suckaddr_len, VclEvent, VfpStatus, VCL_BACKEND, VCL_BOOL, VCL_IP, VCL_TIME,
VCL_VCL, VRT_CTX_MAGIC,
};
#[cfg(varnishsys_90_sslflags)]
use crate::ffi::{BSSL_F_ENABLE, BSSL_F_NOVERIFY, BSSL_F_VERIFY_HOST};
use crate::utils::get_backend;
use crate::vcl::{Buffer, Ctx, IntoVCL, LogTag, VclError, VclResult, Workspace};
use crate::{
ffi, validate_director, validate_vdir, validate_vfp_ctx, validate_vfp_entry, validate_vrt_ctx,
};
use super::BackendRef;
#[derive(Debug)]
pub struct NativeVclBackendShim;
impl VclBackend<NativeVclResponseShim> for NativeVclBackendShim {
fn get_response(&self, _ctx: &mut Ctx) -> Result<Option<NativeVclResponseShim>, VclError> {
Ok(None)
}
}
pub type NativeBackend = Backend<NativeVclBackendShim, NativeVclResponseShim>;
#[derive(Debug)]
pub struct NativeVclResponseShim;
impl VclResponse for NativeVclResponseShim {}
#[derive(Debug)]
pub struct Backend<S: VclBackend<T>, T: VclResponse> {
#[expect(dead_code)]
methods: Box<ffi::vdi_methods>,
inner: Box<S>,
#[expect(dead_code)]
ctype: CString,
phantom: PhantomData<T>,
#[allow(clippy::struct_field_names)]
backend_ref: BackendRef,
native_configuration: Option<(Box<ffi::vrt_endpoint>, Box<ffi::vrt_backend>)>,
}
impl<S: VclBackend<T>, T: VclResponse> Backend<S, T> {
pub fn get_inner(&self) -> &S {
&self.inner
}
pub fn new(
ctx: &mut Ctx,
backend_type: &str,
backend_id: &str,
be: S,
has_probe: bool,
) -> VclResult<Self> {
let mut inner = Box::new(be);
let ctype: CString = CString::new(backend_type).map_err(|e| e.to_string())?;
let cname: CString = CString::new(backend_id).map_err(|e| e.to_string())?;
let methods = Box::new(ffi::vdi_methods {
type_: ctype.as_ptr(),
magic: ffi::VDI_METHODS_MAGIC,
destroy: None,
event: Some(wrap_event::<S, T>),
finish: Some(wrap_finish::<S, T>),
gethdrs: Some(wrap_gethdrs::<S, T>),
getip: Some(wrap_getip::<T>),
healthy: has_probe.then_some(wrap_healthy::<S, T>),
http1pipe: Some(wrap_pipe::<S, T>),
list: Some(wrap_list::<S, T>),
panic: Some(wrap_panic::<S, T>),
resolve: None,
release: None,
});
let bep = unsafe {
ffi::VRT_AddDirector(
ctx.raw,
&raw const *methods,
ptr::from_mut::<S>(&mut *inner).cast::<c_void>(),
c"%.*s".as_ptr(),
cname.as_bytes().len(),
cname.as_ptr().cast::<c_char>(),
)
};
if bep.0.is_null() {
return Err(format!("VRT_AddDirector return null while creating {backend_id}").into());
}
let backend_ref = unsafe {
BackendRef::new_without_refcount(bep).expect("Backend pointer should never be null")
};
Ok(Backend {
ctype,
inner,
methods,
phantom: PhantomData,
backend_ref,
native_configuration: None,
})
}
}
pub trait VclBackend<T: VclResponse> {
fn get_response(&self, _ctx: &mut Ctx) -> Result<Option<T>, VclError>;
fn finish(&self, _ctx: &mut Ctx) {}
fn probe(&self, _ctx: &mut Ctx) -> (bool, SystemTime) {
(true, SystemTime::UNIX_EPOCH)
}
fn pipe(&self, ctx: &mut Ctx, _tcp_stream: TcpStream) -> StreamClose {
ctx.log(LogTag::Error, "Backend does not support pipe");
StreamClose::TxError
}
fn event(&self, _event: VclEvent) {}
fn panic(&self, _vsb: &mut Buffer) {}
fn report(&self, ctx: &mut Ctx, vsb: &mut Buffer) {
let state = if self.probe(ctx).0 { "healthy" } else { "sick" };
vsb.write(&"0/0\t").expect("VSB write must succeed");
vsb.write(&state).expect("VSB write must succeed");
}
fn report_details(&self, _ctx: &mut Ctx, _vsb: &mut Buffer) {}
fn report_json(&self, ctx: &mut Ctx, vsb: &mut Buffer) {
let state = if self.probe(ctx).0 { "healthy" } else { "sick" };
vsb.write(&"[0, 0, ").expect("VSB write must succeed");
vsb.write(&state).expect("VSB write must succeed");
vsb.write(&"]").expect("VSB write must succeed");
}
fn report_details_json(&self, _ctx: &mut Ctx, vsb: &mut Buffer) {
let _ = vsb.write(&"{}");
}
}
#[expect(clippy::len_without_is_empty)] pub trait VclResponse {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, VclError> {
let _ = buf;
Ok(0)
}
fn len(&self) -> Option<usize> {
None
}
fn get_ip(&self) -> Result<Option<SocketAddr>, VclError> {
Ok(None)
}
}
impl VclResponse for () {
fn read(&mut self, _buf: &mut [u8]) -> Result<usize, VclError> {
Ok(0)
}
}
impl<S: VclBackend<T>, T: VclResponse> Drop for Backend<S, T> {
fn drop(&mut self) {
unsafe {
let mut bep = self.backend_ref.vcl_ptr();
if self.native_configuration.is_some() {
ffi::VRT_delete_backend(null(), &raw mut bep);
} else {
ffi::VRT_DelDirector(&raw mut bep);
}
};
}
}
impl<S: VclBackend<T>, T: VclResponse> AsRef<BackendRef> for Backend<S, T> {
fn as_ref(&self) -> &BackendRef {
&self.backend_ref
}
}
#[derive(Debug, Clone, Copy)]
pub enum StreamClose {
RemClose,
ReqClose,
ReqHttp10,
RxBad,
RxBody,
RxJunk,
RxOverflow,
RxTimeout,
RxCloseIdle,
TxPipe,
TxError,
TxEof,
RespClose,
Overload,
PipeOverflow,
RangeShort,
ReqHttp20,
VclFailure,
}
fn sc_to_ptr(sc: StreamClose) -> ffi::stream_close_t {
unsafe {
match sc {
StreamClose::RemClose => ffi::SC_REM_CLOSE.as_ptr(),
StreamClose::ReqClose => ffi::SC_REQ_CLOSE.as_ptr(),
StreamClose::ReqHttp10 => ffi::SC_REQ_HTTP10.as_ptr(),
StreamClose::RxBad => ffi::SC_RX_BAD.as_ptr(),
StreamClose::RxBody => ffi::SC_RX_BODY.as_ptr(),
StreamClose::RxJunk => ffi::SC_RX_JUNK.as_ptr(),
StreamClose::RxOverflow => ffi::SC_RX_OVERFLOW.as_ptr(),
StreamClose::RxTimeout => ffi::SC_RX_TIMEOUT.as_ptr(),
StreamClose::RxCloseIdle => ffi::SC_RX_CLOSE_IDLE.as_ptr(),
StreamClose::TxPipe => ffi::SC_TX_PIPE.as_ptr(),
StreamClose::TxError => ffi::SC_TX_ERROR.as_ptr(),
StreamClose::TxEof => ffi::SC_TX_EOF.as_ptr(),
StreamClose::RespClose => ffi::SC_RESP_CLOSE.as_ptr(),
StreamClose::Overload => ffi::SC_OVERLOAD.as_ptr(),
StreamClose::PipeOverflow => ffi::SC_PIPE_OVERFLOW.as_ptr(),
StreamClose::RangeShort => ffi::SC_RANGE_SHORT.as_ptr(),
StreamClose::ReqHttp20 => ffi::SC_REQ_HTTP20.as_ptr(),
StreamClose::VclFailure => ffi::SC_VCL_FAILURE.as_ptr(),
}
}
}
#[derive(Debug, Clone, Copy)]
enum BackendEndpoint<'a> {
Ip(SocketAddr),
Uds(&'a CStr),
}
#[derive(Debug)]
pub struct NativeBackendBuilder<'a> {
endpoint: Option<BackendEndpoint<'a>>,
vcl_name: &'a CStr,
hosthdr: Option<&'a CStr>,
authority: Option<&'a CStr>,
connect_timeout: Option<std::time::Duration>,
first_byte_timeout: Option<std::time::Duration>,
between_bytes_timeout: Option<std::time::Duration>,
backend_wait_timeout: Option<std::time::Duration>,
max_connections: Option<u32>,
proxy_header: Option<u32>,
backend_wait_limit: Option<u32>,
#[cfg(varnishsys_90_sslflags)]
sslflags: std::ffi::c_uint,
}
macro_rules! builder_setter {
($name:ident, $type:ty, $doc:expr) => {
#[doc = $doc]
#[must_use]
pub fn $name(mut self, $name: $type) -> Self {
self.$name = Some($name);
self
}
};
}
impl<'a> NativeBackendBuilder<'a> {
pub fn new_ip(vcl_name: &'a CStr, addr: SocketAddr) -> Self {
Self {
endpoint: Some(BackendEndpoint::Ip(addr)),
vcl_name,
hosthdr: None,
authority: None,
connect_timeout: None,
first_byte_timeout: None,
between_bytes_timeout: None,
backend_wait_timeout: None,
max_connections: None,
proxy_header: None,
backend_wait_limit: None,
#[cfg(varnishsys_90_sslflags)]
sslflags: 0,
}
}
pub fn new_uds(vcl_name: &'a CStr, path: &'a CStr) -> Self {
Self {
endpoint: Some(BackendEndpoint::Uds(path)),
vcl_name,
hosthdr: None,
authority: None,
connect_timeout: None,
first_byte_timeout: None,
between_bytes_timeout: None,
backend_wait_timeout: None,
max_connections: None,
proxy_header: None,
backend_wait_limit: None,
#[cfg(varnishsys_90_sslflags)]
sslflags: 0,
}
}
builder_setter!(
authority,
&'a CStr,
"Set the authority for this backend when connecting with the `PROXY`
protocol"
);
builder_setter!(
connect_timeout,
std::time::Duration,
" Set the connection timeout to the backend. Negative will count as 0s.
Native backends pool their connections, meaning that connecting may not
be necessary for all request."
);
builder_setter!(
first_byte_timeout,
std::time::Duration,
"Set the timeout for the first byte of the backend response."
);
builder_setter!(
between_bytes_timeout,
std::time::Duration,
" Set the timeout for receving each bytes. In pratice, it's like more of
a \"between TCP packet\"."
);
builder_setter!(
max_connections,
u32,
"Set the number of connections to pool. If a new connection needs to be
created while already at the limit, the request will be queued. See
also `backend_wait_limit` and `backend_wait_timeout`."
);
builder_setter!(
backend_wait_limit,
u32,
"Set how many requests can be queue while waiting for a connection. If
the queue is full, new request will go directly to `vcl_backend_error`."
);
builder_setter!(
backend_wait_timeout,
std::time::Duration,
"Set the time a request can wait for a connection if `max_connections`
is at its maximum."
);
#[cfg(varnishsys_90_sslflags)]
builder_setter!(
hosthdr,
&'a CStr,
"Set the Host header to use sending a request that doesn't have a
`Host` header. "
);
#[must_use]
pub fn proxy_v1(mut self) -> Self {
self.proxy_header = Some(1);
self
}
#[must_use]
pub fn proxy_v2(mut self) -> Self {
self.proxy_header = Some(2);
self
}
#[cfg(varnishsys_90_sslflags)]
#[must_use]
pub fn tls(mut self, verify_host: bool, verify_peer: bool) -> Self {
self.sslflags |= BSSL_F_ENABLE;
if verify_host {
self.sslflags |= BSSL_F_VERIFY_HOST;
} else {
self.sslflags &= !BSSL_F_VERIFY_HOST;
}
if verify_peer {
self.sslflags &= !BSSL_F_NOVERIFY;
} else {
self.sslflags |= BSSL_F_NOVERIFY;
}
self
}
pub unsafe fn build_with_vcl(
self,
vcl: *mut ffi::vcl,
) -> VclResult<Backend<NativeVclBackendShim, NativeVclResponseShim>> {
let raw_ctx = vrt_ctx {
magic: VRT_CTX_MAGIC,
vcl: VCL_VCL(vcl),
..Default::default()
};
self.build_with_raw_ctx(&raw const raw_ctx)
}
pub fn build(
self,
ctx: &mut Ctx,
) -> VclResult<Backend<NativeVclBackendShim, NativeVclResponseShim>> {
unsafe { self.build_with_raw_ctx(ctx.raw) }
}
unsafe fn build_with_raw_ctx(
self,
raw: *const vrt_ctx,
) -> VclResult<Backend<NativeVclBackendShim, NativeVclResponseShim>> {
let endpoint_type = self
.endpoint
.expect("endpoint must be set before calling build()");
let mut endpoint = Box::new(ffi::vrt_endpoint {
magic: ffi::VRT_ENDPOINT_MAGIC,
ipv4: VCL_IP(null()),
ipv6: VCL_IP(null()),
uds_path: null(),
preamble: null(),
#[cfg(varnishsys_90_sslflags)]
hosthdr: match self.hosthdr {
Some(s) => s.as_ptr(),
None => null(),
},
#[cfg(varnishsys_90_sslflags)]
sslflags: self.sslflags,
});
let mut sa_buf = vec![0u8; vsa_suckaddr_len];
match endpoint_type {
BackendEndpoint::Uds(path) => {
endpoint.uds_path = path.as_ptr();
}
BackendEndpoint::Ip(addr) => {
crate::vcl::convert::write_ip_to_buf(addr, &mut sa_buf);
match addr {
SocketAddr::V4(_) => endpoint.ipv4 = VCL_IP(sa_buf.as_ptr().cast()),
SocketAddr::V6(_) => endpoint.ipv6 = VCL_IP(sa_buf.as_ptr().cast()),
}
}
}
let backend_config = Box::new(ffi::vrt_backend {
magic: ffi::VRT_BACKEND_MAGIC,
endpoint: &raw const *endpoint,
vcl_name: self.vcl_name.as_ptr(),
hosthdr: self.hosthdr.map_or(null(), CStr::as_ptr),
authority: self.authority.map_or(null(), CStr::as_ptr),
connect_timeout: ffi::vtim_dur(self.connect_timeout.map_or(-1.0, |d| d.as_secs_f64())),
first_byte_timeout: ffi::vtim_dur(
self.first_byte_timeout.map_or(-1.0, |d| d.as_secs_f64()),
),
between_bytes_timeout: ffi::vtim_dur(
self.between_bytes_timeout.map_or(-1.0, |d| d.as_secs_f64()),
),
backend_wait_timeout: ffi::vtim_dur(
self.backend_wait_timeout.map_or(-1.0, |d| d.as_secs_f64()),
),
max_connections: self.max_connections.unwrap_or(0),
proxy_header: self.proxy_header.unwrap_or(0),
backend_wait_limit: self.backend_wait_limit.unwrap_or(0),
probe: ffi::VCL_PROBE(null()),
});
let bep = ffi::VRT_new_backend(
raw.cast_mut(),
&raw const *backend_config,
VCL_BACKEND(null()),
);
if bep.0.is_null() {
return Err(format!(
"VRT_new_backend returned null for {}",
self.vcl_name.to_string_lossy()
)
.into());
}
let methods = Box::new(ffi::vdi_methods::default());
let backend_ref =
BackendRef::new_without_refcount(bep).expect("Backend pointer should never be null");
Ok(Backend {
methods,
inner: Box::new(NativeVclBackendShim),
ctype: CString::new("native").expect("\"native\" is a valid C string"),
phantom: PhantomData,
backend_ref,
native_configuration: Some((endpoint, backend_config)),
})
}
}
unsafe extern "C" fn vfp_pull<T: VclResponse>(
ctxp: *mut ffi::vfp_ctx,
vfep: *mut ffi::vfp_entry,
ptr: *mut c_void,
len: *mut isize,
) -> VfpStatus {
let ctx = validate_vfp_ctx(ctxp);
let vfe = validate_vfp_entry(vfep);
let buf = std::slice::from_raw_parts_mut(ptr.cast::<u8>(), *len as usize);
if buf.is_empty() {
*len = 0;
return VfpStatus::Ok;
}
let reader = vfe
.priv1
.cast::<T>()
.as_mut()
.expect("vfp_entry priv1 must not be null during pull");
match reader.read(buf) {
Err(e) => {
let msg = ffi::txt::from_str(e.as_str().as_ref());
ffi::VSLbt(
ctx.req
.as_ref()
.expect("req must be set when VFP is active")
.vsl,
ffi::VslTag::Error,
msg,
);
VfpStatus::Error
}
Ok(0) => {
*len = 0;
VfpStatus::End
}
Ok(l) => {
*len = l as isize;
VfpStatus::Ok
}
}
}
unsafe extern "C" fn wrap_event<S: VclBackend<T>, T: VclResponse>(be: VCL_BACKEND, ev: VclEvent) {
let backend: &S = get_backend(validate_director(be));
backend.event(ev);
}
unsafe extern "C" fn wrap_list<S: VclBackend<T>, T: VclResponse>(
ctxp: *const vrt_ctx,
be: VCL_BACKEND,
vsbp: *mut ffi::vsb,
detailed: i32,
json: i32,
) {
let mut ctx = Ctx::from_ptr(ctxp);
let mut vsb = Buffer::from_ptr(vsbp);
let backend: &S = get_backend(validate_director(be));
match (json != 0, detailed != 0) {
(true, true) => backend.report_details_json(&mut ctx, &mut vsb),
(true, false) => backend.report_json(&mut ctx, &mut vsb),
(false, true) => backend.report_details(&mut ctx, &mut vsb),
(false, false) => backend.report(&mut ctx, &mut vsb),
}
}
unsafe extern "C" fn wrap_panic<S: VclBackend<T>, T: VclResponse>(
be: VCL_BACKEND,
vsbp: *mut ffi::vsb,
) {
let mut vsb = Buffer::from_ptr(vsbp);
let backend: &S = get_backend(validate_director(be));
backend.panic(&mut vsb);
}
unsafe extern "C" fn wrap_pipe<S: VclBackend<T>, T: VclResponse>(
ctxp: *const vrt_ctx,
be: VCL_BACKEND,
) -> ffi::stream_close_t {
let mut ctx = Ctx::from_ptr(ctxp);
let req = ctx.raw.validated_req();
let sp = req.validated_session();
let fd = sp.fd;
assert_ne!(fd, 0);
let tcp_stream = TcpStream::from_raw_fd(fd);
let backend: &S = get_backend(validate_director(be));
sc_to_ptr(backend.pipe(&mut ctx, tcp_stream))
}
impl VCL_BACKEND {
unsafe fn get_type(&self) -> &str {
CStr::from_ptr(
self.0
.as_ref()
.expect("VCL_BACKEND pointer must not be null")
.vdir
.as_ref()
.expect("director vdir must not be null")
.methods
.as_ref()
.expect("director methods must not be null")
.type_
.as_ref()
.expect("director type_ pointer must not be null"),
)
.to_str()
.expect("director type string must be valid UTF-8")
}
}
#[allow(clippy::too_many_lines)] unsafe extern "C" fn wrap_gethdrs<S: VclBackend<T>, T: VclResponse>(
ctxp: *const vrt_ctx,
bep: VCL_BACKEND,
) -> c_int {
let mut ctx = Ctx::from_ptr(ctxp);
let be = validate_director(bep);
let backend: &S = get_backend(be);
assert!(!be.vcl_name.is_null()); validate_vdir(be);
match backend.get_response(&mut ctx) {
Ok(res) => {
let beresp = ctx
.http_beresp
.as_mut()
.expect("http_beresp must be set during backend gethdrs");
if beresp.status().is_none() {
beresp.set_status(200);
}
if beresp.proto().is_none() {
if let Err(e) = beresp.set_proto("HTTP/1.1") {
ctx.fail(format!("{:?}: {e}", bep.get_type()));
return 1;
}
}
let bo = ctx
.raw
.bo
.as_mut()
.expect("busyobj must not be null during backend gethdrs");
let Some(htc) = ffi::WS_Alloc(bo.ws.as_mut_ptr(), size_of::<ffi::http_conn>() as u32)
.cast::<ffi::http_conn>()
.as_mut()
else {
ctx.fail(format!("{}: insufficient workspace", bep.get_type()));
return -1;
};
htc.magic = ffi::HTTP_CONN_MAGIC;
htc.doclose = &raw const ffi::SC_REM_CLOSE[0];
htc.content_length = 0;
match res {
None => {
htc.body_status = ffi::BS_NONE.as_ptr();
}
Some(transfer) => {
match transfer.len() {
None => {
htc.body_status = ffi::BS_CHUNKED.as_ptr();
htc.content_length = -1;
}
Some(0) => {
htc.body_status = ffi::BS_NONE.as_ptr();
}
Some(l) => {
htc.body_status = ffi::BS_LENGTH.as_ptr();
htc.content_length = l as isize;
}
}
htc.priv_ = Box::into_raw(Box::new(transfer)).cast::<c_void>();
if htc.body_status != ffi::BS_NONE.as_ptr() {
let Some(vfp) =
ffi::WS_Alloc(bo.ws.as_mut_ptr(), size_of::<ffi::vfp>() as u32)
.cast::<ffi::vfp>()
.as_mut()
else {
ctx.fail(format!("{}: insufficient workspace", bep.get_type()));
return -1;
};
let Ok(t) = Workspace::from_ptr(bo.ws.as_mut_ptr())
.copy_bytes_with_null(bep.get_type())
else {
ctx.fail(format!("{}: insufficient workspace", bep.get_type()));
return -1;
};
vfp.name = t.b;
vfp.init = None;
vfp.pull = Some(vfp_pull::<T>);
vfp.fini = None;
vfp.priv1 = null();
let Some(vfe) = ffi::VFP_Push(bo.vfc, vfp).as_mut() else {
ctx.fail(format!("{}: couldn't insert vfp", bep.get_type()));
return -1;
};
vfe.priv1 = htc.priv_;
}
}
}
bo.htc = htc;
0
}
Err(s) => {
let typ = bep.get_type();
ctx.log(LogTag::FetchError, format!("{typ}: {s}"));
1
}
}
}
unsafe extern "C" fn wrap_healthy<S: VclBackend<T>, T: VclResponse>(
ctxp: *const vrt_ctx,
be: VCL_BACKEND,
changed: *mut VCL_TIME,
) -> VCL_BOOL {
let backend: &S = get_backend(validate_director(be));
let mut ctx = Ctx::from_ptr(ctxp);
let (healthy, when) = backend.probe(&mut ctx);
if !changed.is_null() {
if let Ok(t) = when.try_into() {
*changed = t;
}
}
healthy.into()
}
unsafe extern "C" fn wrap_getip<T: VclResponse>(ctxp: *const vrt_ctx, _be: VCL_BACKEND) -> VCL_IP {
let ctxp = validate_vrt_ctx(ctxp);
let bo = ctxp
.bo
.as_ref()
.expect("busyobj must not be null during getip");
assert_eq!(bo.magic, ffi::BUSYOBJ_MAGIC);
let htc = bo
.htc
.as_ref()
.expect("http_conn must not be null during getip");
assert_eq!(htc.magic, ffi::BUSYOBJ_MAGIC);
let transfer = htc
.priv_
.cast::<T>()
.as_ref()
.expect("http_conn priv_ must not be null during getip");
let mut ctx = Ctx::from_ptr(ctxp);
transfer
.get_ip()
.and_then(|ip| match ip {
Some(ip) => Ok(ip.into_vcl(&mut ctx.ws)?),
None => Ok(VCL_IP(null())),
})
.unwrap_or_else(|e| {
ctx.fail(format!("{e}"));
VCL_IP(null())
})
}
unsafe extern "C" fn wrap_finish<S: VclBackend<T>, T: VclResponse>(
ctxp: *const vrt_ctx,
be: VCL_BACKEND,
) {
let prev_backend: &S = get_backend(validate_director(be));
let ctx = ctxp
.as_ref()
.expect("vrt_ctx pointer must not be null during backend finish");
let bo = ctx
.bo
.as_mut()
.expect("busyobj must not be null during backend finish");
if let Some(htc) = ptr::replace(&raw mut bo.htc, null_mut()).as_mut() {
if let Some(val) = ptr::replace(&raw mut htc.priv_, null_mut())
.cast::<T>()
.as_mut()
{
drop(Box::from_raw(val));
}
}
prev_backend.finish(&mut Ctx::from_ptr(ctx));
}
#[cfg(all(test, varnishsys_90_sslflags))]
mod tests {
use std::net::SocketAddr;
use super::*;
use crate::ffi::BSSL_F_ENABLE;
fn builder() -> NativeBackendBuilder<'static> {
let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
NativeBackendBuilder::new_ip(c"test", addr)
}
#[test]
fn fresh_builder_has_tls_disabled() {
assert_eq!(builder().sslflags & BSSL_F_ENABLE, 0);
}
#[test]
fn tls_sets_enable_flag_when_verify_all() {
let b = builder().tls(true, true);
assert_ne!(
b.sslflags & BSSL_F_ENABLE,
0,
"tls(true, true) must set BSSL_F_ENABLE, got sslflags={:#x}",
b.sslflags,
);
}
#[test]
fn tls_sets_enable_flag_when_verify_none() {
let b = builder().tls(false, false);
assert_ne!(
b.sslflags & BSSL_F_ENABLE,
0,
"tls(false, false) must set BSSL_F_ENABLE, got sslflags={:#x}",
b.sslflags,
);
}
#[test]
fn tls_sets_enable_flag_when_verify_peer_only() {
let b = builder().tls(false, true);
assert_ne!(
b.sslflags & BSSL_F_ENABLE,
0,
"tls(false, true) must set BSSL_F_ENABLE, got sslflags={:#x}",
b.sslflags,
);
}
}