mod check;
mod constparse;
mod generate;
mod vouch;
#[derive(Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(not(feature = "prost"), derive(Debug))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "prost", derive(prost::Message))]
#[repr(transparent)]
pub struct Voucher(#[cfg_attr(feature = "prost", prost(fixed64, tag = "1"))] u64);
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct CheckingParameters {
unoffset: u64,
unscale: u64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct VouchingParameters {
offset: u64,
scale: u64,
checking: CheckingParameters,
}
impl CheckingParameters {
#[inline(always)]
pub const fn parse(string: &str) -> Result<CheckingParameters, &'static str> {
Self::parse_bytes(string.as_bytes())
}
#[inline(never)]
pub const fn parse_or_die(string: &str) -> CheckingParameters {
match Self::parse(string) {
Ok(ret) => ret,
Err(_) => panic!("failed to parse checking parameter string."),
}
}
#[must_use]
#[inline(always)]
pub const fn check(self, expected: u64, voucher: Voucher) -> bool {
check::check(self.unoffset, self.unscale, expected, voucher.0)
}
#[must_use]
pub fn check_many(self, expected: &[u64], vouchers: &[Voucher]) -> bool {
if expected.len() != vouchers.len() {
return false;
}
std::iter::zip(expected.iter(), vouchers.iter())
.enumerate()
.all(|(idx, (expected, voucher))| {
let input_rot = (idx % 64) as u32;
let voucher_rot = (idx % 63) as u32;
self.check(
expected.rotate_right(input_rot),
Voucher(voucher.0.rotate_right(voucher_rot)),
)
})
}
pub const REPRESENTATION_BYTE_COUNT: usize = 39;
#[inline(never)]
pub const fn parse_bytes(bytes: &[u8]) -> Result<CheckingParameters, &'static str> {
#[allow(clippy::assertions_on_constants)]
const _: () = assert!(
CheckingParameters::REPRESENTATION_BYTE_COUNT == check::REPRESENTATION_BYTE_COUNT
);
match check::parse_bytes(bytes) {
Err(e) => Err(e),
Ok((unoffset, unscale)) => Ok(CheckingParameters { unoffset, unscale }),
}
}
}
impl std::fmt::Display for CheckingParameters {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "CHECK-{:016x}-{:016x}", self.unoffset, self.unscale)
}
}
impl VouchingParameters {
pub fn generate<Err>(
mut generator: impl FnMut() -> Result<u64, Err>,
) -> Result<VouchingParameters, Err> {
fn gen64<Err>(mut generator: impl FnMut() -> Result<u64, Err>) -> Result<u64, Err> {
loop {
let ret = generator()?;
if ret > 10 && !ret > 10 && ret.count_ones() > 2 && ret.count_zeros() > 2 {
return Ok(ret);
}
}
}
let (offset, scale, (unoffset, unscale)) =
generate::derive_parameters(gen64(&mut generator)?, gen64(&mut generator)?);
Ok(VouchingParameters {
offset,
scale,
checking: CheckingParameters { unoffset, unscale },
})
}
#[inline(always)]
pub const fn parse(string: &str) -> Result<VouchingParameters, &'static str> {
Self::parse_bytes(string.as_bytes())
}
#[inline(never)]
pub const fn parse_or_die(string: &str) -> VouchingParameters {
match Self::parse(string) {
Ok(ret) => ret,
Err(_) => panic!("failed to parse vouching parameter string."),
}
}
#[must_use]
#[inline(always)]
pub const fn vouch(&self, value: u64) -> Voucher {
Voucher(vouch::vouch(
self.offset,
self.scale,
(self.checking.unoffset, self.checking.unscale),
value,
))
}
pub fn vouch_many<'scope>(
&'scope self,
values: impl IntoIterator<Item = u64> + 'scope,
) -> impl Iterator<Item = Voucher> + 'scope {
values.into_iter().enumerate().map(|(idx, value)| {
let input_rot = (idx % 64) as u32;
let voucher_rot = (idx % 63) as u32;
let voucher = self.vouch(value.rotate_right(input_rot));
Voucher(voucher.0.rotate_left(voucher_rot))
})
}
#[must_use]
#[inline(always)]
pub const fn checking_parameters(&self) -> CheckingParameters {
self.checking
}
pub const REPRESENTATION_BYTE_COUNT: usize = 73;
#[inline(never)]
pub const fn parse_bytes(bytes: &[u8]) -> Result<VouchingParameters, &'static str> {
#[allow(clippy::assertions_on_constants)]
const _: () = assert!(
VouchingParameters::REPRESENTATION_BYTE_COUNT == vouch::REPRESENTATION_BYTE_COUNT
);
match vouch::parse_bytes(bytes) {
Err(e) => Err(e),
Ok((offset, scale, (unoffset, unscale))) => {
let expected = generate::derive_parameters(scale ^ vouch::VOUCHING_TAG, unoffset);
if (expected.0 == offset)
& (expected.1 == scale)
& (expected.2 .0 == unoffset)
& (expected.2 .1 == unscale)
{
Ok(VouchingParameters {
offset,
scale,
checking: CheckingParameters { unoffset, unscale },
})
} else {
Err("Invalid VouchingParameters values")
}
}
}
}
}
impl std::fmt::Display for VouchingParameters {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"VOUCH-{:016x}-{:016x}-{:016x}-{:016x}",
self.offset, self.scale, self.checking.unoffset, self.checking.unscale
)
}
}
#[cfg(test)]
fn make_generator(values: &[u64]) -> impl FnMut() -> Result<u64, &'static str> + '_ {
let mut idx = 0;
move || {
if idx < values.len() {
let ret = values[idx];
idx += 1;
Ok(ret)
} else {
Err("ran out of indices")
}
}
}
#[test]
fn test_round_trip() {
let params = VouchingParameters::generate(make_generator(&[131, 131])).expect("must succeed");
let voucher = params.vouch(42);
assert!(params.checking.check(42, voucher));
assert!(!params.checking.check(voucher.0, Voucher(42)));
assert!(!params.checking.check(43, voucher));
assert!(!params.checking.check(41, voucher));
assert!(!params.checking.check(42, Voucher(voucher.0 - 1)));
assert!(!params.checking.check(42, Voucher(voucher.0 + 1)));
assert!(!params.checking.check(41, Voucher(voucher.0 - 1)));
assert!(!params.checking.check(41, Voucher(voucher.0 + 1)));
assert!(!params.checking.check(43, Voucher(voucher.0 - 1)));
assert!(!params.checking.check(43, Voucher(voucher.0 + 1)));
}
#[test]
fn test_round_trip_many() {
let params = VouchingParameters::generate(make_generator(&[131, 131])).expect("must succeed");
let vouchers: Vec<Voucher> = params.vouch_many([42u64, 101u64]).collect();
assert!(params.checking.check_many(&[42, 101], &vouchers));
let arr = [42u64, 101u64];
let vouchers2: Vec<Voucher> = params.vouch_many(arr.iter().copied()).collect();
assert_eq!(vouchers, vouchers2);
let vouchers3: Vec<Voucher> = params
.vouch_many([41u64, 100u64].iter().map(|x| x + 1))
.collect();
assert_eq!(vouchers, vouchers3);
assert!(params.checking.check(42, vouchers[0]));
assert!(!params.checking.check(101, vouchers[1]));
assert!(!params.checking.check_many(&[41, 101], &vouchers));
assert!(!params.checking.check_many(&[42, 100], &vouchers));
assert!(!params.checking.check_many(&[42], &vouchers));
assert!(!params.checking.check_many(&[42, 101, 10], &vouchers));
}
#[test]
fn test_round_trip_many_long() {
let params = VouchingParameters::generate(make_generator(&[131, 131])).expect("must succeed");
let values: Vec<u64> = (0..5000u64).collect();
let vouchers: Vec<Voucher> = params.vouch_many(values.iter().copied()).collect();
assert!(params.checking_parameters().check_many(&values, &vouchers));
}
#[test]
fn test_parse_check() {
let params = VouchingParameters::generate(make_generator(&[131, 131]))
.expect("must succeed")
.checking_parameters();
eprintln!("{}", params);
const SERIAL: &str = "CHECK-0000000000000083-9b791a2755d2d996";
assert_eq!(format!("{}", params), SERIAL);
const COPY: CheckingParameters = CheckingParameters::parse_or_die(SERIAL);
assert_eq!(params, COPY);
assert_eq!(
params,
CheckingParameters::parse(SERIAL).expect("Should succeed")
);
assert_eq!(params, CheckingParameters::parse_or_die(SERIAL));
}
#[test]
#[should_panic(expected = "failed to parse checking parameter string.")]
fn test_parse_check_fail() {
CheckingParameters::parse_or_die("CHECK-0000000000000083-9b791a2755d2d99");
}
#[test]
fn test_generate() {
VouchingParameters::generate(make_generator(&[131, 131])).expect("must succeed");
}
#[test]
fn test_generate_eventually_accept() {
let (offset, scale, (unoffset, unscale)) = generate::derive_parameters(13, 142);
let values = [0u64, 1u64, u64::MAX, 3u64, !17u64, 13, 142];
assert_eq!(
VouchingParameters::generate(make_generator(&values)),
Ok(VouchingParameters {
offset,
scale,
checking: CheckingParameters { unoffset, unscale }
})
);
}
#[test]
fn test_generate_fail() {
let values = [0u64, 1u64, u64::MAX, 3u64, 17, !17u64, 13];
assert_eq!(
VouchingParameters::generate(make_generator(&values)),
Err("ran out of indices")
);
}
#[test]
fn test_generate_fail_early() {
assert_eq!(
VouchingParameters::generate(make_generator(&[13])),
Err("ran out of indices")
);
assert_eq!(
VouchingParameters::generate(make_generator(&[])),
Err("ran out of indices")
);
}
#[test]
fn test_parse_vouch() {
let params = VouchingParameters::generate(make_generator(&[131, 131])).expect("must succeed");
eprintln!("{}", params);
const SERIAL: &str =
"VOUCH-b4b0de979c8a90a9-676e696863756fd5-0000000000000083-9b791a2755d2d996";
assert_eq!(format!("{}", params), SERIAL);
const COPY: VouchingParameters = VouchingParameters::parse_or_die(SERIAL);
assert_eq!(params, COPY);
assert_eq!(params, VouchingParameters::parse(SERIAL).unwrap());
assert_eq!(params, VouchingParameters::parse_or_die(SERIAL));
}
#[test]
#[should_panic(expected = "failed to parse vouching parameter string.")]
fn test_parse_vouch_fail_params() {
let bad_serial = "VOUCH-b4b0de979c8a90a9-676e696863756fd5-0000000000000083-9b791a2755d2d995";
VouchingParameters::parse_or_die(bad_serial);
}
#[test]
#[should_panic(expected = "failed to parse vouching parameter string.")]
fn test_parse_vouch_fail_early() {
let bad_serial = "VOUCH-b4b0de979c8a90a9-676e696863756fd5-0000000000000083-9b791a2755d2d99";
VouchingParameters::parse_or_die(bad_serial);
}