use bamboo_rs_core::entry::is_lipmaa_required;
use serde::{Deserialize, Serialize};
use crate::entry::{EntryError, LogId, SeqNum};
use crate::hash::Hash;
use crate::message::Message;
use crate::Validate;
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Entry {
entry_hash_backlink: Option<Hash>,
entry_hash_skiplink: Option<Hash>,
log_id: LogId,
message: Option<Message>,
seq_num: SeqNum,
}
impl Entry {
pub fn new(
log_id: &LogId,
message: Option<&Message>,
entry_hash_skiplink: Option<&Hash>,
entry_hash_backlink: Option<&Hash>,
seq_num: &SeqNum,
) -> Result<Self, EntryError> {
let entry = Self {
log_id: log_id.clone().to_owned(),
message: message.cloned(),
entry_hash_skiplink: entry_hash_skiplink.cloned(),
entry_hash_backlink: entry_hash_backlink.cloned(),
seq_num: seq_num.clone(),
};
entry.validate()?;
Ok(entry)
}
pub fn backlink_hash(&self) -> Option<&Hash> {
self.entry_hash_backlink.as_ref()
}
pub fn skiplink_hash(&self) -> Option<&Hash> {
self.entry_hash_skiplink.as_ref()
}
pub fn seq_num(&self) -> &SeqNum {
&self.seq_num
}
pub fn seq_num_backlink(&self) -> Option<SeqNum> {
self.seq_num.backlink_seq_num()
}
pub fn seq_num_skiplink(&self) -> Option<SeqNum> {
self.seq_num.skiplink_seq_num()
}
pub fn message(&self) -> Option<&Message> {
self.message.as_ref()
}
pub fn log_id(&self) -> &LogId {
&self.log_id
}
pub fn has_message(&self) -> bool {
self.message.is_some()
}
pub fn is_skiplink_required(&self) -> bool {
is_lipmaa_required(self.seq_num.as_i64() as u64)
}
}
impl Validate for Entry {
type Error = EntryError;
fn validate(&self) -> Result<(), Self::Error> {
match (
self.seq_num.is_first(),
self.entry_hash_backlink.is_some(),
self.entry_hash_skiplink.is_some(),
self.is_skiplink_required(),
) {
(true, false, false, false) => Ok(()),
(false, true, false, false) => Ok(()),
(false, true, true, _) => Ok(()),
(_, _, _, _) => Err(EntryError::InvalidLinks),
}
}
}
#[cfg(test)]
mod tests {
use crate::entry::{LogId, SeqNum};
use crate::hash::Hash;
use crate::message::{Message, MessageFields, MessageValue};
use super::Entry;
#[test]
fn validation() {
let mut fields = MessageFields::new();
fields
.add("test", MessageValue::Text("Hello".to_owned()))
.unwrap();
let message =
Message::new_create(Hash::new_from_bytes(vec![1, 2, 3]).unwrap(), fields).unwrap();
let backlink = Hash::new_from_bytes(vec![7, 8, 9]).unwrap();
assert!(Entry::new(
&LogId::default(),
Some(&message),
None,
None,
&SeqNum::new(1).unwrap()
)
.is_ok());
assert!(Entry::new(
&LogId::default(),
Some(&message),
Some(&backlink.clone()),
Some(&backlink),
&SeqNum::new(1).unwrap()
)
.is_err());
assert!(Entry::new(
&LogId::default(),
Some(&message),
Some(&backlink.clone()),
Some(&backlink),
&SeqNum::new(2).unwrap()
)
.is_ok());
assert!(Entry::new(
&LogId::default(),
Some(&message),
None,
Some(&backlink),
&SeqNum::new(2).unwrap()
)
.is_ok());
assert!(Entry::new(
&LogId::default(),
Some(&message),
None,
None,
&SeqNum::new(2).unwrap()
)
.is_err());
}
}