use bytes::{Buf, BufMut};
use commonware_codec::{
EncodeSize, Error as CodecError, Read, ReadExt as _, ReadRangeExt as _, Write,
};
use crate::taint::TrustTag;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Event {
pub kind: String,
pub trust: TrustTag,
pub payload: Vec<u8>,
}
impl Event {
#[must_use]
pub fn new(kind: impl Into<String>, payload: Vec<u8>) -> Self {
Self::with_trust(kind, payload, TrustTag::Unspecified)
}
#[must_use]
pub fn trusted(kind: impl Into<String>, payload: Vec<u8>) -> Self {
Self::with_trust(kind, payload, TrustTag::TrustedUser)
}
#[must_use]
pub fn quarantined(kind: impl Into<String>, payload: Vec<u8>) -> Self {
Self::with_trust(kind, payload, TrustTag::QuarantinedContent)
}
#[must_use]
pub fn with_trust(kind: impl Into<String>, payload: Vec<u8>, trust: TrustTag) -> Self {
Self {
kind: kind.into(),
trust,
payload,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct EventCfg {
pub max_kind_len: usize,
pub max_payload_len: usize,
}
impl EventCfg {
pub const DEFAULT: Self = Self {
max_kind_len: 256,
max_payload_len: 16 * 1024 * 1024,
};
}
impl Default for EventCfg {
fn default() -> Self {
Self::DEFAULT
}
}
impl Write for Event {
fn write(&self, buf: &mut impl BufMut) {
self.kind.as_bytes().to_vec().write(buf);
self.payload.write(buf);
self.trust.as_u8().write(buf);
}
}
impl EncodeSize for Event {
fn encode_size(&self) -> usize {
self.trust.as_u8().encode_size()
+ self.kind.as_bytes().to_vec().encode_size()
+ self.payload.encode_size()
}
}
impl Read for Event {
type Cfg = EventCfg;
fn read_cfg(buf: &mut impl Buf, cfg: &EventCfg) -> Result<Self, CodecError> {
let kind_bytes = <Vec<u8>>::read_range(buf, 0..=cfg.max_kind_len)?;
let kind = String::from_utf8(kind_bytes)
.map_err(|_| CodecError::Invalid("Event", "kind is not valid UTF-8"))?;
let payload = <Vec<u8>>::read_range(buf, 0..=cfg.max_payload_len)?;
let trust = if buf.has_remaining() {
TrustTag::from_u8(u8::read(buf)?)
.ok_or(CodecError::Invalid("Event", "unknown trust tag"))?
} else {
TrustTag::Unspecified
};
Ok(Self {
kind,
trust,
payload,
})
}
}
#[cfg(test)]
mod tests {
use super::{Event, EventCfg};
use crate::taint::TrustTag;
use bytes::BytesMut;
use commonware_codec::{Decode as _, Encode as _, Write as _};
#[test]
fn old_layout_without_trust_byte_decodes_as_unspecified() {
let mut buf = BytesMut::new();
b"user_msg".to_vec().write(&mut buf);
b"summarize my inbox".to_vec().write(&mut buf);
let decoded =
Event::decode_cfg(buf.freeze(), &EventCfg::DEFAULT).expect("old record must decode");
assert_eq!(decoded.kind, "user_msg");
assert_eq!(decoded.payload, b"summarize my inbox".to_vec());
assert_eq!(
decoded.trust,
TrustTag::Unspecified,
"a record with no trust byte must read as Unspecified"
);
}
#[test]
fn round_trips_through_codec() {
let event = Event::new("tool_call", vec![1, 2, 3, 0xff, 0]);
let bytes = event.encode();
let decoded = Event::decode_cfg(bytes, &EventCfg::DEFAULT).expect("decode");
assert_eq!(event, decoded);
}
#[test]
fn trust_tag_survives_codec_round_trip() {
for event in [
Event::trusted("user_msg", b"hi".to_vec()),
Event::quarantined("output_msg", b"<tool result>".to_vec()),
Event::new("turn_start", Vec::new()),
] {
let decoded = Event::decode_cfg(event.encode(), &EventCfg::DEFAULT).expect("decode");
assert_eq!(decoded, event);
assert_eq!(decoded.trust, event.trust);
}
}
#[test]
fn rejects_unknown_trust_tag_byte() {
let mut bytes = Event::trusted("user_msg", b"x".to_vec()).encode().to_vec();
let last = bytes.len() - 1;
bytes[last] = 0xff;
assert!(Event::decode_cfg(&bytes[..], &EventCfg::DEFAULT).is_err());
}
#[test]
fn round_trips_empty_payload() {
let event = Event::new("user_msg", Vec::new());
let bytes = event.encode();
let decoded = Event::decode_cfg(bytes, &EventCfg::DEFAULT).expect("decode");
assert_eq!(event, decoded);
assert!(decoded.payload.is_empty());
}
#[test]
fn rejects_payload_over_cap() {
let event = Event::new("k", vec![0u8; 64]);
let bytes = event.encode();
let tight = EventCfg {
max_kind_len: 256,
max_payload_len: 8,
};
assert!(Event::decode_cfg(bytes, &tight).is_err());
}
}