use crate::distributed::{NodeId, PeerId, PeerPermissions, RemoteOp};
use std::fmt;
pub const SHM_BLOB_HEADER_LEN: usize = 40;
const SHM_BLOB_MAGIC: u32 = 0x4c5a_5348; const SHM_BLOB_VERSION: u16 = 1;
const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
pub type IpcPayload = Vec<u8>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct ShmBlobRef {
pub offset: u64,
pub len: u64,
pub generation: u64,
pub epoch: u64,
pub checksum: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum IpcValue {
Inline(IpcPayload),
SharedBlob(ShmBlobRef),
}
impl From<IpcPayload> for IpcValue {
fn from(payload: IpcPayload) -> Self {
Self::Inline(payload)
}
}
impl From<ShmBlobRef> for IpcValue {
fn from(blob: ShmBlobRef) -> Self {
Self::SharedBlob(blob)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ShmBlobArenaError {
CapacityTooSmall {
capacity: usize,
min_capacity: usize,
},
BlobTooLarge {
len: usize,
max_len: usize,
},
DescriptorOutOfBounds {
offset: u64,
len: u64,
capacity: usize,
},
DescriptorMismatch {
field: &'static str,
},
ChecksumMismatch {
expected: u64,
actual: u64,
},
GenerationOverflow,
}
impl fmt::Display for ShmBlobArenaError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CapacityTooSmall {
capacity,
min_capacity,
} => write!(
f,
"SHM blob arena capacity {capacity} is smaller than minimum {min_capacity}"
),
Self::BlobTooLarge { len, max_len } => {
write!(f, "SHM blob length {len} exceeds maximum {max_len}")
}
Self::DescriptorOutOfBounds {
offset,
len,
capacity,
} => write!(
f,
"SHM blob descriptor offset={offset} len={len} exceeds arena capacity {capacity}"
),
Self::DescriptorMismatch { field } => {
write!(f, "SHM blob descriptor mismatch for {field}")
}
Self::ChecksumMismatch { expected, actual } => write!(
f,
"SHM blob checksum mismatch: expected {expected:#x}, got {actual:#x}"
),
Self::GenerationOverflow => write!(f, "SHM blob generation counter overflowed"),
}
}
}
impl std::error::Error for ShmBlobArenaError {}
#[derive(Debug, Clone)]
pub struct ShmBlobArena<B = Vec<u8>> {
bytes: B,
write_offset: usize,
next_generation: u64,
}
impl ShmBlobArena<Vec<u8>> {
pub fn with_capacity(capacity: usize) -> Result<Self, ShmBlobArenaError> {
Self::from_buffer(vec![0; capacity])
}
}
impl<B> ShmBlobArena<B>
where
B: AsRef<[u8]> + AsMut<[u8]>,
{
pub fn from_buffer(bytes: B) -> Result<Self, ShmBlobArenaError> {
let capacity = bytes.as_ref().len();
let min_capacity = SHM_BLOB_HEADER_LEN + 1;
if capacity < min_capacity {
return Err(ShmBlobArenaError::CapacityTooSmall {
capacity,
min_capacity,
});
}
Ok(Self {
bytes,
write_offset: 0,
next_generation: 1,
})
}
pub fn capacity(&self) -> usize {
self.bytes.as_ref().len()
}
pub fn max_blob_len(&self) -> usize {
self.capacity() - SHM_BLOB_HEADER_LEN
}
pub fn write_offset(&self) -> usize {
self.write_offset
}
pub fn bytes(&self) -> &[u8] {
self.bytes.as_ref()
}
pub fn bytes_mut(&mut self) -> &mut [u8] {
self.bytes.as_mut()
}
pub fn into_inner(self) -> B {
self.bytes
}
pub fn write_blob(
&mut self,
epoch: u64,
payload: &[u8],
) -> Result<ShmBlobRef, ShmBlobArenaError> {
let capacity = self.capacity();
let max_len = self.max_blob_len();
if payload.len() > max_len {
return Err(ShmBlobArenaError::BlobTooLarge {
len: payload.len(),
max_len,
});
}
let total_len = SHM_BLOB_HEADER_LEN + payload.len();
if self.write_offset + total_len > capacity {
self.write_offset = 0;
}
let generation = self.next_generation;
self.next_generation = self
.next_generation
.checked_add(1)
.ok_or(ShmBlobArenaError::GenerationOverflow)?;
let offset = self.write_offset;
let checksum = checksum(payload);
let descriptor = ShmBlobRef {
offset: offset as u64,
len: payload.len() as u64,
generation,
epoch,
checksum,
};
let payload_offset = offset + SHM_BLOB_HEADER_LEN;
let payload_end = payload_offset + payload.len();
write_header(self.bytes.as_mut(), offset, descriptor);
self.bytes.as_mut()[payload_offset..payload_end].copy_from_slice(payload);
self.write_offset += total_len;
if self.write_offset == capacity {
self.write_offset = 0;
}
Ok(descriptor)
}
pub fn read_blob(&self, descriptor: ShmBlobRef) -> Result<&[u8], ShmBlobArenaError> {
let capacity = self.capacity();
let offset = usize::try_from(descriptor.offset).map_err(|_| {
ShmBlobArenaError::DescriptorOutOfBounds {
offset: descriptor.offset,
len: descriptor.len,
capacity,
}
})?;
let len = usize::try_from(descriptor.len).map_err(|_| {
ShmBlobArenaError::DescriptorOutOfBounds {
offset: descriptor.offset,
len: descriptor.len,
capacity,
}
})?;
let total_len = SHM_BLOB_HEADER_LEN.checked_add(len).ok_or(
ShmBlobArenaError::DescriptorOutOfBounds {
offset: descriptor.offset,
len: descriptor.len,
capacity,
},
)?;
if offset
.checked_add(total_len)
.is_none_or(|end| end > capacity)
{
return Err(ShmBlobArenaError::DescriptorOutOfBounds {
offset: descriptor.offset,
len: descriptor.len,
capacity,
});
}
let header = read_header(self.bytes.as_ref(), offset)?;
if header != descriptor {
return Err(mismatch_field(header, descriptor));
}
let payload_offset = offset + SHM_BLOB_HEADER_LEN;
let payload = &self.bytes.as_ref()[payload_offset..payload_offset + len];
let actual = checksum(payload);
if actual != descriptor.checksum {
return Err(ShmBlobArenaError::ChecksumMismatch {
expected: descriptor.checksum,
actual,
});
}
Ok(payload)
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum NodeState {
Payload(IpcPayload),
SharedBlob(ShmBlobRef),
Opaque,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct NodeSnapshot {
pub node: NodeId,
pub type_tag: String,
pub state: NodeState,
}
impl NodeSnapshot {
pub fn payload(node: NodeId, type_tag: impl Into<String>, payload: IpcPayload) -> Self {
Self {
node,
type_tag: type_tag.into(),
state: NodeState::Payload(payload),
}
}
pub fn opaque(node: NodeId, type_tag: impl Into<String>) -> Self {
Self {
node,
type_tag: type_tag.into(),
state: NodeState::Opaque,
}
}
pub fn shared_blob(node: NodeId, type_tag: impl Into<String>, blob: ShmBlobRef) -> Self {
Self {
node,
type_tag: type_tag.into(),
state: NodeState::SharedBlob(blob),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct EdgeSnapshot {
pub dependent: NodeId,
pub dependency: NodeId,
}
impl EdgeSnapshot {
pub fn new(dependent: NodeId, dependency: NodeId) -> Self {
Self {
dependent,
dependency,
}
}
fn is_readable_by(self, permissions: &PeerPermissions, peer: PeerId) -> bool {
can_read(permissions, peer, self.dependent) && can_read(permissions, peer, self.dependency)
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Snapshot {
pub epoch: u64,
pub nodes: Vec<NodeSnapshot>,
pub edges: Vec<EdgeSnapshot>,
pub roots: Vec<NodeId>,
}
impl Snapshot {
pub fn new(
epoch: u64,
nodes: Vec<NodeSnapshot>,
edges: Vec<EdgeSnapshot>,
roots: Vec<NodeId>,
) -> Self {
Self {
epoch,
nodes,
edges,
roots,
}
}
pub fn filter_readable(&self, permissions: &PeerPermissions, peer: PeerId) -> Self {
let nodes = self
.nodes
.iter()
.filter(|node| can_read(permissions, peer, node.node))
.cloned()
.collect();
let edges = self
.edges
.iter()
.copied()
.filter(|edge| edge.is_readable_by(permissions, peer))
.collect();
let roots = permissions.filter_readable(peer, self.roots.iter().copied());
Self {
epoch: self.epoch,
nodes,
edges,
roots,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum DeltaOp {
CellSet { node: NodeId, payload: IpcValue },
SlotValue { node: NodeId, payload: IpcValue },
Invalidate { node: NodeId },
NodeAdd {
node: NodeId,
type_tag: String,
state: NodeState,
},
NodeRemove { node: NodeId },
EdgeAdd {
dependent: NodeId,
dependency: NodeId,
},
EdgeRemove {
dependent: NodeId,
dependency: NodeId,
},
}
impl DeltaOp {
pub fn cell_set(node: NodeId, payload: impl Into<IpcValue>) -> Self {
Self::CellSet {
node,
payload: payload.into(),
}
}
pub fn slot_value(node: NodeId, payload: impl Into<IpcValue>) -> Self {
Self::SlotValue {
node,
payload: payload.into(),
}
}
pub fn cell_set_blob(node: NodeId, blob: ShmBlobRef) -> Self {
Self::cell_set(node, blob)
}
pub fn slot_value_blob(node: NodeId, blob: ShmBlobRef) -> Self {
Self::slot_value(node, blob)
}
pub fn invalidate(node: NodeId) -> Self {
Self::Invalidate { node }
}
fn filter_readable(&self, permissions: &PeerPermissions, peer: PeerId) -> Option<Self> {
match self {
Self::CellSet { node, .. }
| Self::SlotValue { node, .. }
| Self::Invalidate { node }
| Self::NodeAdd { node, .. }
| Self::NodeRemove { node } => can_read(permissions, peer, *node).then(|| self.clone()),
Self::EdgeAdd {
dependent,
dependency,
}
| Self::EdgeRemove {
dependent,
dependency,
} => (can_read(permissions, peer, *dependent)
&& can_read(permissions, peer, *dependency))
.then(|| self.clone()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Delta {
pub base_epoch: u64,
pub epoch: u64,
pub ops: Vec<DeltaOp>,
}
impl Delta {
pub fn next(base_epoch: u64, ops: Vec<DeltaOp>) -> Self {
let epoch = base_epoch
.checked_add(1)
.expect("ipc epoch overflow requires a fresh snapshot/session");
Self {
base_epoch,
epoch,
ops,
}
}
pub fn new(base_epoch: u64, epoch: u64, ops: Vec<DeltaOp>) -> Self {
Self {
base_epoch,
epoch,
ops,
}
}
pub fn is_next_after(&self, last_epoch: u64) -> bool {
self.base_epoch == last_epoch
&& self
.base_epoch
.checked_add(1)
.is_some_and(|next| self.epoch == next)
}
pub fn apply_status(&self, last_epoch: u64) -> DeltaApplyStatus {
if self.is_next_after(last_epoch) {
DeltaApplyStatus::Apply
} else {
DeltaApplyStatus::ResyncRequired {
last_epoch,
base_epoch: self.base_epoch,
epoch: self.epoch,
}
}
}
pub fn filter_readable(&self, permissions: &PeerPermissions, peer: PeerId) -> Self {
let ops = self
.ops
.iter()
.filter_map(|op| op.filter_readable(permissions, peer))
.collect();
Self {
base_epoch: self.base_epoch,
epoch: self.epoch,
ops,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeltaApplyStatus {
Apply,
ResyncRequired {
last_epoch: u64,
base_epoch: u64,
epoch: u64,
},
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum IpcMessage {
Snapshot(Snapshot),
Delta(Delta),
}
pub trait IpcSink {
type Error;
fn send(&mut self, message: &IpcMessage) -> Result<(), Self::Error>;
fn send_snapshot(&mut self, snapshot: &Snapshot) -> Result<(), Self::Error> {
self.send(&IpcMessage::Snapshot(snapshot.clone()))
}
fn send_delta(&mut self, delta: &Delta) -> Result<(), Self::Error> {
self.send(&IpcMessage::Delta(delta.clone()))
}
}
pub trait IpcSource {
type Error;
fn recv(&mut self) -> Result<Option<IpcMessage>, Self::Error>;
}
fn can_read(permissions: &PeerPermissions, peer: PeerId, node: NodeId) -> bool {
permissions.is_allowed(peer, RemoteOp::read(node))
}
fn write_header(bytes: &mut [u8], offset: usize, descriptor: ShmBlobRef) {
let header = &mut bytes[offset..offset + SHM_BLOB_HEADER_LEN];
write_u32(header, 0, SHM_BLOB_MAGIC);
write_u16(header, 4, SHM_BLOB_VERSION);
write_u16(header, 6, SHM_BLOB_HEADER_LEN as u16);
write_u64(header, 8, descriptor.generation);
write_u64(header, 16, descriptor.epoch);
write_u64(header, 24, descriptor.len);
write_u64(header, 32, descriptor.checksum);
}
fn read_header(bytes: &[u8], offset: usize) -> Result<ShmBlobRef, ShmBlobArenaError> {
let header = &bytes[offset..offset + SHM_BLOB_HEADER_LEN];
let magic = read_u32(header, 0);
if magic != SHM_BLOB_MAGIC {
return Err(ShmBlobArenaError::DescriptorMismatch { field: "magic" });
}
let version = read_u16(header, 4);
if version != SHM_BLOB_VERSION {
return Err(ShmBlobArenaError::DescriptorMismatch { field: "version" });
}
let header_len = read_u16(header, 6);
if usize::from(header_len) != SHM_BLOB_HEADER_LEN {
return Err(ShmBlobArenaError::DescriptorMismatch {
field: "header_len",
});
}
Ok(ShmBlobRef {
offset: offset as u64,
generation: read_u64(header, 8),
epoch: read_u64(header, 16),
len: read_u64(header, 24),
checksum: read_u64(header, 32),
})
}
fn mismatch_field(actual: ShmBlobRef, expected: ShmBlobRef) -> ShmBlobArenaError {
let field = if actual.generation != expected.generation {
"generation"
} else if actual.epoch != expected.epoch {
"epoch"
} else if actual.len != expected.len {
"len"
} else if actual.checksum != expected.checksum {
"checksum"
} else {
"offset"
};
ShmBlobArenaError::DescriptorMismatch { field }
}
fn checksum(payload: &[u8]) -> u64 {
payload.iter().fold(FNV_OFFSET_BASIS, |hash, byte| {
(hash ^ u64::from(*byte)).wrapping_mul(FNV_PRIME)
})
}
fn write_u16(bytes: &mut [u8], offset: usize, value: u16) {
bytes[offset..offset + 2].copy_from_slice(&value.to_le_bytes());
}
fn write_u32(bytes: &mut [u8], offset: usize, value: u32) {
bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
}
fn write_u64(bytes: &mut [u8], offset: usize, value: u64) {
bytes[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
}
fn read_u16(bytes: &[u8], offset: usize) -> u16 {
u16::from_le_bytes(
bytes[offset..offset + 2]
.try_into()
.expect("slice size checked"),
)
}
fn read_u32(bytes: &[u8], offset: usize) -> u32 {
u32::from_le_bytes(
bytes[offset..offset + 4]
.try_into()
.expect("slice size checked"),
)
}
fn read_u64(bytes: &[u8], offset: usize) -> u64 {
u64::from_le_bytes(
bytes[offset..offset + 8]
.try_into()
.expect("slice size checked"),
)
}