use std::time::Duration;
use crate::Backend;
use fastly_shared::FastlyStatus;
use fastly_sys::fastly_shielding;
const MAXIMUM_BACKEND_NAME_LENGTH: usize = 1024;
pub struct Shield {
plain_target: String,
ssl_target: String,
first_byte_timeout: Option<Duration>,
is_me: bool,
}
impl Shield {
pub fn new<S: AsRef<str>>(name: S) -> Result<Self, FastlyStatus> {
let name_bytes = name.as_ref().as_bytes();
let mut out_buffer_size = 1024;
let out_buffer = loop {
let mut out_buffer = vec![0; out_buffer_size];
let mut used_amt = 0;
let result = unsafe {
fastly_shielding::shield_info(
name_bytes.as_ptr(),
name_bytes.len(),
out_buffer.as_mut_ptr(),
out_buffer_size,
&mut used_amt,
)
};
match result {
FastlyStatus::OK => {
out_buffer.resize(used_amt as usize, 0);
break out_buffer;
}
FastlyStatus::BUFLEN => {
out_buffer_size *= 2;
}
_ => return Err(result),
}
};
if out_buffer.len() < 3 {
return Err(FastlyStatus::ERROR);
}
let is_me = out_buffer[0] != 0;
let mut strings = out_buffer[1..].split(|c| *c == 0);
let plain_bytes = strings.next().ok_or(FastlyStatus::ERROR)?;
let ssl_bytes = strings.next().ok_or(FastlyStatus::ERROR)?;
let empty = strings.next().ok_or(FastlyStatus::ERROR)?;
if !empty.is_empty() {
return Err(FastlyStatus::ERROR);
}
if strings.next().is_some() {
return Err(FastlyStatus::ERROR);
}
let plain_target =
String::from_utf8(plain_bytes.to_vec()).map_err(|_| FastlyStatus::ERROR)?;
let ssl_target = String::from_utf8(ssl_bytes.to_vec()).map_err(|_| FastlyStatus::ERROR)?;
Ok(Shield {
is_me,
plain_target,
ssl_target,
first_byte_timeout: None,
})
}
pub fn running_on(&self) -> bool {
self.is_me
}
#[must_use]
pub fn with_first_byte_timeout(&self, timeout: Duration) -> Self {
Self {
first_byte_timeout: Some(timeout),
is_me: self.is_me,
plain_target: self.plain_target.clone(),
ssl_target: self.ssl_target.clone(),
}
}
pub fn unencrypted_backend(&self) -> Result<Backend, FastlyStatus> {
self.backend_builder(false).finish()
}
pub fn encrypted_backend(&self) -> Result<Backend, FastlyStatus> {
self.backend_builder(true).finish()
}
fn backend_builder(&self, encrypt_data: bool) -> ShieldBackendBuilder<'_> {
ShieldBackendBuilder {
_originating_shield: self,
chosen_backend: if encrypt_data {
self.ssl_target.as_str()
} else {
self.plain_target.as_str()
},
cache_key: None,
}
}
}
struct ShieldBackendBuilder<'a> {
_originating_shield: &'a Shield,
chosen_backend: &'a str,
cache_key: Option<String>,
}
impl ShieldBackendBuilder<'_> {
pub fn finish(self) -> Result<Backend, FastlyStatus> {
use fastly_shielding::{backend_for_shield, ShieldBackendConfig, ShieldBackendOptions};
let name_bytes = self.chosen_backend.as_bytes();
let name_len = name_bytes.len();
let mut options_mask = ShieldBackendOptions::default();
let mut options = ShieldBackendConfig::default();
let mut backend_name_buffer = vec![0; MAXIMUM_BACKEND_NAME_LENGTH];
let mut final_backend_name_len = 0;
if let Some(cache_key) = self.cache_key.as_deref() {
options_mask.insert(ShieldBackendOptions::CACHE_KEY);
options.cache_key = cache_key.as_ptr();
options.cache_key_len = cache_key.len() as u32;
}
if let Some(timeout_ms) = self._originating_shield.first_byte_timeout {
options_mask.insert(ShieldBackendOptions::FIRST_BYTE_TIMEOUT);
options.first_byte_timeout_ms = timeout_ms.as_millis().try_into().unwrap_or(u32::MAX);
}
let result = unsafe {
backend_for_shield(
name_bytes.as_ptr(),
name_len,
options_mask,
&options,
backend_name_buffer.as_mut_ptr(),
MAXIMUM_BACKEND_NAME_LENGTH,
&mut final_backend_name_len,
)
};
if result != FastlyStatus::OK {
return Err(result);
}
backend_name_buffer.resize(final_backend_name_len as usize, 0);
let backend_name =
String::from_utf8(backend_name_buffer).map_err(|_| FastlyStatus::ERROR)?;
Backend::from_name(&backend_name).map_err(|_| FastlyStatus::ERROR)
}
}