use bytes::Bytes;
use nautilus_common::msgbus::{self, MStr};
use nautilus_core::UnixNanos;
use serde::{Deserialize, Serialize};
use ustr::Ustr;
use crate::{hash::EntryHash, headers::Headers, wire};
pub type Topic = MStr<msgbus::Topic>;
pub type PayloadType = Ustr;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct EventStoreEntry {
pub entry_hash: EntryHash,
pub seq: u64,
pub headers: Headers,
pub topic: Topic,
pub payload_type: PayloadType,
pub payload: Bytes,
#[serde(with = "wire::nanos_as_u64")]
pub ts_init: UnixNanos,
#[serde(with = "wire::nanos_as_u64")]
pub ts_publish: UnixNanos,
}
impl EventStoreEntry {
#[must_use]
#[allow(clippy::too_many_arguments)] pub fn new(
entry_hash: EntryHash,
seq: u64,
headers: Headers,
topic: Topic,
payload_type: PayloadType,
payload: Bytes,
ts_init: UnixNanos,
ts_publish: UnixNanos,
) -> Self {
Self {
entry_hash,
seq,
headers,
topic,
payload_type,
payload,
ts_init,
ts_publish,
}
}
#[must_use]
pub fn recompute_hash(&self) -> EntryHash {
crate::hash::compute_entry_hash(
self.seq,
self.ts_init,
self.ts_publish,
self.topic.as_ref(),
self.payload_type.as_str(),
&self.payload,
&self.headers,
)
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
use crate::hash::compute_entry_hash;
fn entry() -> EventStoreEntry {
let topic: Topic = "exec.command".into();
let payload_type = Ustr::from("SubmitOrder");
let payload = Bytes::from_static(b"\x01\x02\x03");
let headers = Headers::empty();
let hash = compute_entry_hash(
1,
UnixNanos::from(10),
UnixNanos::from(11),
topic.as_ref(),
payload_type.as_str(),
&payload,
&headers,
);
EventStoreEntry::new(
hash,
1,
headers,
topic,
payload_type,
payload,
UnixNanos::from(10),
UnixNanos::from(11),
)
}
#[rstest]
fn recompute_matches_stored_hash() {
let e = entry();
assert_eq!(e.recompute_hash(), e.entry_hash);
}
#[rstest]
fn tampered_payload_breaks_hash_check() {
let mut e = entry();
e.payload = Bytes::from_static(b"\x01\x02\x04");
assert_ne!(e.recompute_hash(), e.entry_hash);
}
}