use std::fmt;
use protocol::identity::SEED_BYTES;
use crate::error::{Error, ErrorKind};
use crate::identity::LaunchIdentity;
pub trait Entropy: Send + Sync + fmt::Debug {
fn fill(&self, buf: &mut [u8]) -> Result<(), Error>;
fn seed(&self) -> Result<[u8; SEED_BYTES], Error> {
let mut seed = [0_u8; SEED_BYTES];
self.fill(&mut seed)?;
Ok(seed)
}
}
pub fn launch_identity(entropy: &dyn Entropy) -> Result<LaunchIdentity, Error> {
let vm_seed = entropy.seed()?;
let host_seed = entropy.seed()?;
LaunchIdentity::from_seeds(vm_seed, host_seed)
.map_err(|error| Error::new(ErrorKind::Unexpected, error.to_string()))
}
#[cfg(any(test, feature = "test-support"))]
#[cfg_attr(test, mutants::skip)] pub(crate) mod testing {
use std::sync::atomic::{AtomicU64, Ordering};
use super::*;
#[derive(Debug, Default)]
pub struct SequenceEntropy {
calls: AtomicU64,
unavailable: bool,
}
impl SequenceEntropy {
pub fn new() -> Self {
Self::default()
}
pub fn unavailable() -> Self {
Self {
unavailable: true,
..Self::default()
}
}
pub fn draw(n: u64, len: usize) -> Vec<u8> {
let counter = n.to_le_bytes();
(0..len)
.map(|index| counter[index % counter.len()] ^ (index as u8))
.collect()
}
pub fn calls(&self) -> u64 {
self.calls.load(Ordering::SeqCst)
}
}
impl Entropy for SequenceEntropy {
fn fill(&self, buf: &mut [u8]) -> Result<(), Error> {
let n = self.calls.fetch_add(1, Ordering::SeqCst) + 1;
if self.unavailable {
return Err(Error::new(
ErrorKind::Unexpected,
"the scripted random pool is unavailable",
));
}
buf.copy_from_slice(&Self::draw(n, buf.len()));
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use super::testing::SequenceEntropy;
use super::*;
#[test]
fn the_sequence_source_is_distinct_per_call_and_never_zero() {
let entropy = SequenceEntropy::new();
let seeds: HashSet<[u8; SEED_BYTES]> = (0..300)
.map(|_| entropy.seed().expect("scripted"))
.collect();
assert_eq!(seeds.len(), 300);
assert!(seeds.iter().all(|seed| seed.iter().any(|byte| *byte != 0)));
}
#[test]
fn a_launch_identity_is_the_source_s_next_two_seeds() {
use base64::Engine as _;
let entropy = SequenceEntropy::new();
let identity = launch_identity(&entropy).expect("valid seeds");
let expected =
base64::engine::general_purpose::STANDARD.encode(SequenceEntropy::draw(1, SEED_BYTES));
assert_eq!(identity.seed_field(), expected);
assert_eq!(entropy.calls(), 2);
}
#[test]
fn an_unavailable_source_refuses_the_identity() {
let err = launch_identity(&SequenceEntropy::unavailable()).expect_err("no pool");
assert_eq!(err.kind(), ErrorKind::Unexpected);
}
}