use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
use sui_graph_store::GraphHash;
pub const FRAME_MAGIC: [u8; 4] = *b"SUI1";
pub type RequestId = u64;
#[derive(
Archive,
RkyvSerialize,
RkyvDeserialize,
Debug,
Clone,
PartialEq,
)]
#[rkyv(derive(Debug))]
pub enum WireFrame {
Heartbeat(Heartbeat),
Request {
id: RequestId,
body: LocalRequest,
},
Response {
id: RequestId,
body: LocalResponse,
},
Event { topic: String, payload: Vec<u8> },
Goodbye,
}
#[derive(
Archive,
RkyvSerialize,
RkyvDeserialize,
Debug,
Clone,
Copy,
PartialEq,
Eq,
)]
#[rkyv(derive(Debug))]
pub struct Heartbeat {
pub nonce: u64,
pub sent_unix_nanos: u64,
}
#[derive(
Archive,
RkyvSerialize,
RkyvDeserialize,
Debug,
Clone,
PartialEq,
)]
#[rkyv(derive(Debug))]
pub enum LocalRequest {
Ping,
GetGraph {
kind_tag: u8,
hash: GraphHash,
},
PutGraph {
kind_tag: u8,
hash: GraphHash,
bytes: Vec<u8>,
},
GetStats,
}
#[derive(
Archive,
RkyvSerialize,
RkyvDeserialize,
Debug,
Clone,
PartialEq,
)]
#[rkyv(derive(Debug))]
pub enum LocalResponse {
Pong {
build_id: [u8; 32],
uptime_seconds: u64,
},
GraphBytes(Vec<u8>),
GraphStored {
hash: GraphHash,
},
Error(LocalError),
Stats(StatsSnapshot),
}
#[derive(
Archive,
RkyvSerialize,
RkyvDeserialize,
Debug,
Clone,
PartialEq,
Eq,
)]
#[rkyv(derive(Debug))]
pub struct LocalError {
pub code: ErrorCode,
pub message: String,
}
#[derive(
Archive,
RkyvSerialize,
RkyvDeserialize,
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
)]
#[rkyv(derive(Debug))]
pub enum ErrorCode {
GraphNotFound,
GraphHashMismatch,
InvalidGraphKind,
StoreUnavailable,
Internal,
}
#[derive(
Archive,
RkyvSerialize,
RkyvDeserialize,
Debug,
Clone,
Copy,
PartialEq,
Eq,
)]
#[rkyv(derive(Debug))]
pub struct StatsSnapshot {
pub hot_cache_entries: u64,
pub hot_cache_bytes: u64,
pub cache_hits: u64,
pub cache_misses: u64,
pub puts: u64,
pub uptime_seconds: u64,
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn ping_pong_roundtrips() {
let frame = WireFrame::Request {
id: 42,
body: LocalRequest::Ping,
};
let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&frame).unwrap();
let archived = rkyv::access::<ArchivedWireFrame, rkyv::rancor::Error>(&bytes).unwrap();
match archived {
ArchivedWireFrame::Request { id, body } => {
assert_eq!(id.to_native(), 42);
assert!(matches!(body, ArchivedLocalRequest::Ping));
}
_ => panic!("expected Request"),
}
}
#[test]
fn get_graph_request_carries_kind_and_hash() {
let h = GraphHash::of(b"sample");
let frame = WireFrame::Request {
id: 1,
body: LocalRequest::GetGraph {
kind_tag: 1,
hash: h,
},
};
let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&frame).unwrap();
let archived = rkyv::access::<ArchivedWireFrame, rkyv::rancor::Error>(&bytes).unwrap();
match archived {
ArchivedWireFrame::Request { body, .. } => match body {
ArchivedLocalRequest::GetGraph { kind_tag, hash } => {
assert_eq!(*kind_tag, 1);
assert_eq!(hash.0, h.0);
}
_ => panic!("expected GetGraph"),
},
_ => panic!("expected Request"),
}
}
#[test]
fn error_response_carries_code() {
let frame = WireFrame::Response {
id: 7,
body: LocalResponse::Error(LocalError {
code: ErrorCode::GraphNotFound,
message: "blob missing".into(),
}),
};
let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&frame).unwrap();
let archived = rkyv::access::<ArchivedWireFrame, rkyv::rancor::Error>(&bytes).unwrap();
match archived {
ArchivedWireFrame::Response { body, .. } => match body {
ArchivedLocalResponse::Error(err) => {
assert!(matches!(err.code, ArchivedErrorCode::GraphNotFound));
assert_eq!(err.message.as_str(), "blob missing");
}
_ => panic!("expected Error response"),
},
_ => panic!("expected Response"),
}
}
#[test]
fn frame_magic_is_fixed() {
assert_eq!(&FRAME_MAGIC, b"SUI1");
}
}