//! # rust-cascade //! //! A library for creating and querying the cascading bloom filters described by //! Larisch, Choffnes, Levin, Maggs, Mislove, and Wilson in //! "CRLite: A Scalable System for Pushing All TLS Revocations to All Browsers" //! extern crate byteorder; extern crate murmurhash3; extern crate rand; extern crate sha2; use byteorder::{ByteOrder, LittleEndian, ReadBytesExt}; use murmurhash3::murmurhash3_x86_32; #[cfg(feature = "builder")] use rand::rngs::OsRng; #[cfg(feature = "builder")] use rand::RngCore; use sha2::{Digest, Sha256}; use std::convert::{TryFrom, TryInto}; use std::fmt; use std::io::{ErrorKind, Read}; use std::mem::size_of; #[derive(Debug)] pub enum CascadeError { LongSalt, TooManyLayers, Collision, UnknownHashFunction, CapacityViolation(&'static str), Parse(&'static str), } impl fmt::Display for CascadeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { CascadeError::LongSalt => { write!(f, "Cannot serialize a filter with a salt of length >= 256.") } CascadeError::TooManyLayers => { write!(f, "Cannot serialize a filter with >= 255 layers.") } CascadeError::Collision => { write!(f, "Collision between included and excluded sets.") } CascadeError::UnknownHashFunction => { write!(f, "Unknown hash function.") } CascadeError::CapacityViolation(function) => { write!(f, "Unexpected call to {}", function) } CascadeError::Parse(reason) => { write!(f, "Cannot parse cascade: {}", reason) } } } } /// A Bloom filter representing a specific layer in a multi-layer cascading Bloom filter. /// The same hash function is used for all layers, so it is not encoded here. struct Bloom { /// How many hash functions this filter uses n_hash_funcs: u32, /// The bit length of the filter size: u32, /// The data of the filter data: Vec, } #[repr(u8)] #[derive(Copy, Clone, PartialEq)] /// These enumerations need to match the python filter-cascade project: /// pub enum HashAlgorithm { MurmurHash3 = 1, Sha256l32 = 2, // low 32 bits of sha256 Sha256 = 3, // all 256 bits of sha256 } impl fmt::Display for HashAlgorithm { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", *self as u8) } } impl TryFrom for HashAlgorithm { type Error = CascadeError; fn try_from(value: u8) -> Result { match value { // Naturally, these need to match the enum declaration 1 => Ok(Self::MurmurHash3), 2 => Ok(Self::Sha256l32), 3 => Ok(Self::Sha256), _ => Err(CascadeError::UnknownHashFunction), } } } /// A CascadeIndexGenerator provides one-time access to a table of pseudorandom functions H_ij /// in which each function is of the form /// H(s: &[u8], r: u32) -> usize /// and for which 0 <= H(s,r) < r for all s, r. /// The pseudorandom functions share a common key, represented as a octet string, and the table can /// be constructed from this key alone. The functions are pseudorandom with respect to s, but not /// r. For a uniformly random key/table, fixed r, and arbitrary strings m0 and m1, /// H_ij(m0, r) is computationally indistinguishable from H_ij(m1,r) /// for all i,j. /// /// A call to next_layer() increments i and resets j. /// A call to next_index(s, r) increments j, and outputs some value H_ij(s) with 0 <= H_ij(s) < r. #[derive(Debug)] enum CascadeIndexGenerator { MurmurHash3 { key: Vec, counter: u32, depth: u8, }, Sha256l32 { key: Vec, counter: u32, depth: u8, }, Sha256Ctr { key: Vec, counter: u32, state: [u8; 32], state_available: u8, }, } impl PartialEq for CascadeIndexGenerator { fn eq(&self, other: &Self) -> bool { match (self, other) { ( CascadeIndexGenerator::MurmurHash3 { key: ref a, .. }, CascadeIndexGenerator::MurmurHash3 { key: ref b, .. }, ) | ( CascadeIndexGenerator::Sha256l32 { key: ref a, .. }, CascadeIndexGenerator::Sha256l32 { key: ref b, .. }, ) | ( CascadeIndexGenerator::Sha256Ctr { key: ref a, .. }, CascadeIndexGenerator::Sha256Ctr { key: ref b, .. }, ) => a == b, _ => false, } } } impl CascadeIndexGenerator { fn new(hash_alg: HashAlgorithm, key: Vec) -> Self { match hash_alg { HashAlgorithm::MurmurHash3 => Self::MurmurHash3 { key, counter: 0, depth: 1, }, HashAlgorithm::Sha256l32 => Self::Sha256l32 { key, counter: 0, depth: 1, }, HashAlgorithm::Sha256 => Self::Sha256Ctr { key, counter: 0, state: [0; 32], state_available: 0, }, } } fn next_layer(&mut self) { match self { Self::MurmurHash3 { ref mut counter, ref mut depth, .. } | Self::Sha256l32 { ref mut counter, ref mut depth, .. } => { *counter = 0; *depth += 1; } Self::Sha256Ctr { .. } => (), } } fn next_index(&mut self, salt: &[u8], range: u32) -> usize { let index = match self { Self::MurmurHash3 { key, ref mut counter, depth, } => { let hash_seed = (*counter << 16) + *depth as u32; *counter += 1; murmurhash3_x86_32(key, hash_seed) } Self::Sha256l32 { key, ref mut counter, depth, } => { let mut hasher = Sha256::new(); hasher.update(salt); hasher.update(counter.to_le_bytes()); hasher.update(depth.to_le_bytes()); hasher.update(&key); *counter += 1; u32::from_le_bytes( hasher.finalize()[0..4] .try_into() .expect("sha256 should have given enough bytes"), ) } Self::Sha256Ctr { key, ref mut counter, ref mut state, ref mut state_available, } => { // |bytes_needed| is the minimum number of bytes needed to represent a value in [0, range). let bytes_needed = ((range.next_power_of_two().trailing_zeros() + 7) / 8) as usize; let mut index_arr = [0u8; 4]; for byte in index_arr.iter_mut().take(bytes_needed) { if *state_available == 0 { let mut hasher = Sha256::new(); hasher.update(counter.to_le_bytes()); hasher.update(salt); hasher.update(&key); hasher.finalize_into(state.into()); *state_available = state.len() as u8; *counter += 1; } *byte = state[state.len() - *state_available as usize]; *state_available -= 1; } LittleEndian::read_u32(&index_arr) } }; (index % range) as usize } } impl Bloom { /// `new_crlite_bloom` creates an empty bloom filter for a layer of a cascade with the /// parameters specified in [LCL+17, Section III.C]. /// /// # Arguments /// * `include_capacity` - the number of elements that will be encoded at the new layer. /// * `exclude_capacity` - the number of elements in the complement of the encoded set. /// * `top_layer` - whether this is the top layer of the filter. #[cfg(feature = "builder")] pub fn new_crlite_bloom( include_capacity: usize, exclude_capacity: usize, top_layer: bool, ) -> Self { assert!(include_capacity != 0 && exclude_capacity != 0); let r = include_capacity as f64; let s = exclude_capacity as f64; // The desired false positive rate for the top layer is // p = r/(sqrt(2)*s). // With this setting, the number of false positives (which will need to be // encoded at the second layer) is expected to be a factor of sqrt(2) // smaller than the number of elements encoded at the top layer. // // At layer i > 1 we try to ensure that the number of elements to be // encoded at layer i+1 is half the number of elements encoded at // layer i. So we take p = 1/2. let log2_fp_rate = match top_layer { true => (r / s).log2() - 0.5f64, false => -1f64, }; // the number of hash functions (k) and the size of the bloom filter (m) are given in // [LCL+17] as k = log2(1/p) and m = r log2(1/p) / ln(2). // // If this formula gives a value of m < 256, we take m=256 instead. This results in very // slightly sub-optimal size, but gives us the added benefit of doing less hashing. let n_hash_funcs = (-log2_fp_rate).round() as u32; let size = match (r * (-log2_fp_rate) / (f64::ln(2f64))).round() as u32 { size if size >= 256 => size, _ => 256, }; Bloom { n_hash_funcs, size, data: vec![0u8; ((size + 7) / 8) as usize], } } /// `read` attempts to decode the Bloom filter represented by the bytes in the given reader. /// /// # Arguments /// * `reader` - The encoded representation of this Bloom filter. May be empty. May include /// additional data describing further Bloom filters. /// The format of an encoded Bloom filter is: /// [1 byte] - the hash algorithm to use in the filter /// [4 little endian bytes] - the length in bits of the filter /// [4 little endian bytes] - the number of hash functions to use in the filter /// [1 byte] - which layer in the cascade this filter is /// [variable length bytes] - the filter itself (must be of minimal length) pub fn read( reader: &mut R, ) -> Result, CascadeError> { let hash_algorithm_val = match reader.read_u8() { Ok(val) => val, // If reader is at EOF, there is no bloom filter. Err(e) if e.kind() == ErrorKind::UnexpectedEof => return Ok(None), Err(_) => return Err(CascadeError::Parse("read error")), }; let hash_algorithm = HashAlgorithm::try_from(hash_algorithm_val)?; let size = reader .read_u32::() .or(Err(CascadeError::Parse("truncated at layer size")))?; let n_hash_funcs = reader .read_u32::() .or(Err(CascadeError::Parse("truncated at layer hash count")))?; let layer = reader .read_u8() .or(Err(CascadeError::Parse("truncated at layer number")))?; let byte_count = ((size + 7) / 8) as usize; let mut data = vec![0; byte_count]; reader .read_exact(&mut data) .or(Err(CascadeError::Parse("truncated at layer data")))?; let bloom = Bloom { n_hash_funcs, size, data, }; Ok(Some((bloom, layer as usize, hash_algorithm))) } fn has(&self, generator: &mut CascadeIndexGenerator, salt: &[u8]) -> bool { for _ in 0..self.n_hash_funcs { let bit_index = generator.next_index(salt, self.size); assert!(bit_index < self.size as usize); let byte_index = bit_index / 8; let mask = 1 << (bit_index % 8); if self.data[byte_index] & mask == 0 { return false; } } true } #[cfg(feature = "builder")] fn insert(&mut self, generator: &mut CascadeIndexGenerator, salt: &[u8]) { for _ in 0..self.n_hash_funcs { let bit_index = generator.next_index(salt, self.size); let byte_index = bit_index / 8; let mask = 1 << (bit_index % 8); self.data[byte_index] |= mask; } } pub fn approximate_size_of(&self) -> usize { size_of::() + self.data.len() } } impl fmt::Display for Bloom { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "n_hash_funcs={} size={}", self.n_hash_funcs, self.size) } } /// A multi-layer cascading Bloom filter. pub struct Cascade { /// The Bloom filter for this layer in the cascade filters: Vec, /// The salt in use, if any salt: Vec, /// The hash algorithm / index generating function to use hash_algorithm: HashAlgorithm, /// Whether the logic should be inverted inverted: bool, } impl Cascade { /// from_bytes attempts to decode and return a multi-layer cascading Bloom filter. /// /// # Arguments /// `bytes` - The encoded representation of the Bloom filters in this cascade. Starts with 2 /// little endian bytes indicating the version. The current version is 2. The Python /// filter-cascade project defines the formats, see /// /// /// May be of length 0, in which case `None` is returned. pub fn from_bytes(bytes: Vec) -> Result, CascadeError> { if bytes.is_empty() { return Ok(None); } let mut reader = bytes.as_slice(); let version = reader .read_u16::() .or(Err(CascadeError::Parse("truncated at version")))?; let mut filters = vec![]; let mut salt = vec![]; let mut top_hash_alg = None; let mut inverted = false; if version > 2 { return Err(CascadeError::Parse("unknown version")); } if version == 2 { let inverted_val = reader .read_u8() .or(Err(CascadeError::Parse("truncated at inverted")))?; if inverted_val > 1 { return Err(CascadeError::Parse("invalid value for inverted")); } inverted = 0 != inverted_val; let salt_len: usize = reader .read_u8() .or(Err(CascadeError::Parse("truncated at salt length")))? .into(); if salt_len >= 256 { return Err(CascadeError::Parse("salt too long")); } if salt_len > 0 { let mut salt_bytes = vec![0; salt_len]; reader .read_exact(&mut salt_bytes) .or(Err(CascadeError::Parse("truncated at salt")))?; salt = salt_bytes; } } while let Some((filter, layer_number, layer_hash_alg)) = Bloom::read(&mut reader)? { filters.push(filter); if layer_number != filters.len() { return Err(CascadeError::Parse("irregular layer numbering")); } if *top_hash_alg.get_or_insert(layer_hash_alg) != layer_hash_alg { return Err(CascadeError::Parse("Inconsistent hash algorithms")); } } if filters.is_empty() { return Err(CascadeError::Parse("missing filters")); } let hash_algorithm = top_hash_alg.ok_or(CascadeError::Parse("missing hash algorithm"))?; Ok(Some(Cascade { filters, salt, hash_algorithm, inverted, })) } /// to_bytes encodes a cascade in the version 2 format. pub fn to_bytes(&self) -> Result, CascadeError> { if self.salt.len() >= 256 { return Err(CascadeError::LongSalt); } if self.filters.len() >= 255 { return Err(CascadeError::TooManyLayers); } let mut out = vec![]; let version: u16 = 2; let inverted: u8 = self.inverted.into(); let salt_len: u8 = self.salt.len() as u8; let hash_alg: u8 = self.hash_algorithm as u8; out.extend_from_slice(&version.to_le_bytes()); out.push(inverted); out.push(salt_len); out.extend_from_slice(&self.salt); for (layer, bloom) in self.filters.iter().enumerate() { out.push(hash_alg); out.extend_from_slice(&bloom.size.to_le_bytes()); out.extend_from_slice(&bloom.n_hash_funcs.to_le_bytes()); out.push((1 + layer) as u8); // 1-indexed out.extend_from_slice(&bloom.data); } Ok(out) } /// has determines if the given sequence of bytes is in the cascade. /// /// # Arguments /// `entry` - The bytes to query pub fn has(&self, entry: Vec) -> bool { // Query filters 0..self.filters.len() until we get a non-membership result. // If this occurs at an even index filter, the element *is not* included. // ... at an odd-index filter, the element *is* included. let mut generator = CascadeIndexGenerator::new(self.hash_algorithm, entry); let mut rv = false; for filter in &self.filters { if filter.has(&mut generator, &self.salt) { rv = !rv; generator.next_layer(); } else { break; } } if self.inverted { rv = !rv; } rv } pub fn invert(&mut self) { self.inverted = !self.inverted; } /// Determine the approximate amount of memory in bytes used by this /// Cascade. Because this implementation does not integrate with the /// allocator, it can't get an accurate measurement of how much memory it /// uses. However, it can make a reasonable guess, assuming the sizes of /// the bloom filters are large enough to dominate the overall allocated /// size. pub fn approximate_size_of(&self) -> usize { size_of::() + self .filters .iter() .map(|x| x.approximate_size_of()) .sum::() + self.salt.len() } } impl fmt::Display for Cascade { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!( f, "salt={:?} inverted={} hash_algorithm={}", self.salt, self.inverted, self.hash_algorithm, )?; for filter in &self.filters { writeln!(f, "\t[{}]", filter)?; } Ok(()) } } /// A CascadeBuilder creates a Cascade with layers given by `Bloom::new_crlite_bloom`. /// /// A builder is initialized using [`CascadeBuilder::default`] or [`CascadeBuilder::new`]. Prefer `default`. The `new` constructor /// allows the user to specify sensitive internal details such as the hash function and the domain /// separation parameter. /// /// Both constructors take `include_capacity` and an `exclude_capacity` parameters. The /// `include_capacity` is the number of elements that will be encoded in the Cascade. The /// `exclude_capacity` is size of the complement of the encoded set. /// /// The encoded set is specified through calls to [`CascadeBuilder::include`]. Its complement is specified through /// calls to [`CascadeBuilder::exclude`]. The cascade is built with a call to [`CascadeBuilder::finalize`]. /// /// The builder will track of the number of calls to `include` and `exclude`. /// The caller is responsible for making *exactly* `include_capacity` calls to `include` /// followed by *exactly* `exclude_capacity` calls to `exclude`. /// Calling `exclude` before all `include` calls have been made will result in a panic!(). /// Calling `finalize` before all `exclude` calls have been made will result in a panic!(). /// #[cfg(feature = "builder")] pub struct CascadeBuilder { filters: Vec, salt: Vec, hash_algorithm: HashAlgorithm, to_include: Vec, to_exclude: Vec, status: BuildStatus, } #[cfg(feature = "builder")] impl CascadeBuilder { pub fn default(include_capacity: usize, exclude_capacity: usize) -> Self { let mut salt = vec![0u8; 16]; OsRng.fill_bytes(&mut salt); CascadeBuilder::new( HashAlgorithm::Sha256, salt, include_capacity, exclude_capacity, ) } pub fn new( hash_algorithm: HashAlgorithm, salt: Vec, include_capacity: usize, exclude_capacity: usize, ) -> Self { CascadeBuilder { filters: vec![Bloom::new_crlite_bloom( include_capacity, exclude_capacity, true, )], salt, to_include: vec![], to_exclude: vec![], hash_algorithm, status: BuildStatus(include_capacity, exclude_capacity), } } pub fn include(&mut self, item: Vec) -> Result<(), CascadeError> { match self.status { BuildStatus(ref mut cap, _) if *cap > 0 => *cap -= 1, _ => return Err(CascadeError::CapacityViolation("include")), } let mut generator = CascadeIndexGenerator::new(self.hash_algorithm, item); self.filters[0].insert(&mut generator, &self.salt); self.to_include.push(generator); Ok(()) } pub fn exclude(&mut self, item: Vec) -> Result<(), CascadeError> { match self.status { BuildStatus(0, ref mut cap) if *cap > 0 => *cap -= 1, _ => return Err(CascadeError::CapacityViolation("exclude")), } let mut generator = CascadeIndexGenerator::new(self.hash_algorithm, item); if self.filters[0].has(&mut generator, &self.salt) { self.to_exclude.push(generator); } Ok(()) } /// `exclude_threaded` is like `exclude` but it stores false positives in a caller-owned /// `ExcludeSet`. This allows the caller to exclude items in parallel. pub fn exclude_threaded(&self, exclude_set: &mut ExcludeSet, item: Vec) { exclude_set.size += 1; let mut generator = CascadeIndexGenerator::new(self.hash_algorithm, item); if self.filters[0].has(&mut generator, &self.salt) { exclude_set.set.push(generator); } } /// `collect_exclude_set` merges an `ExcludeSet` into the internal storage of the CascadeBuilder. pub fn collect_exclude_set( &mut self, exclude_set: &mut ExcludeSet, ) -> Result<(), CascadeError> { match self.status { BuildStatus(0, ref mut cap) if *cap >= exclude_set.size => *cap -= exclude_set.size, _ => return Err(CascadeError::CapacityViolation("exclude")), } self.to_exclude.append(&mut exclude_set.set); Ok(()) } fn push_layer(&mut self) -> Result<(), CascadeError> { // At even layers we encode elements of to_include. At odd layers we encode elements of // to_exclude. In both cases, we track false positives by filtering the complement of the // encoded set through the newly produced bloom filter. let at_even_layer = self.filters.len() % 2 == 0; let (to_encode, to_filter) = match at_even_layer { true => (&mut self.to_include, &mut self.to_exclude), false => (&mut self.to_exclude, &mut self.to_include), }; // split ownership of `salt` away from `to_encode` and `to_filter` // We need an immutable reference to salt during `to_encode.iter_mut()` let mut bloom = Bloom::new_crlite_bloom(to_encode.len(), to_filter.len(), false); let salt = self.salt.as_slice(); to_encode.iter_mut().for_each(|x| { x.next_layer(); bloom.insert(x, salt) }); let mut delta = to_filter.len(); to_filter.retain_mut(|x| { x.next_layer(); bloom.has(x, salt) }); delta -= to_filter.len(); if delta == 0 { // Check for collisions between the |to_encode| and |to_filter| sets. // The implementation of PartialEq for CascadeIndexGenerator will successfully // identify cases where the user called |include(item)| and |exclude(item)| for the // same item. It will not identify collisions in the underlying hash function. for x in to_encode.iter_mut() { if to_filter.contains(x) { return Err(CascadeError::Collision); } } } self.filters.push(bloom); Ok(()) } pub fn finalize(mut self) -> Result, CascadeError> { match self.status { BuildStatus(0, 0) => (), _ => return Err(CascadeError::CapacityViolation("finalize")), } loop { if self.to_exclude.is_empty() { break; } self.push_layer()?; if self.to_include.is_empty() { break; } self.push_layer()?; } Ok(Box::new(Cascade { filters: self.filters, salt: self.salt, hash_algorithm: self.hash_algorithm, inverted: false, })) } } /// BuildStatus is used to ensure that the `include`, `exclude`, and `finalize` calls to /// CascadeBuilder are made in the right order. The (a,b) state indicates that the /// CascadeBuilder is waiting for `a` calls to `include` and `b` calls to `exclude`. #[cfg(feature = "builder")] struct BuildStatus(usize, usize); /// CascadeBuilder::exclude takes `&mut self` so that it can count exclusions and push items to /// self.to_exclude. The bulk of the work it does, however, can be done with an immutable reference /// to the top level bloom filter. An `ExcludeSet` is used by `CascadeBuilder::exclude_threaded` to /// track the changes to a `CascadeBuilder` that would be made with a call to /// `CascadeBuilder::exclude`. #[cfg(feature = "builder")] #[derive(Default)] pub struct ExcludeSet { size: usize, set: Vec, } #[cfg(test)] mod tests { use Bloom; use Cascade; #[cfg(feature = "builder")] use CascadeBuilder; #[cfg(feature = "builder")] use CascadeError; use CascadeIndexGenerator; #[cfg(feature = "builder")] use ExcludeSet; use HashAlgorithm; #[test] fn bloom_v1_test_from_bytes() { let src: Vec = vec![ 0x01, 0x09, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x41, 0x00, ]; let mut reader = src.as_slice(); match Bloom::read(&mut reader) { Ok(Some((bloom, 1, HashAlgorithm::MurmurHash3))) => { assert!(bloom.has( &mut CascadeIndexGenerator::new(HashAlgorithm::MurmurHash3, b"this".to_vec()), &vec![] )); assert!(bloom.has( &mut CascadeIndexGenerator::new(HashAlgorithm::MurmurHash3, b"that".to_vec()), &vec![] )); assert!(!bloom.has( &mut CascadeIndexGenerator::new(HashAlgorithm::MurmurHash3, b"other".to_vec()), &vec![] )); } Ok(_) => panic!("Parsing failed"), Err(_) => panic!("Parsing failed"), }; assert!(reader.is_empty()); let short: Vec = vec![ 0x01, 0x09, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x41, ]; assert!(Bloom::read(&mut short.as_slice()).is_err()); let empty: Vec = Vec::new(); let mut reader = empty.as_slice(); match Bloom::read(&mut reader) { Ok(should_be_none) => assert!(should_be_none.is_none()), Err(_) => panic!("Parsing failed"), }; } #[test] fn bloom_v3_unsupported() { let src: Vec = vec![0x03, 0x01, 0x00]; assert!(Bloom::read(&mut src.as_slice()).is_err()); } #[test] fn cascade_v1_murmur_from_file_bytes_test() { let v = include_bytes!("../test_data/test_v1_murmur_mlbf").to_vec(); let cascade = Cascade::from_bytes(v) .expect("parsing Cascade should succeed") .expect("Cascade should be Some"); // Key format is SHA256(issuer SPKI) + serial number let key_for_revoked_cert_1 = vec![ 0x2e, 0xb2, 0xd5, 0xa8, 0x60, 0xfe, 0x50, 0xe9, 0xc2, 0x42, 0x36, 0x85, 0x52, 0x98, 0x01, 0x50, 0xe4, 0x5d, 0xb5, 0x32, 0x1a, 0x5b, 0x00, 0x5e, 0x26, 0xd6, 0x76, 0x25, 0x3a, 0x40, 0x9b, 0xf5, 0x06, 0x2d, 0xf5, 0x68, 0xa0, 0x51, 0x31, 0x08, 0x20, 0xd7, 0xec, 0x43, 0x27, 0xe1, 0xba, 0xfd, ]; assert!(cascade.has(key_for_revoked_cert_1)); let key_for_revoked_cert_2 = vec![ 0xf1, 0x1c, 0x3d, 0xd0, 0x48, 0xf7, 0x4e, 0xdb, 0x7c, 0x45, 0x19, 0x2b, 0x83, 0xe5, 0x98, 0x0d, 0x2f, 0x67, 0xec, 0x84, 0xb4, 0xdd, 0xb9, 0x39, 0x6e, 0x33, 0xff, 0x51, 0x73, 0xed, 0x69, 0x8f, 0x00, 0xd2, 0xe8, 0xf6, 0xaa, 0x80, 0x48, 0x1c, 0xd4, ]; assert!(cascade.has(key_for_revoked_cert_2)); let key_for_valid_cert = vec![ 0x99, 0xfc, 0x9d, 0x40, 0xf1, 0xad, 0xb1, 0x63, 0x65, 0x61, 0xa6, 0x1d, 0x68, 0x3d, 0x9e, 0xa6, 0xb4, 0x60, 0xc5, 0x7d, 0x0c, 0x75, 0xea, 0x00, 0xc3, 0x41, 0xb9, 0xdf, 0xb9, 0x0b, 0x5f, 0x39, 0x0b, 0x77, 0x75, 0xf7, 0xaf, 0x9a, 0xe5, 0x42, 0x65, 0xc9, 0xcd, 0x32, 0x57, 0x10, 0x77, 0x8e, ]; assert!(!cascade.has(key_for_valid_cert)); assert_eq!(cascade.approximate_size_of(), 15408); let v = include_bytes!("../test_data/test_v1_murmur_short_mlbf").to_vec(); assert!(Cascade::from_bytes(v).is_err()); } #[test] fn cascade_v2_sha256l32_from_file_bytes_test() { let v = include_bytes!("../test_data/test_v2_sha256l32_mlbf").to_vec(); let cascade = Cascade::from_bytes(v) .expect("parsing Cascade should succeed") .expect("Cascade should be Some"); assert!(cascade.salt.len() == 0); assert!(cascade.inverted == false); assert!(cascade.has(b"this".to_vec()) == true); assert!(cascade.has(b"that".to_vec()) == true); assert!(cascade.has(b"other".to_vec()) == false); assert_eq!(cascade.approximate_size_of(), 1001); } #[test] fn cascade_v2_sha256l32_with_salt_from_file_bytes_test() { let v = include_bytes!("../test_data/test_v2_sha256l32_salt_mlbf").to_vec(); let cascade = Cascade::from_bytes(v) .expect("parsing Cascade should succeed") .expect("Cascade should be Some"); assert!(cascade.salt == b"nacl".to_vec()); assert!(cascade.inverted == false); assert!(cascade.has(b"this".to_vec()) == true); assert!(cascade.has(b"that".to_vec()) == true); assert!(cascade.has(b"other".to_vec()) == false); assert_eq!(cascade.approximate_size_of(), 1001); } #[test] fn cascade_v2_murmur_from_file_bytes_test() { let v = include_bytes!("../test_data/test_v2_murmur_mlbf").to_vec(); let cascade = Cascade::from_bytes(v) .expect("parsing Cascade should succeed") .expect("Cascade should be Some"); assert!(cascade.salt.len() == 0); assert!(cascade.inverted == false); assert!(cascade.has(b"this".to_vec()) == true); assert!(cascade.has(b"that".to_vec()) == true); assert!(cascade.has(b"other".to_vec()) == false); assert_eq!(cascade.approximate_size_of(), 992); } #[test] fn cascade_v2_murmur_inverted_from_file_bytes_test() { let v = include_bytes!("../test_data/test_v2_murmur_inverted_mlbf").to_vec(); let cascade = Cascade::from_bytes(v) .expect("parsing Cascade should succeed") .expect("Cascade should be Some"); assert!(cascade.salt.len() == 0); assert!(cascade.inverted == true); assert!(cascade.has(b"this".to_vec()) == true); assert!(cascade.has(b"that".to_vec()) == true); assert!(cascade.has(b"other".to_vec()) == false); assert_eq!(cascade.approximate_size_of(), 1058); } #[test] fn cascade_v2_sha256l32_inverted_from_file_bytes_test() { let v = include_bytes!("../test_data/test_v2_sha256l32_inverted_mlbf").to_vec(); let cascade = Cascade::from_bytes(v) .expect("parsing Cascade should succeed") .expect("Cascade should be Some"); assert!(cascade.salt.len() == 0); assert!(cascade.inverted == true); assert!(cascade.has(b"this".to_vec()) == true); assert!(cascade.has(b"that".to_vec()) == true); assert!(cascade.has(b"other".to_vec()) == false); assert_eq!(cascade.approximate_size_of(), 1061); } #[test] fn cascade_v2_sha256ctr_from_file_bytes_test() { let v = include_bytes!("../test_data/test_v2_sha256ctr_salt_mlbf").to_vec(); let cascade = Cascade::from_bytes(v) .expect("parsing Cascade should succeed") .expect("Cascade should be Some"); assert!(cascade.salt == b"nacl".to_vec()); assert!(cascade.inverted == false); assert!(cascade.has(b"this".to_vec()) == true); assert!(cascade.has(b"that".to_vec()) == true); assert!(cascade.has(b"other".to_vec()) == false); assert_eq!(cascade.approximate_size_of(), 1070); } #[test] fn cascade_empty() { let cascade = Cascade::from_bytes(Vec::new()).expect("parsing Cascade should succeed"); assert!(cascade.is_none()); } #[test] fn cascade_test_from_bytes() { let unknown_version: Vec = vec![0xff, 0xff, 0x00, 0x00]; match Cascade::from_bytes(unknown_version) { Ok(_) => panic!("Cascade::from_bytes allows unknown version."), Err(_) => (), } let first_layer_is_zero: Vec = vec![ 0x01, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, ]; match Cascade::from_bytes(first_layer_is_zero) { Ok(_) => panic!("Cascade::from_bytes allows zero indexed layers."), Err(_) => (), } let second_layer_is_three: Vec = vec![ 0x01, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, ]; match Cascade::from_bytes(second_layer_is_three) { Ok(_) => panic!("Cascade::from_bytes allows non-sequential layers."), Err(_) => (), } } #[test] #[cfg(feature = "builder")] fn cascade_builder_test_collision() { let mut builder = CascadeBuilder::default(1, 1); builder.include(b"collision!".to_vec()).ok(); builder.exclude(b"collision!".to_vec()).ok(); assert!(matches!(builder.finalize(), Err(CascadeError::Collision))); } #[test] #[cfg(feature = "builder")] fn cascade_builder_test_exclude_too_few() { let mut builder = CascadeBuilder::default(1, 1); builder.include(b"1".to_vec()).ok(); assert!(matches!( builder.finalize(), Err(CascadeError::CapacityViolation(_)) )); } #[test] #[cfg(feature = "builder")] fn cascade_builder_test_include_too_few() { let mut builder = CascadeBuilder::default(1, 1); assert!(matches!( builder.exclude(b"1".to_vec()), Err(CascadeError::CapacityViolation(_)) )); } #[test] #[cfg(feature = "builder")] fn cascade_builder_test_include_too_many() { let mut builder = CascadeBuilder::default(1, 1); builder.include(b"1".to_vec()).ok(); assert!(matches!( builder.include(b"2".to_vec()), Err(CascadeError::CapacityViolation(_)) )); } #[test] #[cfg(feature = "builder")] fn cascade_builder_test_exclude_too_many() { let mut builder = CascadeBuilder::default(1, 1); builder.include(b"1".to_vec()).ok(); builder.exclude(b"2".to_vec()).ok(); assert!(matches!( builder.exclude(b"3".to_vec()), Err(CascadeError::CapacityViolation(_)) )); } #[test] #[cfg(feature = "builder")] fn cascade_builder_test_exclude_threaded_no_collect() { let mut builder = CascadeBuilder::default(1, 3); let mut exclude_set = ExcludeSet::default(); builder.include(b"1".to_vec()).ok(); builder.exclude_threaded(&mut exclude_set, b"2".to_vec()); builder.exclude_threaded(&mut exclude_set, b"3".to_vec()); builder.exclude_threaded(&mut exclude_set, b"4".to_vec()); assert!(matches!( builder.finalize(), Err(CascadeError::CapacityViolation(_)) )); } #[test] #[cfg(feature = "builder")] fn cascade_builder_test_exclude_threaded_too_many() { let mut builder = CascadeBuilder::default(1, 3); let mut exclude_set = ExcludeSet::default(); builder.include(b"1".to_vec()).ok(); builder.exclude_threaded(&mut exclude_set, b"2".to_vec()); builder.exclude_threaded(&mut exclude_set, b"3".to_vec()); builder.exclude_threaded(&mut exclude_set, b"4".to_vec()); builder.exclude_threaded(&mut exclude_set, b"5".to_vec()); assert!(matches!( builder.collect_exclude_set(&mut exclude_set), Err(CascadeError::CapacityViolation(_)) )); } #[test] #[cfg(feature = "builder")] fn cascade_builder_test_exclude_threaded() { let mut builder = CascadeBuilder::default(1, 3); let mut exclude_set = ExcludeSet::default(); builder.include(b"1".to_vec()).ok(); builder.exclude_threaded(&mut exclude_set, b"2".to_vec()); builder.exclude_threaded(&mut exclude_set, b"3".to_vec()); builder.exclude_threaded(&mut exclude_set, b"4".to_vec()); builder.collect_exclude_set(&mut exclude_set).ok(); builder.finalize().ok(); } #[cfg(feature = "builder")] fn cascade_builder_test_generate(hash_alg: HashAlgorithm, inverted: bool) { let total = 10_000_usize; let included = 100_usize; let salt = vec![0u8; 16]; let mut builder = CascadeBuilder::new(hash_alg, salt, included, (total - included) as usize); for i in 0..included { builder.include(i.to_le_bytes().to_vec()).ok(); } for i in included..total { builder.exclude(i.to_le_bytes().to_vec()).ok(); } let mut cascade = builder.finalize().unwrap(); if inverted { cascade.invert() } // Ensure we can serialize / deserialize let cascade_bytes = cascade.to_bytes().expect("failed to serialize cascade"); let cascade = Cascade::from_bytes(cascade_bytes) .expect("failed to deserialize cascade") .expect("cascade should not be None here"); // Ensure each query gives the correct result for i in 0..included { assert!(cascade.has(i.to_le_bytes().to_vec()) == true ^ inverted) } for i in included..total { assert!(cascade.has(i.to_le_bytes().to_vec()) == false ^ inverted) } } #[test] #[cfg(feature = "builder")] fn cascade_builder_test_generate_murmurhash3_inverted() { cascade_builder_test_generate(HashAlgorithm::MurmurHash3, true); } #[test] #[cfg(feature = "builder")] fn cascade_builder_test_generate_murmurhash3() { cascade_builder_test_generate(HashAlgorithm::MurmurHash3, false); } #[test] #[cfg(feature = "builder")] fn cascade_builder_test_generate_sha256l32() { cascade_builder_test_generate(HashAlgorithm::Sha256l32, false); } #[test] #[cfg(feature = "builder")] fn cascade_builder_test_generate_sha256() { cascade_builder_test_generate(HashAlgorithm::Sha256, false); } }