use sha2::{Digest, Sha256};
use std::fmt;
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct ResourceId {
hash: [u8; 32],
counter: u64,
}
impl ResourceId {
pub fn new(hash: [u8; 32], counter: u64) -> Self {
Self { hash, counter }
}
pub fn from_resource(resource: &Resource, counter: u64) -> Self {
let content_bytes = resource.to_bytes();
let counter_bytes = counter.to_le_bytes();
let mut hasher = Sha256::new();
hasher.update(content_bytes);
hasher.update(counter_bytes);
let hash: [u8; 32] = hasher.finalize().into();
Self { hash, counter }
}
pub fn to_short_hex(&self) -> String {
let hex: String = self.hash[..4]
.iter()
.map(|b| format!("{:02x}", b))
.collect();
format!("{}:{}", hex, self.counter)
}
#[must_use]
pub fn hash(&self) -> [u8; 32] {
self.hash
}
#[must_use]
pub fn counter(&self) -> u64 {
self.counter
}
}
impl fmt::Debug for ResourceId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ResourceId({})", self.to_short_hex())
}
}
impl fmt::Display for ResourceId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_short_hex())
}
}
impl PartialOrd for ResourceId {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ResourceId {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match self.hash.cmp(&other.hash) {
std::cmp::Ordering::Equal => self.counter.cmp(&other.counter),
ord => ord,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MessagePayload {
pub label: String,
pub payload: Vec<u8>,
}
impl MessagePayload {
pub fn new(label: impl Into<String>, payload: Vec<u8>) -> Self {
Self {
label: label.into(),
payload,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChannelState {
pub sender: String,
pub receiver: String,
pub queue: Vec<MessagePayload>,
}
impl ChannelState {
pub fn new(sender: impl Into<String>, receiver: impl Into<String>) -> Self {
Self {
sender: sender.into(),
receiver: receiver.into(),
queue: Vec::new(),
}
}
pub fn queue_size(&self) -> usize {
self.queue.len()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Message {
pub source: String,
pub dest: String,
pub content: MessagePayload,
pub seq_no: u64,
}
impl Message {
pub fn new(
source: impl Into<String>,
dest: impl Into<String>,
label: impl Into<String>,
payload: Vec<u8>,
seq_no: u64,
) -> Self {
Self {
source: source.into(),
dest: dest.into(),
content: MessagePayload::new(label, payload),
seq_no,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Resource {
Channel(ChannelState),
Message(Message),
Session {
role: String,
type_hash: u64,
},
Value {
tag: String,
data: Vec<u8>,
},
}
impl Resource {
pub fn channel(sender: impl Into<String>, receiver: impl Into<String>) -> Self {
Resource::Channel(ChannelState::new(sender, receiver))
}
pub fn message(
source: impl Into<String>,
dest: impl Into<String>,
label: impl Into<String>,
payload: Vec<u8>,
seq_no: u64,
) -> Self {
Resource::Message(Message::new(source, dest, label, payload, seq_no))
}
pub fn session(role: impl Into<String>, type_hash: u64) -> Self {
Resource::Session {
role: role.into(),
type_hash,
}
}
pub fn value(tag: impl Into<String>, data: Vec<u8>) -> Self {
Resource::Value {
tag: tag.into(),
data,
}
}
pub fn kind(&self) -> &'static str {
match self {
Resource::Channel(_) => "channel",
Resource::Message(_) => "message",
Resource::Session { .. } => "session",
Resource::Value { .. } => "value",
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
match self {
Resource::Channel(cs) => {
bytes.extend_from_slice(b"channel:");
bytes.extend_from_slice(cs.sender.as_bytes());
bytes.push(0);
bytes.extend_from_slice(cs.receiver.as_bytes());
bytes.push(0);
bytes.extend_from_slice(&cs.queue.len().to_le_bytes());
}
Resource::Message(msg) => {
bytes.extend_from_slice(b"message:");
bytes.extend_from_slice(msg.source.as_bytes());
bytes.push(0);
bytes.extend_from_slice(msg.dest.as_bytes());
bytes.push(0);
bytes.extend_from_slice(msg.content.label.as_bytes());
bytes.push(0);
bytes.extend_from_slice(&msg.seq_no.to_le_bytes());
}
Resource::Session { role, type_hash } => {
bytes.extend_from_slice(b"session:");
bytes.extend_from_slice(role.as_bytes());
bytes.push(0);
bytes.extend_from_slice(&type_hash.to_le_bytes());
}
Resource::Value { tag, data } => {
bytes.extend_from_slice(b"value:");
bytes.extend_from_slice(tag.as_bytes());
bytes.push(0);
bytes.extend_from_slice(&data.len().to_le_bytes());
bytes.extend_from_slice(data);
}
}
bytes
}
}
impl fmt::Display for Resource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Resource({})", self.kind())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HeapError {
NotFound(ResourceId),
AlreadyConsumed(ResourceId),
AlreadyExists(ResourceId),
TypeMismatch { expected: String, got: String },
Other(String),
}
impl fmt::Display for HeapError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HeapError::NotFound(rid) => write!(f, "Resource not found: {}", rid),
HeapError::AlreadyConsumed(rid) => write!(f, "Resource already consumed: {}", rid),
HeapError::AlreadyExists(rid) => write!(f, "Resource already exists: {}", rid),
HeapError::TypeMismatch { expected, got } => {
write!(f, "Type mismatch: expected {}, got {}", expected, got)
}
HeapError::Other(msg) => write!(f, "{}", msg),
}
}
}
impl std::error::Error for HeapError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resource_id_creation() {
let r1 = Resource::channel("Alice", "Bob");
let r2 = Resource::channel("Alice", "Bob");
let id1 = ResourceId::from_resource(&r1, 0);
let id2 = ResourceId::from_resource(&r2, 0);
let id3 = ResourceId::from_resource(&r1, 1);
assert_eq!(id1, id2);
assert_ne!(id1, id3);
}
#[test]
fn test_resource_id_ordering() {
let r = Resource::channel("Alice", "Bob");
let id1 = ResourceId::from_resource(&r, 0);
let id2 = ResourceId::from_resource(&r, 1);
assert!(id1 < id2);
}
#[test]
fn test_resource_to_bytes() {
let channel = Resource::channel("Alice", "Bob");
let bytes = channel.to_bytes();
assert!(bytes.starts_with(b"channel:"));
let msg = Resource::message("Alice", "Bob", "Hello", vec![1, 2, 3], 42);
let bytes = msg.to_bytes();
assert!(bytes.starts_with(b"message:"));
}
#[test]
fn test_message_creation() {
let msg = Message::new("Alice", "Bob", "Ping", vec![1, 2, 3], 1);
assert_eq!(msg.source, "Alice");
assert_eq!(msg.dest, "Bob");
assert_eq!(msg.content.label, "Ping");
assert_eq!(msg.seq_no, 1);
}
#[test]
fn test_channel_state() {
let mut channel = ChannelState::new("Alice", "Bob");
assert_eq!(channel.queue_size(), 0);
channel.queue.push(MessagePayload::new("Test", vec![]));
assert_eq!(channel.queue_size(), 1);
}
#[test]
fn test_heap_error_display() {
let rid = ResourceId::new([0u8; 32], 42);
let err = HeapError::NotFound(rid);
assert!(err.to_string().contains("not found"));
}
}