use std::fmt;
use std::ops::{Deref, Index, IndexMut};
use crate::{
Error,
Result,
types::Timestamp,
types::Duration,
};
#[derive(Debug, Clone)]
pub(super) enum VecOrSlice<'a, T> {
Vec(Vec<T>),
Slice(&'a [T]),
}
impl<'a, T> VecOrSlice<'a, T> {
const fn empty() -> Self {
VecOrSlice::Vec(Vec::new())
}
fn get(&self, i: usize) -> Option<&T> {
match self {
VecOrSlice::Vec(v) => v.get(i),
VecOrSlice::Slice(s) => s.get(i),
}
}
fn len(&self) -> usize {
match self {
VecOrSlice::Vec(v) => v.len(),
VecOrSlice::Slice(s) => s.len(),
}
}
fn resize(&mut self, size: usize, value: T)
where T: Clone
{
let v = self.as_mut();
v.resize(size, value);
}
pub(super) fn as_mut(&mut self) -> &mut Vec<T>
where T: Clone
{
let v: Vec<T> = match self {
VecOrSlice::Vec(ref mut v) => std::mem::take(v),
VecOrSlice::Slice(s) => s.to_vec(),
};
*self = VecOrSlice::Vec(v);
if let VecOrSlice::Vec(ref mut v) = self {
v
} else {
unreachable!()
}
}
}
impl<'a, T> Deref for VecOrSlice<'a, T> {
type Target = [T];
fn deref(&self) -> &Self::Target {
match self {
VecOrSlice::Vec(ref v) => &v[..],
VecOrSlice::Slice(s) => s,
}
}
}
impl<'a, T> Index<usize> for VecOrSlice<'a, T> {
type Output = T;
fn index(&self, i: usize) -> &T {
match self {
VecOrSlice::Vec(v) => &v[i],
VecOrSlice::Slice(s) => &s[i],
}
}
}
impl<'a, T> IndexMut<usize> for VecOrSlice<'a, T>
where T: Clone
{
fn index_mut(&mut self, i: usize) -> &mut T {
if let VecOrSlice::Slice(s) = self {
*self = VecOrSlice::Vec(s.to_vec());
};
match self {
VecOrSlice::Vec(v) => &mut v[i],
VecOrSlice::Slice(_) => unreachable!(),
}
}
}
#[derive(Debug, Clone)]
pub(super) struct CutoffList<A> {
pub(super) cutoffs: VecOrSlice<'static, Option<Timestamp>>,
pub(super) _a: std::marker::PhantomData<A>,
}
pub(super) const REJECT : Option<Timestamp> = Some(Timestamp::UNIX_EPOCH);
pub(super) const ACCEPT : Option<Timestamp> = None;
pub(super) const DEFAULT_POLICY : Option<Timestamp> = REJECT;
impl<A> Default for CutoffList<A> {
fn default() -> Self {
Self::reject_all()
}
}
impl<A> CutoffList<A> {
pub(super) const fn reject_all() -> Self {
Self {
cutoffs: VecOrSlice::empty(),
_a: std::marker::PhantomData,
}
}
}
impl<A> CutoffList<A>
where u8: From<A>,
A: fmt::Display,
A: std::clone::Clone
{
pub(super) fn set(&mut self, a: A, cutoff: Option<Timestamp>) {
let i : u8 = a.into();
let i : usize = i.into();
if i >= self.cutoffs.len() {
self.cutoffs.resize(i + 1, DEFAULT_POLICY);
}
self.cutoffs[i] = cutoff;
}
#[inline]
pub(super) fn cutoff(&self, a: A) -> Option<Timestamp> {
let i : u8 = a.into();
*self.cutoffs.get(i as usize).unwrap_or(&DEFAULT_POLICY)
}
#[inline]
pub(super) fn check(&self, a: A, time: Timestamp,
tolerance: Option<Duration>)
-> Result<()>
{
if let Some(cutoff) = self.cutoff(a.clone()) {
let cutoff = cutoff
.checked_add(tolerance.unwrap_or_else(|| Duration::seconds(0)))
.unwrap_or(Timestamp::MAX);
if time >= cutoff {
Err(Error::PolicyViolation(
a.to_string(), Some(cutoff.into())).into())
} else {
Ok(())
}
} else {
Ok(())
}
}
}
macro_rules! a_cutoff_list {
($name:ident, $algo:ty, $values_count:expr, $values:expr) => {
#[derive(Debug, Clone)]
enum $name {
Default(),
Custom(CutoffList<$algo>),
}
#[allow(unused)]
impl $name {
const DEFAULTS : [ Option<Timestamp>; $values_count ] = $values;
fn force(&mut self) -> &mut CutoffList<$algo> {
use crate::policy::cutofflist::VecOrSlice;
if let $name::Default() = self {
*self = $name::Custom(CutoffList {
cutoffs: VecOrSlice::Vec(Self::DEFAULTS.to_vec()),
_a: std::marker::PhantomData,
});
}
match self {
$name::Custom(ref mut l) => l,
_ => unreachable!(),
}
}
fn set(&mut self, a: $algo, cutoff: Option<Timestamp>) {
self.force().set(a, cutoff)
}
fn defaults(&mut self) {
*self = Self::Default();
}
fn reject_all(&mut self) {
*self = Self::Custom(CutoffList::reject_all());
}
fn cutoff(&self, a: $algo) -> Option<Timestamp> {
use crate::policy::cutofflist::DEFAULT_POLICY;
match self {
$name::Default() => {
let i : u8 = a.into();
let i : usize = i.into();
if i >= Self::DEFAULTS.len() {
DEFAULT_POLICY
} else {
Self::DEFAULTS[i]
}
}
$name::Custom(ref l) => l.cutoff(a),
}
}
fn check(&self, a: $algo, time: Timestamp, d: Option<types::Duration>)
-> Result<()>
{
use crate::policy::cutofflist::VecOrSlice;
match self {
$name::Default() => {
CutoffList {
cutoffs: VecOrSlice::Slice(&Self::DEFAULTS[..]),
_a: std::marker::PhantomData,
}.check(a, time, d)
}
$name::Custom(ref l) => l.check(a, time, d),
}
}
}
}
}
#[derive(Debug, Clone)]
pub(super) struct VersionedCutoffList<A> where A: 'static {
pub(super) unversioned_cutoffs: VecOrSlice<'static, Option<Timestamp>>,
pub(super) versioned_cutoffs:
VecOrSlice<'static, (A, u8, Option<Timestamp>)>,
pub(super) _a: std::marker::PhantomData<A>,
}
impl<A> Default for VersionedCutoffList<A> {
fn default() -> Self {
Self::reject_all()
}
}
impl<A> VersionedCutoffList<A> {
pub(super) const fn reject_all() -> Self {
Self {
unversioned_cutoffs: VecOrSlice::empty(),
versioned_cutoffs: VecOrSlice::empty(),
_a: std::marker::PhantomData,
}
}
}
impl<A> VersionedCutoffList<A>
where u8: From<A>,
A: fmt::Display,
A: std::clone::Clone,
A: Eq,
A: Ord,
{
pub(super) fn assert_sorted(&self) {
if cfg!(debug_assertions) || cfg!(test) {
for window in self.versioned_cutoffs.windows(2) {
let a = &window[0];
let b = &window[1];
assert!((&a.0, a.1) < (&b.0, b.1));
}
}
}
pub(super) fn set_versioned(&mut self,
algo: A, version: u8,
cutoff: Option<Timestamp>)
{
self.assert_sorted();
let cutofflist = self.versioned_cutoffs.as_mut();
match cutofflist.binary_search_by(|(a, v, _)| {
algo.cmp(a).then(version.cmp(v)).reverse()
}) {
Ok(i) => {
cutofflist[i] = (algo, version, cutoff);
}
Err(i) => {
cutofflist.insert(i, (algo, version, cutoff));
}
};
self.assert_sorted();
}
pub(super) fn set_unversioned(&mut self, algo: A,
cutoff: Option<Timestamp>)
{
let i: u8 = algo.into();
let i: usize = i.into();
if i >= self.unversioned_cutoffs.len() {
self.unversioned_cutoffs.resize(i + 1, DEFAULT_POLICY);
}
self.unversioned_cutoffs[i] = cutoff;
}
#[inline]
pub(super) fn cutoff(&self, algo: A, version: u8) -> Option<Timestamp> {
self.assert_sorted();
match self.versioned_cutoffs.binary_search_by(|(a, v, _)| {
algo.cmp(a).then(version.cmp(v)).reverse()
}) {
Ok(i) => {
self.versioned_cutoffs[i].2
}
Err(_loc) => {
*self.unversioned_cutoffs.get(u8::from(algo) as usize)
.unwrap_or(&DEFAULT_POLICY)
}
}
}
#[inline]
pub(super) fn check(&self, algo: A, version: u8, time: Timestamp,
tolerance: Option<Duration>)
-> Result<()>
{
if let Some(cutoff) = self.cutoff(algo.clone(), version) {
let cutoff = cutoff
.checked_add(tolerance.unwrap_or_else(|| Duration::seconds(0)))
.unwrap_or(Timestamp::MAX);
if time >= cutoff {
Err(Error::PolicyViolation(
format!("{} v{}", algo, version),
Some(cutoff.into())).into())
} else {
Ok(())
}
} else {
Ok(())
}
}
}
macro_rules! a_versioned_cutoff_list {
($name:ident, $algo:ty,
// A slice indexed by the algorithm.
$unversioned_values_count: expr, $unversioned_values: expr,
// A slice of the form: [ (algo, version, cutoff), ... ]
$versioned_values_count:expr, $versioned_values:expr) => {
#[derive(Debug, Clone)]
enum $name {
Default(),
Custom(VersionedCutoffList<$algo>),
}
impl std::ops::Deref for $name {
type Target = VersionedCutoffList<$algo>;
fn deref(&self) -> &Self::Target {
match self {
$name::Default() => &Self::DEFAULT,
$name::Custom(l) => l,
}
}
}
#[allow(unused)]
impl $name {
const VERSIONED_DEFAULTS:
[ ($algo, u8, Option<Timestamp>); $versioned_values_count ]
= $versioned_values;
const UNVERSIONED_DEFAULTS:
[ Option<Timestamp>; $unversioned_values_count ]
= $unversioned_values;
const DEFAULT: VersionedCutoffList<$algo> = VersionedCutoffList {
versioned_cutoffs:
crate::policy::cutofflist::VecOrSlice::Slice(
&Self::VERSIONED_DEFAULTS),
unversioned_cutoffs:
crate::policy::cutofflist::VecOrSlice::Slice(
&Self::UNVERSIONED_DEFAULTS),
_a: std::marker::PhantomData,
};
fn force(&mut self) -> &mut VersionedCutoffList<$algo> {
use crate::policy::cutofflist::VecOrSlice;
if let $name::Default() = self {
*self = Self::Custom($name::DEFAULT);
}
match self {
$name::Custom(ref mut l) => l,
_ => unreachable!(),
}
}
fn set_versioned(&mut self, algo: $algo, version: u8,
cutoff: Option<Timestamp>)
{
self.force().set_versioned(algo, version, cutoff)
}
fn set_unversioned(&mut self, algo: $algo,
cutoff: Option<Timestamp>)
{
let l = self.force();
l.versioned_cutoffs.as_mut().retain(|(a, _v, _c)| {
&algo != a
});
l.set_unversioned(algo, cutoff)
}
fn defaults(&mut self) {
*self = Self::Default();
}
fn reject_all(&mut self) {
*self = Self::Custom(VersionedCutoffList::reject_all());
}
fn cutoff(&self, algo: $algo, version: u8) -> Option<Timestamp> {
let cutofflist = if let $name::Custom(ref l) = self {
l
} else {
&Self::DEFAULT
};
cutofflist.cutoff(algo, version)
}
fn check(&self, algo: $algo, version: u8,
time: Timestamp, d: Option<types::Duration>)
-> Result<()>
{
let cutofflist = if let $name::Custom(ref l) = self {
l
} else {
&Self::DEFAULT
};
cutofflist.check(algo, version, time, d)
}
}
#[test]
#[allow(non_snake_case)]
fn $name() {
$name::DEFAULT.assert_sorted();
}
}
}