p2panda_rs/entry/
entry.rs1use std::convert::TryInto;
4use std::hash::Hash as StdHash;
5
6use bamboo_rs_core_ed25519_yasmf::Entry as BambooEntry;
7
8use crate::entry::encode::sign_entry;
9use crate::entry::error::EntryBuilderError;
10use crate::entry::traits::AsEntry;
11use crate::entry::{LogId, SeqNum, Signature};
12use crate::hash::Hash;
13use crate::identity::{KeyPair, PublicKey};
14use crate::operation::EncodedOperation;
15
16#[derive(Clone, Debug, Default)]
18pub struct EntryBuilder {
19 log_id: LogId,
21
22 seq_num: SeqNum,
24
25 skiplink: Option<Hash>,
27
28 backlink: Option<Hash>,
30}
31
32impl EntryBuilder {
33 pub fn new() -> Self {
35 Self::default()
36 }
37
38 pub fn log_id(mut self, log_id: &LogId) -> Self {
40 self.log_id = log_id.to_owned();
41 self
42 }
43
44 pub fn seq_num(mut self, seq_num: &SeqNum) -> Self {
46 self.seq_num = seq_num.to_owned();
47 self
48 }
49
50 pub fn skiplink(mut self, hash: &Hash) -> Self {
52 self.skiplink = Some(hash.to_owned());
53 self
54 }
55
56 pub fn backlink(mut self, hash: &Hash) -> Self {
58 self.backlink = Some(hash.to_owned());
59 self
60 }
61
62 pub fn sign(
72 &self,
73 encoded_operation: &EncodedOperation,
74 key_pair: &KeyPair,
75 ) -> Result<Entry, EntryBuilderError> {
76 let entry = sign_entry(
77 &self.log_id,
78 &self.seq_num,
79 self.skiplink.as_ref(),
80 self.backlink.as_ref(),
81 encoded_operation,
82 key_pair,
83 )?;
84
85 Ok(entry)
86 }
87}
88
89#[derive(Debug, Clone, Eq, PartialEq, StdHash)]
107pub struct Entry {
108 pub(crate) public_key: PublicKey,
110
111 pub(crate) log_id: LogId,
113
114 pub(crate) seq_num: SeqNum,
116
117 pub(crate) skiplink: Option<Hash>,
119
120 pub(crate) backlink: Option<Hash>,
122
123 pub(crate) payload_size: u64,
125
126 pub(crate) payload_hash: Hash,
128
129 pub(crate) signature: Signature,
131}
132
133impl AsEntry for Entry {
134 fn public_key(&self) -> &PublicKey {
136 &self.public_key
137 }
138
139 fn log_id(&self) -> &LogId {
141 &self.log_id
142 }
143
144 fn seq_num(&self) -> &SeqNum {
146 &self.seq_num
147 }
148
149 fn skiplink(&self) -> Option<&Hash> {
151 self.skiplink.as_ref()
152 }
153
154 fn backlink(&self) -> Option<&Hash> {
156 self.backlink.as_ref()
157 }
158
159 fn payload_size(&self) -> u64 {
161 self.payload_size
162 }
163
164 fn payload_hash(&self) -> &Hash {
166 &self.payload_hash
167 }
168
169 fn signature(&self) -> &Signature {
171 &self.signature
172 }
173}
174
175impl From<BambooEntry<&[u8], &[u8]>> for Entry {
176 fn from(entry: BambooEntry<&[u8], &[u8]>) -> Self {
177 let backlink: Option<Hash> = entry.backlink.map(|link| (&link).into());
179 let skiplink: Option<Hash> = entry.lipmaa_link.map(|link| (&link).into());
180 let payload_hash: Hash = (&entry.payload_hash).into();
181
182 let signature = entry.sig.expect("signature expected").into();
184
185 let seq_num = entry.seq_num.try_into().expect("invalid sequence number");
188
189 Entry {
190 public_key: (&entry.author).into(),
191 log_id: entry.log_id.into(),
192 seq_num,
193 skiplink,
194 backlink,
195 payload_hash,
196 payload_size: entry.payload_size,
197 signature,
198 }
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use rstest::rstest;
205
206 use crate::entry::traits::AsEntry;
207 use crate::entry::{LogId, SeqNum};
208 use crate::hash::Hash;
209 use crate::identity::KeyPair;
210 use crate::operation::EncodedOperation;
211 use crate::test_utils::fixtures::{encoded_operation, key_pair, random_hash};
212
213 use super::EntryBuilder;
214
215 #[rstest]
216 fn entry_builder(
217 #[from(random_hash)] entry_hash: Hash,
218 encoded_operation: EncodedOperation,
219 key_pair: KeyPair,
220 ) {
221 let log_id = LogId::new(92);
222 let seq_num = SeqNum::new(14002).unwrap();
223
224 let entry = EntryBuilder::new()
225 .log_id(&log_id)
226 .seq_num(&seq_num)
227 .backlink(&entry_hash)
228 .sign(&encoded_operation, &key_pair)
229 .unwrap();
230
231 assert_eq!(entry.public_key(), &key_pair.public_key());
232 assert_eq!(entry.log_id(), &log_id);
233 assert_eq!(entry.seq_num(), &seq_num);
234 assert_eq!(entry.skiplink(), None);
235 assert_eq!(entry.backlink(), Some(&entry_hash));
236 assert_eq!(entry.payload_hash(), &encoded_operation.hash());
237 assert_eq!(entry.payload_size(), encoded_operation.size());
238 }
239
240 #[rstest]
241 fn entry_builder_validation(
242 #[from(random_hash)] entry_hash_1: Hash,
243 #[from(random_hash)] entry_hash_2: Hash,
244 encoded_operation: EncodedOperation,
245 key_pair: KeyPair,
246 ) {
247 assert!(EntryBuilder::new()
249 .sign(&encoded_operation, &key_pair)
250 .is_ok());
251
252 assert!(EntryBuilder::new()
254 .skiplink(&entry_hash_1)
255 .backlink(&entry_hash_2)
256 .sign(&encoded_operation, &key_pair)
257 .is_err());
258
259 assert!(EntryBuilder::new()
261 .seq_num(&SeqNum::new(2).unwrap())
262 .backlink(&entry_hash_1)
263 .sign(&encoded_operation, &key_pair)
264 .is_ok());
265
266 assert!(EntryBuilder::new()
267 .seq_num(&SeqNum::new(2).unwrap())
268 .sign(&encoded_operation, &key_pair)
269 .is_err());
270
271 assert!(EntryBuilder::new()
273 .seq_num(&SeqNum::new(4).unwrap())
274 .backlink(&entry_hash_1)
275 .skiplink(&entry_hash_2)
276 .sign(&encoded_operation, &key_pair)
277 .is_ok());
278
279 assert!(EntryBuilder::new()
280 .seq_num(&SeqNum::new(4).unwrap())
281 .backlink(&entry_hash_1)
282 .sign(&encoded_operation, &key_pair)
283 .is_err());
284 }
285
286 #[rstest]
287 fn entry_links_methods(
288 #[from(random_hash)] entry_hash_1: Hash,
289 #[from(random_hash)] entry_hash_2: Hash,
290 encoded_operation: EncodedOperation,
291 key_pair: KeyPair,
292 ) {
293 let entry = EntryBuilder::new()
295 .sign(&encoded_operation, &key_pair)
296 .unwrap();
297
298 assert_eq!(entry.seq_num_backlink(), None);
299 assert!(!entry.is_skiplink_required());
303
304 let entry = EntryBuilder::new()
306 .seq_num(&SeqNum::new(2).unwrap())
307 .backlink(&entry_hash_1)
308 .sign(&encoded_operation, &key_pair)
309 .unwrap();
310
311 assert_eq!(entry.seq_num_backlink(), Some(SeqNum::new(1).unwrap()));
312 assert!(!entry.is_skiplink_required());
316
317 let entry = EntryBuilder::new()
319 .seq_num(&SeqNum::new(4).unwrap())
320 .backlink(&entry_hash_1)
321 .skiplink(&entry_hash_2)
322 .sign(&encoded_operation, &key_pair)
323 .unwrap();
324
325 assert_eq!(entry.seq_num_backlink(), Some(SeqNum::new(3).unwrap()));
326 assert_eq!(entry.seq_num_skiplink(), Some(SeqNum::new(1).unwrap()));
327 assert!(entry.is_skiplink_required());
328 }
329}