use bamboo_rs_core_ed25519_yasmf::decode;
use crate::entry::error::DecodeEntryError;
use crate::entry::traits::{AsEncodedEntry, AsEntry};
use crate::entry::validate::{validate_links, validate_signature};
use crate::entry::{EncodedEntry, Entry};
pub fn decode_entry(entry_encoded: &EncodedEntry) -> Result<Entry, DecodeEntryError> {
let bytes = entry_encoded.into_bytes();
let bamboo_entry = decode(&bytes)?;
let entry: Entry = bamboo_entry.into();
validate_links(&entry)?;
validate_signature(entry.public_key(), entry.signature(), entry_encoded)?;
Ok(entry)
}
#[cfg(test)]
mod tests {
use std::convert::TryInto;
use rstest::rstest;
use rstest_reuse::apply;
use crate::entry::encode::encode_entry;
use crate::entry::traits::{AsEncodedEntry, AsEntry};
use crate::entry::{EncodedEntry, Entry};
use crate::identity::KeyPair;
use crate::operation::encode::encode_operation;
use crate::operation::EncodedOperation;
use crate::test_utils::constants::{HASH, PRIVATE_KEY};
use crate::test_utils::fixtures::{
create_operation_with_schema, encoded_entry, encoded_operation, entry,
entry_signed_encoded_unvalidated, key_pair, random_hash, Fixture,
};
use crate::test_utils::templates::version_fixtures;
use super::decode_entry;
#[rstest]
fn test_entry_signed(entry: Entry) {
let encoded_entry = encode_entry(&entry).unwrap();
let verification = KeyPair::verify(
entry.public_key(),
&encoded_entry.unsigned_bytes(),
&entry.signature().into(),
);
assert!(verification.is_ok(), "{:?}", verification.unwrap_err())
}
#[rstest]
fn test_size(encoded_entry: EncodedEntry) {
let size: usize = encoded_entry.size().try_into().unwrap();
assert_eq!(size, encoded_entry.into_bytes().len())
}
#[rstest]
fn test_payload_hash(entry: Entry, encoded_operation: EncodedOperation) {
let expected_payload_hash = encoded_operation.hash();
assert_eq!(entry.payload_hash(), &expected_payload_hash)
}
#[rstest]
#[case::valid_first_entry(entry_signed_encoded_unvalidated(
1,
1,
None,
None,
Some(encode_operation(&create_operation_with_schema()).unwrap()),
key_pair(PRIVATE_KEY)
))]
#[case::valid_entry_with_backlink(entry_signed_encoded_unvalidated(
2,
1,
Some(random_hash()),
None,
Some(encode_operation(&create_operation_with_schema()).unwrap()),
key_pair(PRIVATE_KEY)
))]
#[case::valid_entry_with_skiplink_and_backlink(entry_signed_encoded_unvalidated(
13,
1,
Some(random_hash()),
Some(random_hash()),
Some(encode_operation(&create_operation_with_schema()).unwrap()),
key_pair(PRIVATE_KEY)
))]
#[case::skiplink_ommitted_when_sam_as_backlink(entry_signed_encoded_unvalidated(
14,
1,
Some(random_hash()),
None,
Some(encode_operation(&create_operation_with_schema()).unwrap()),
key_pair(PRIVATE_KEY)
))]
fn decode_correct_entries(#[case] entry_encoded_unvalidated: EncodedEntry) {
assert!(decode_entry(&entry_encoded_unvalidated).is_ok());
}
#[rstest]
#[case::empty_string(EncodedEntry::from_hex(""), "Bytes to decode had length of 0")]
#[case::seq_number_zero(
entry_signed_encoded_unvalidated(
0,
1,
None,
None,
Some(encode_operation(&create_operation_with_schema()).unwrap()),
key_pair(PRIVATE_KEY)
),
"Entry sequence must be larger than 0 but was 0"
)]
#[case::should_not_have_skiplink(
entry_signed_encoded_unvalidated(
1,
1,
None,
Some(random_hash()),
Some(encode_operation(&create_operation_with_schema()).unwrap()),
key_pair(PRIVATE_KEY)
),
"Could not decode payload hash DecodeError"
)]
#[case::should_not_have_backlink(
entry_signed_encoded_unvalidated(
1,
1,
Some(random_hash()),
None,
Some(encode_operation(&create_operation_with_schema()).unwrap()),
key_pair(PRIVATE_KEY)
),
"Could not decode payload hash DecodeError"
)]
#[case::should_not_have_backlink_or_skiplink(
entry_signed_encoded_unvalidated(
1,
1,
Some(HASH.parse().unwrap()),
Some(HASH.parse().unwrap()),
Some(encode_operation(&create_operation_with_schema()).unwrap()),
key_pair(PRIVATE_KEY)
),
"Could not decode payload hash DecodeError"
)]
#[case::missing_backlink(
entry_signed_encoded_unvalidated(
2,
1,
None,
None,
Some(encode_operation(&create_operation_with_schema()).unwrap()),
key_pair(PRIVATE_KEY)
),
"Could not decode backlink yamf hash: DecodeError"
)]
#[case::missing_skiplink(
entry_signed_encoded_unvalidated(
8,
1,
Some(random_hash()),
None,
Some(encode_operation(&create_operation_with_schema()).unwrap()),
key_pair(PRIVATE_KEY)
),
"Could not decode backlink yamf hash: DecodeError"
)]
#[case::should_not_include_skiplink(
entry_signed_encoded_unvalidated(
14,
1,
Some(HASH.parse().unwrap()),
Some(HASH.parse().unwrap()),
Some(encode_operation(&create_operation_with_schema()).unwrap()),
key_pair(PRIVATE_KEY)
),
"Could not decode payload hash DecodeError"
)]
#[case::payload_hash_and_size_missing(
entry_signed_encoded_unvalidated(
14,
1,
Some(random_hash()),
Some(HASH.parse().unwrap()),
None,
key_pair(PRIVATE_KEY)
),
"Could not decode payload hash DecodeError"
)]
#[case::skiplink_and_backlink_should_be_unique(
entry_signed_encoded_unvalidated(
13,
1,
Some(HASH.parse().unwrap()),
Some(HASH.parse().unwrap()),
Some(encode_operation(&create_operation_with_schema()).unwrap()),
key_pair(PRIVATE_KEY)
),
"backlink and skiplink are identical"
)]
fn correct_errors_on_invalid_entries(
#[case] unverified_encoded_entry: EncodedEntry,
#[case] expected_error_message: &str,
) {
assert_eq!(
decode_entry(&unverified_encoded_entry)
.unwrap_err()
.to_string(),
expected_error_message
);
}
#[apply(version_fixtures)]
fn decode_fixture_entry(#[case] fixture: Fixture) {
let entry = decode_entry(&fixture.entry_encoded).unwrap();
assert_eq!(entry.log_id(), fixture.entry.log_id());
assert_eq!(entry.seq_num(), fixture.entry.seq_num());
assert_eq!(entry.skiplink(), fixture.entry.skiplink());
assert_eq!(entry.backlink(), fixture.entry.backlink());
}
}