1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
use std::{collections::HashSet, cell::RefCell, borrow::Cow};
use bc_components::{Digest, DigestProvider};
use crate::Envelope;
use super::{walk::EdgeType, envelope::EnvelopeCase};
/// Support for calculating the digests associated with `Envelope`.
impl DigestProvider for Envelope {
/// The envelope's digest.
///
/// This digest can be used to compare two envelopes for semantic equivalence, that
/// is, the two envelopes would contain the same information in their unencrypted
/// and unelided forms. See <doc:Diffing> for more information.
fn digest(&self) -> Cow<'_, Digest> {
match self.case() {
EnvelopeCase::Node { digest, .. } => Cow::Borrowed(digest),
EnvelopeCase::Leaf { digest, .. } => Cow::Borrowed(digest),
EnvelopeCase::Wrapped { digest, .. } => Cow::Borrowed(digest),
EnvelopeCase::Assertion(assertion) => assertion.digest(),
EnvelopeCase::Elided(digest) => Cow::Borrowed(digest),
#[cfg(feature = "known_value")]
EnvelopeCase::KnownValue { digest, .. } => Cow::Borrowed(digest),
#[cfg(feature = "encrypt")]
EnvelopeCase::Encrypted(encrypted_message) => encrypted_message.digest(),
#[cfg(feature = "compress")]
EnvelopeCase::Compressed(compressed) => compressed.digest(),
}
}
}
/// Support for working with the digest tree of an `Envelope`.
impl Envelope {
/// Returns the set of digests contained in the envelope's elements, down to the
/// specified level.
///
/// The digest of the envelope is included as well as the digest of the envelope's
/// subject (if it is different).
///
/// If no `levelLimit` is provided, all digests in the envelope will be returned.
///
/// A `levelLimit` of zero will return no digests.
///
/// # Arguments
///
/// * `levelLimit` - Return digests at levels below this value.
///
/// # Returns
///
/// * A set of digests down to `levelLimit`.
pub fn digests(&self, level_limit: usize) -> HashSet<Digest> {
let result = RefCell::new(HashSet::new());
let visitor = |envelope: Self, level: usize, _: EdgeType, _: Option<&()>| -> _ {
if level < level_limit {
let mut result = result.borrow_mut();
result.insert(envelope.digest().into_owned());
result.insert(envelope.subject().digest().into_owned());
}
None
};
self.walk(false, &visitor);
result.into_inner()
}
/// Returns the set of all digests in the envelope.
pub fn deep_digests(&self) -> HashSet<Digest> {
self.digests(usize::MAX)
}
/// Returns the set of all digests in the envelope, down to its second level.
pub fn shallow_digests(&self) -> HashSet<Digest> {
self.digests(2)
}
/// Produce a value that will necessarily be different if two envelopes differ
/// structurally, even if they are semantically equivalent.
///
/// Comparing the `digest` field of two envelopes (or calling `isEquivalent(to:)`) tests
/// whether two envelopes are *semantically equivalent*. This is accomplished by
/// simply comparing the top level digests of the envelopes for equality, and has a
/// complexity of `O(1)`.
///
/// This means that two envelopes are considered equivalent if they contain
/// identical information in their completely unencrypted and unelided form.
///
/// Some applications need to determine whether two envelopes are not only
/// semantically equivalent, but also structurally identical. Two envelopes that are
/// not semantically equivalent cannot be structurally identical, but two envelopes
/// that *are* semantically equivalent *may or may not* be structurally identical.
///
/// The `structural_digest` attribute is used to produce a value that will
/// necessarily be different if two envelopes differ structurally, even if they are
/// semantically equivalent. It has a complexity of `O(m + n)` where `m` and `n` are
/// the number of elements in each of the two envelopes when they *are* semantically
/// equivalent. It is recommended that envelopes be compared for structural equality
/// by calling `isIdentical(to:)` as this short-circuits to `false` in cases where
/// the compared envelopes are not semantically equivalent.
pub fn structural_digest(&self) -> Digest {
let image = RefCell::new(Vec::new());
let visitor = |envelope: Self, _: usize, _: EdgeType, _: Option<&()>| -> _ {
// Add a discriminator to the image for the obscured cases.
match envelope.case() {
EnvelopeCase::Elided(_) => image.borrow_mut().push(1),
#[cfg(feature = "encrypt")]
EnvelopeCase::Encrypted(_) => image.borrow_mut().push(0),
#[cfg(feature = "compress")]
EnvelopeCase::Compressed(_) => image.borrow_mut().push(2),
_ => {}
}
image.borrow_mut().extend_from_slice(envelope.digest().data());
None
};
self.walk(false, &visitor);
Digest::from_image(image.into_inner())
}
/// Tests two envelopes for semantic equivalence.
///
/// Calling `e1.is_equivalent_to(e2)` has a complexity of `O(1)` and simply compares
/// the two envelope's digests. The means that two envelopes with certain structural
/// differences (e.g., one envelope is partially elided and the other is not) will
/// still test as equivalent.
pub fn is_equivalent_to(&self, other: &Self) -> bool {
self.digest() == other.digest()
}
/// Tests two envelopes for structural equality.
///
/// Calling `e1.is_identical_to(e2)` has a complexity of `O(1)` if the envelopes are
/// not semantically equivalent (that is, their top-level digests are different, and
/// thus they *must* have different structures) and a complexity of `O(m + n)` where
/// `m` and `n` are the number of elements in each of the two envelopes when they
/// *are* semantically equivalent.
pub fn is_identical_to(&self, other: &Self) -> bool {
if !self.is_equivalent_to(other) {
return true;
}
self.structural_digest() == other.structural_digest()
}
}