use std::convert::TryInto;
use std::hash::Hash as StdHash;
use bamboo_rs_core_ed25519_yasmf::Entry as BambooEntry;
use crate::entry::encode::sign_entry;
use crate::entry::error::EntryBuilderError;
use crate::entry::traits::AsEntry;
use crate::entry::{LogId, SeqNum, Signature};
use crate::hash::Hash;
use crate::identity::{KeyPair, PublicKey};
use crate::operation::EncodedOperation;
#[derive(Clone, Debug, Default)]
pub struct EntryBuilder {
log_id: LogId,
seq_num: SeqNum,
skiplink: Option<Hash>,
backlink: Option<Hash>,
}
impl EntryBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn log_id(mut self, log_id: &LogId) -> Self {
self.log_id = log_id.to_owned();
self
}
pub fn seq_num(mut self, seq_num: &SeqNum) -> Self {
self.seq_num = seq_num.to_owned();
self
}
pub fn skiplink(mut self, hash: &Hash) -> Self {
self.skiplink = Some(hash.to_owned());
self
}
pub fn backlink(mut self, hash: &Hash) -> Self {
self.backlink = Some(hash.to_owned());
self
}
pub fn sign(
&self,
encoded_operation: &EncodedOperation,
key_pair: &KeyPair,
) -> Result<Entry, EntryBuilderError> {
let entry = sign_entry(
&self.log_id,
&self.seq_num,
self.skiplink.as_ref(),
self.backlink.as_ref(),
encoded_operation,
key_pair,
)?;
Ok(entry)
}
}
#[derive(Debug, Clone, Eq, PartialEq, StdHash)]
pub struct Entry {
pub(crate) public_key: PublicKey,
pub(crate) log_id: LogId,
pub(crate) seq_num: SeqNum,
pub(crate) skiplink: Option<Hash>,
pub(crate) backlink: Option<Hash>,
pub(crate) payload_size: u64,
pub(crate) payload_hash: Hash,
pub(crate) signature: Signature,
}
impl AsEntry for Entry {
fn public_key(&self) -> &PublicKey {
&self.public_key
}
fn log_id(&self) -> &LogId {
&self.log_id
}
fn seq_num(&self) -> &SeqNum {
&self.seq_num
}
fn skiplink(&self) -> Option<&Hash> {
self.skiplink.as_ref()
}
fn backlink(&self) -> Option<&Hash> {
self.backlink.as_ref()
}
fn payload_size(&self) -> u64 {
self.payload_size
}
fn payload_hash(&self) -> &Hash {
&self.payload_hash
}
fn signature(&self) -> &Signature {
&self.signature
}
}
impl From<BambooEntry<&[u8], &[u8]>> for Entry {
fn from(entry: BambooEntry<&[u8], &[u8]>) -> Self {
let backlink: Option<Hash> = entry.backlink.map(|link| (&link).into());
let skiplink: Option<Hash> = entry.lipmaa_link.map(|link| (&link).into());
let payload_hash: Hash = (&entry.payload_hash).into();
let signature = entry.sig.expect("signature expected").into();
let seq_num = entry.seq_num.try_into().expect("invalid sequence number");
Entry {
public_key: (&entry.author).into(),
log_id: entry.log_id.into(),
seq_num,
skiplink,
backlink,
payload_hash,
payload_size: entry.payload_size,
signature,
}
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use crate::entry::traits::AsEntry;
use crate::entry::{LogId, SeqNum};
use crate::hash::Hash;
use crate::identity::KeyPair;
use crate::operation::EncodedOperation;
use crate::test_utils::fixtures::{encoded_operation, key_pair, random_hash};
use super::EntryBuilder;
#[rstest]
fn entry_builder(
#[from(random_hash)] entry_hash: Hash,
encoded_operation: EncodedOperation,
key_pair: KeyPair,
) {
let log_id = LogId::new(92);
let seq_num = SeqNum::new(14002).unwrap();
let entry = EntryBuilder::new()
.log_id(&log_id)
.seq_num(&seq_num)
.backlink(&entry_hash)
.sign(&encoded_operation, &key_pair)
.unwrap();
assert_eq!(entry.public_key(), &key_pair.public_key());
assert_eq!(entry.log_id(), &log_id);
assert_eq!(entry.seq_num(), &seq_num);
assert_eq!(entry.skiplink(), None);
assert_eq!(entry.backlink(), Some(&entry_hash));
assert_eq!(entry.payload_hash(), &encoded_operation.hash());
assert_eq!(entry.payload_size(), encoded_operation.size());
}
#[rstest]
fn entry_builder_validation(
#[from(random_hash)] entry_hash_1: Hash,
#[from(random_hash)] entry_hash_2: Hash,
encoded_operation: EncodedOperation,
key_pair: KeyPair,
) {
assert!(EntryBuilder::new()
.sign(&encoded_operation, &key_pair)
.is_ok());
assert!(EntryBuilder::new()
.skiplink(&entry_hash_1)
.backlink(&entry_hash_2)
.sign(&encoded_operation, &key_pair)
.is_err());
assert!(EntryBuilder::new()
.seq_num(&SeqNum::new(2).unwrap())
.backlink(&entry_hash_1)
.sign(&encoded_operation, &key_pair)
.is_ok());
assert!(EntryBuilder::new()
.seq_num(&SeqNum::new(2).unwrap())
.sign(&encoded_operation, &key_pair)
.is_err());
assert!(EntryBuilder::new()
.seq_num(&SeqNum::new(4).unwrap())
.backlink(&entry_hash_1)
.skiplink(&entry_hash_2)
.sign(&encoded_operation, &key_pair)
.is_ok());
assert!(EntryBuilder::new()
.seq_num(&SeqNum::new(4).unwrap())
.backlink(&entry_hash_1)
.sign(&encoded_operation, &key_pair)
.is_err());
}
#[rstest]
fn entry_links_methods(
#[from(random_hash)] entry_hash_1: Hash,
#[from(random_hash)] entry_hash_2: Hash,
encoded_operation: EncodedOperation,
key_pair: KeyPair,
) {
let entry = EntryBuilder::new()
.sign(&encoded_operation, &key_pair)
.unwrap();
assert_eq!(entry.seq_num_backlink(), None);
assert!(!entry.is_skiplink_required());
let entry = EntryBuilder::new()
.seq_num(&SeqNum::new(2).unwrap())
.backlink(&entry_hash_1)
.sign(&encoded_operation, &key_pair)
.unwrap();
assert_eq!(entry.seq_num_backlink(), Some(SeqNum::new(1).unwrap()));
assert!(!entry.is_skiplink_required());
let entry = EntryBuilder::new()
.seq_num(&SeqNum::new(4).unwrap())
.backlink(&entry_hash_1)
.skiplink(&entry_hash_2)
.sign(&encoded_operation, &key_pair)
.unwrap();
assert_eq!(entry.seq_num_backlink(), Some(SeqNum::new(3).unwrap()));
assert_eq!(entry.seq_num_skiplink(), Some(SeqNum::new(1).unwrap()));
assert!(entry.is_skiplink_required());
}
}