use crate::distributed::{NodeId, PeerId, PeerPermissions, RemoteOp};
use std::collections::HashMap;
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, Default)]
#[repr(u8)]
pub enum BlobBackendKind {
#[default]
Shm,
Arrow,
InProcess,
}
impl BlobBackendKind {
pub const fn as_str(self) -> &'static str {
match self {
Self::Shm => "shm",
Self::Arrow => "arrow",
Self::InProcess => "in_process",
}
}
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Self {
match s {
"arrow" => Self::Arrow,
"in_process" => Self::InProcess,
_ => Self::Shm,
}
}
pub const fn is_default(&self) -> bool {
matches!(self, Self::Shm)
}
}
impl fmt::Display for BlobBackendKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl serde::Serialize for BlobBackendKind {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
impl<'de> serde::Deserialize<'de> for BlobBackendKind {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct V;
impl<'de> serde::de::Visitor<'de> for V {
type Value = BlobBackendKind;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a blob backend string (\"shm\" | \"arrow\" | \"in_process\")")
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
Ok(BlobBackendKind::from_str(v))
}
}
deserializer.deserialize_str(V)
}
}
#[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,
#[serde(default, skip_serializing_if = "BlobBackendKind::is_default")]
pub backend: BlobBackendKind,
}
#[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,
BackendIo {
detail: String,
},
}
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"),
Self::BackendIo { detail } => write!(f, "blob backend I/O error: {detail}"),
}
}
}
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,
backend: BlobBackendKind::Shm,
};
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 mut header = read_header(self.bytes.as_ref(), offset)?;
header.backend = descriptor.backend;
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)
}
}
pub const NODE_KEY_MAX_LEN: usize = 1024;
pub const NODE_KEY_MAX_SEGMENTS: usize = 32;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NodeKeyError {
Empty,
TooLong {
len: usize,
},
TooManySegments {
segments: usize,
},
EmptySegment,
}
impl fmt::Display for NodeKeyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => write!(f, "node key path is empty"),
Self::TooLong { len } => {
write!(
f,
"node key path is {len} bytes, exceeds {NODE_KEY_MAX_LEN}"
)
}
Self::TooManySegments { segments } => write!(
f,
"node key has {segments} segments, exceeds {NODE_KEY_MAX_SEGMENTS}"
),
Self::EmptySegment => write!(f, "node key path has an empty segment"),
}
}
}
impl std::error::Error for NodeKeyError {}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct NodeKey(String);
impl NodeKey {
pub fn new(path: impl Into<String>) -> Result<Self, NodeKeyError> {
let path = path.into();
Self::validate(&path)?;
Ok(Self(path))
}
pub fn from_segments<I, S>(segments: I) -> Result<Self, NodeKeyError>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let joined = segments
.into_iter()
.map(|s| s.as_ref().to_owned())
.collect::<Vec<_>>()
.join("/");
Self::new(joined)
}
fn validate(path: &str) -> Result<(), NodeKeyError> {
if path.is_empty() {
return Err(NodeKeyError::Empty);
}
if path.len() > NODE_KEY_MAX_LEN {
return Err(NodeKeyError::TooLong { len: path.len() });
}
let mut segments = 0usize;
for segment in path.split('/') {
if segment.is_empty() {
return Err(NodeKeyError::EmptySegment);
}
segments += 1;
}
if segments > NODE_KEY_MAX_SEGMENTS {
return Err(NodeKeyError::TooManySegments { segments });
}
Ok(())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn segments(&self) -> impl Iterator<Item = &str> {
self.0.split('/')
}
}
impl fmt::Display for NodeKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl TryFrom<String> for NodeKey {
type Error = NodeKeyError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl TryFrom<&str> for NodeKey {
type Error = NodeKeyError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl serde::Serialize for NodeKey {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.0)
}
}
impl<'de> serde::Deserialize<'de> for NodeKey {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let path = String::deserialize(deserializer)?;
Self::new(path).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum NodeState {
Payload(IpcPayload),
SharedBlob(ShmBlobRef),
Opaque,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NodeSnapshot {
pub node: NodeId,
pub type_tag: String,
pub state: NodeState,
pub key: Option<NodeKey>,
}
impl serde::Serialize for NodeSnapshot {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeStruct;
let emit_key = self.key.is_some() || !serializer.is_human_readable();
let mut st = serializer.serialize_struct("NodeSnapshot", 3 + emit_key as usize)?;
st.serialize_field("node", &self.node)?;
st.serialize_field("type_tag", &self.type_tag)?;
st.serialize_field("state", &self.state)?;
if emit_key {
st.serialize_field("key", &self.key)?;
}
st.end()
}
}
impl<'de> serde::Deserialize<'de> for NodeSnapshot {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
#[derive(serde::Deserialize)]
#[serde(rename = "NodeSnapshot")]
struct Raw {
node: NodeId,
type_tag: String,
state: NodeState,
#[serde(default)]
key: Option<NodeKey>,
}
let raw = Raw::deserialize(deserializer)?;
Ok(Self {
node: raw.node,
type_tag: raw.type_tag,
state: raw.state,
key: raw.key,
})
}
}
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),
key: None,
}
}
pub fn opaque(node: NodeId, type_tag: impl Into<String>) -> Self {
Self {
node,
type_tag: type_tag.into(),
state: NodeState::Opaque,
key: None,
}
}
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),
key: None,
}
}
pub fn with_key(mut self, key: NodeKey) -> Self {
self.key = Some(key);
self
}
}
#[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)]
pub enum DeltaOp {
CellSet { node: NodeId, payload: IpcValue },
SlotValue { node: NodeId, payload: IpcValue },
Invalidate { node: NodeId },
NodeAdd {
node: NodeId,
type_tag: String,
state: NodeState,
key: Option<NodeKey>,
},
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()),
}
}
}
fn delta_op_key_ref_is_none(key: &&Option<NodeKey>) -> bool {
key.is_none()
}
impl serde::Serialize for DeltaOp {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
#[derive(serde::Serialize)]
#[serde(rename = "DeltaOp")]
enum Hr<'a> {
CellSet {
node: &'a NodeId,
payload: &'a IpcValue,
},
SlotValue {
node: &'a NodeId,
payload: &'a IpcValue,
},
Invalidate {
node: &'a NodeId,
},
NodeAdd {
node: &'a NodeId,
type_tag: &'a String,
state: &'a NodeState,
#[serde(skip_serializing_if = "delta_op_key_ref_is_none")]
key: &'a Option<NodeKey>,
},
NodeRemove {
node: &'a NodeId,
},
EdgeAdd {
dependent: &'a NodeId,
dependency: &'a NodeId,
},
EdgeRemove {
dependent: &'a NodeId,
dependency: &'a NodeId,
},
}
#[derive(serde::Serialize)]
#[serde(rename = "DeltaOp")]
enum Bin<'a> {
CellSet {
node: &'a NodeId,
payload: &'a IpcValue,
},
SlotValue {
node: &'a NodeId,
payload: &'a IpcValue,
},
Invalidate {
node: &'a NodeId,
},
NodeAdd {
node: &'a NodeId,
type_tag: &'a String,
state: &'a NodeState,
key: &'a Option<NodeKey>,
},
NodeRemove {
node: &'a NodeId,
},
EdgeAdd {
dependent: &'a NodeId,
dependency: &'a NodeId,
},
EdgeRemove {
dependent: &'a NodeId,
dependency: &'a NodeId,
},
}
if serializer.is_human_readable() {
match self {
DeltaOp::CellSet { node, payload } => Hr::CellSet { node, payload },
DeltaOp::SlotValue { node, payload } => Hr::SlotValue { node, payload },
DeltaOp::Invalidate { node } => Hr::Invalidate { node },
DeltaOp::NodeAdd {
node,
type_tag,
state,
key,
} => Hr::NodeAdd {
node,
type_tag,
state,
key,
},
DeltaOp::NodeRemove { node } => Hr::NodeRemove { node },
DeltaOp::EdgeAdd {
dependent,
dependency,
} => Hr::EdgeAdd {
dependent,
dependency,
},
DeltaOp::EdgeRemove {
dependent,
dependency,
} => Hr::EdgeRemove {
dependent,
dependency,
},
}
.serialize(serializer)
} else {
match self {
DeltaOp::CellSet { node, payload } => Bin::CellSet { node, payload },
DeltaOp::SlotValue { node, payload } => Bin::SlotValue { node, payload },
DeltaOp::Invalidate { node } => Bin::Invalidate { node },
DeltaOp::NodeAdd {
node,
type_tag,
state,
key,
} => Bin::NodeAdd {
node,
type_tag,
state,
key,
},
DeltaOp::NodeRemove { node } => Bin::NodeRemove { node },
DeltaOp::EdgeAdd {
dependent,
dependency,
} => Bin::EdgeAdd {
dependent,
dependency,
},
DeltaOp::EdgeRemove {
dependent,
dependency,
} => Bin::EdgeRemove {
dependent,
dependency,
},
}
.serialize(serializer)
}
}
}
impl<'de> serde::Deserialize<'de> for DeltaOp {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
#[derive(serde::Deserialize)]
#[serde(rename = "DeltaOp")]
enum Wire {
CellSet {
node: NodeId,
payload: IpcValue,
},
SlotValue {
node: NodeId,
payload: IpcValue,
},
Invalidate {
node: NodeId,
},
NodeAdd {
node: NodeId,
type_tag: String,
state: NodeState,
#[serde(default)]
key: Option<NodeKey>,
},
NodeRemove {
node: NodeId,
},
EdgeAdd {
dependent: NodeId,
dependency: NodeId,
},
EdgeRemove {
dependent: NodeId,
dependency: NodeId,
},
}
Ok(match Wire::deserialize(deserializer)? {
Wire::CellSet { node, payload } => DeltaOp::CellSet { node, payload },
Wire::SlotValue { node, payload } => DeltaOp::SlotValue { node, payload },
Wire::Invalidate { node } => DeltaOp::Invalidate { node },
Wire::NodeAdd {
node,
type_tag,
state,
key,
} => DeltaOp::NodeAdd {
node,
type_tag,
state,
key,
},
Wire::NodeRemove { node } => DeltaOp::NodeRemove { node },
Wire::EdgeAdd {
dependent,
dependency,
} => DeltaOp::EdgeAdd {
dependent,
dependency,
},
Wire::EdgeRemove {
dependent,
dependency,
} => DeltaOp::EdgeRemove {
dependent,
dependency,
},
})
}
}
#[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 span(&self) -> u64 {
self.epoch.saturating_sub(self.base_epoch)
}
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, Default, PartialEq, Eq)]
pub struct KeyIndex {
forward: HashMap<NodeKey, NodeId>,
reverse: HashMap<NodeId, NodeKey>,
}
impl KeyIndex {
pub fn new() -> Self {
Self::default()
}
pub fn ingest_snapshot(&mut self, snapshot: &Snapshot) {
self.forward.clear();
self.reverse.clear();
for node in &snapshot.nodes {
if let Some(key) = &node.key {
self.insert(key.clone(), node.node);
}
}
}
pub fn apply_delta(&mut self, delta: &Delta) {
for op in &delta.ops {
match op {
DeltaOp::NodeAdd {
node,
key: Some(key),
..
} => self.insert(key.clone(), *node),
DeltaOp::NodeRemove { node } => self.remove_node(*node),
_ => {}
}
}
}
pub fn insert(&mut self, key: NodeKey, node: NodeId) {
if let Some(old_key) = self.reverse.insert(node, key.clone())
&& old_key != key
{
self.forward.remove(&old_key);
}
if let Some(old_node) = self.forward.insert(key, node)
&& old_node != node
{
self.reverse.remove(&old_node);
}
}
pub fn remove_node(&mut self, node: NodeId) {
if let Some(key) = self.reverse.remove(&node) {
self.forward.remove(&key);
}
}
pub fn node_for_key(&self, key: &NodeKey) -> Option<NodeId> {
self.forward.get(key).copied()
}
pub fn key_for_node(&self, node: NodeId) -> Option<&NodeKey> {
self.reverse.get(&node)
}
pub fn len(&self) -> usize {
self.forward.len()
}
pub fn is_empty(&self) -> bool {
self.forward.is_empty()
}
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
pub struct WireStamp {
pub wall_time: u64,
pub logical: u64,
pub peer: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct CrdtOp {
pub node: NodeId,
pub key: Option<NodeKey>,
pub stamp: WireStamp,
pub state: IpcValue,
}
impl CrdtOp {
pub fn new(node: NodeId, stamp: WireStamp, state: impl Into<IpcValue>) -> Self {
Self {
node,
key: None,
stamp,
state: state.into(),
}
}
pub fn keyed(node: NodeId, key: NodeKey, stamp: WireStamp, state: impl Into<IpcValue>) -> Self {
Self {
node,
key: Some(key),
stamp,
state: state.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct CrdtSync {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub frontier: Vec<(u64, WireStamp)>,
pub ops: Vec<CrdtOp>,
}
impl CrdtSync {
pub fn new(frontier: Vec<(u64, WireStamp)>, ops: Vec<CrdtOp>) -> Self {
Self { frontier, ops }
}
pub fn filter_readable(&self, permissions: &PeerPermissions, peer: PeerId) -> Self {
let ops = self
.ops
.iter()
.filter(|op| can_read(permissions, peer, op.node))
.cloned()
.collect();
Self {
frontier: self.frontier.clone(),
ops,
}
}
pub fn ops_only(ops: Vec<CrdtOp>) -> Self {
Self {
frontier: Vec::new(),
ops,
}
}
pub fn is_frontier_suppressed(&self) -> bool {
self.frontier.is_empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct ResyncRequest {
pub from_epoch: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct OutboxAck {
pub through_epoch: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum IpcMessage {
Snapshot(Snapshot),
Delta(Delta),
CrdtSync(CrdtSync),
ResyncRequest(ResyncRequest),
OutboxAck(OutboxAck),
}
impl IpcMessage {
pub fn is_control(&self) -> bool {
matches!(
self,
IpcMessage::ResyncRequest(_) | IpcMessage::OutboxAck(_)
)
}
}
#[cfg(any(
feature = "ffi",
feature = "webrtc",
feature = "ipc-binary",
feature = "ipc-msgpack"
))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IpcCodec {
#[cfg(any(feature = "ffi", feature = "webrtc"))]
Json,
#[cfg(feature = "ipc-msgpack")]
MessagePack,
#[cfg(feature = "ipc-binary")]
Postcard,
}
#[cfg(any(feature = "ffi", feature = "webrtc"))]
#[allow(clippy::derivable_impls)]
impl Default for IpcCodec {
fn default() -> Self {
Self::Json
}
}
#[cfg(all(not(any(feature = "ffi", feature = "webrtc")), feature = "ipc-msgpack"))]
impl Default for IpcCodec {
fn default() -> Self {
Self::MessagePack
}
}
#[cfg(all(
not(any(feature = "ffi", feature = "webrtc", feature = "ipc-msgpack")),
feature = "ipc-binary"
))]
impl Default for IpcCodec {
fn default() -> Self {
Self::Postcard
}
}
#[cfg(any(
feature = "ffi",
feature = "webrtc",
feature = "ipc-binary",
feature = "ipc-msgpack"
))]
impl IpcCodec {
pub const fn name(self) -> &'static str {
match self {
#[cfg(any(feature = "ffi", feature = "webrtc"))]
Self::Json => "json",
#[cfg(feature = "ipc-msgpack")]
Self::MessagePack => "msgpack",
#[cfg(feature = "ipc-binary")]
Self::Postcard => "postcard",
}
}
pub fn encode(self, message: &IpcMessage) -> Result<Vec<u8>, EncodeError> {
match self {
#[cfg(any(feature = "ffi", feature = "webrtc"))]
Self::Json => message.encode_json(),
#[cfg(feature = "ipc-msgpack")]
Self::MessagePack => message.encode_msgpack(),
#[cfg(feature = "ipc-binary")]
Self::Postcard => message.encode_binary(),
}
}
pub fn decode(self, bytes: &[u8]) -> Result<IpcMessage, DecodeError> {
match self {
#[cfg(any(feature = "ffi", feature = "webrtc"))]
Self::Json => IpcMessage::decode_json(bytes),
#[cfg(feature = "ipc-msgpack")]
Self::MessagePack => IpcMessage::decode_msgpack(bytes),
#[cfg(feature = "ipc-binary")]
Self::Postcard => IpcMessage::decode_binary(bytes),
}
}
}
pub const PROTOCOL_ID: &str = "lazily-ipc";
pub const PROTOCOL_MAJOR_VERSION: u64 = 1;
fn default_ordered_reliable() -> bool {
true
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct CapabilityHandshake {
pub protocol_id: String,
pub protocol_major_version: u64,
pub codec: String,
pub max_frame_size: u64,
#[serde(default)]
pub fragmentation_supported: bool,
#[serde(default = "default_ordered_reliable")]
pub ordered_reliable: bool,
pub peer_id: PeerId,
pub session_id: String,
#[serde(default)]
pub features: Vec<String>,
}
impl CapabilityHandshake {
pub fn new(peer_id: PeerId, session_id: impl Into<String>) -> Self {
Self {
protocol_id: PROTOCOL_ID.to_owned(),
protocol_major_version: PROTOCOL_MAJOR_VERSION,
codec: "json".to_owned(),
max_frame_size: 1_048_576,
fragmentation_supported: false,
ordered_reliable: true,
peer_id,
session_id: session_id.into(),
features: Vec::new(),
}
}
#[must_use]
pub fn with_codec(mut self, codec: impl Into<String>) -> Self {
self.codec = codec.into();
self
}
#[must_use]
pub fn with_max_frame_size(mut self, max_frame_size: u64) -> Self {
self.max_frame_size = max_frame_size;
self
}
#[must_use]
pub fn with_features(mut self, features: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.features = features.into_iter().map(Into::into).collect();
self
}
#[must_use]
pub fn with_fragmentation(mut self, supported: bool) -> Self {
self.fragmentation_supported = supported;
self
}
#[must_use]
pub fn is_compatible_with(&self, other: &Self) -> bool {
self.protocol_id == PROTOCOL_ID
&& other.protocol_id == PROTOCOL_ID
&& self.protocol_major_version == PROTOCOL_MAJOR_VERSION
&& other.protocol_major_version == PROTOCOL_MAJOR_VERSION
&& self.protocol_major_version == other.protocol_major_version
&& self.codec == other.codec
&& self.ordered_reliable
&& other.ordered_reliable
}
#[must_use]
pub fn has_feature(&self, feature: &str) -> bool {
self.features.iter().any(|f| f == feature)
}
}
impl IpcMessage {
#[cfg(any(feature = "ffi", feature = "webrtc"))]
pub fn encode_json(&self) -> Result<Vec<u8>, EncodeError> {
serde_json::to_vec(self).map_err(EncodeError::Json)
}
#[cfg(any(feature = "ffi", feature = "webrtc"))]
pub fn decode_json(bytes: &[u8]) -> Result<Self, DecodeError> {
serde_json::from_slice(bytes).map_err(DecodeError::Json)
}
#[cfg(feature = "json-base64")]
pub fn encode_json_base64(&self) -> Result<Vec<u8>, EncodeError> {
let mut value = serde_json::to_value(self).map_err(EncodeError::Json)?;
base64_transform::encode_byte_arrays(&mut value);
serde_json::to_vec(&value).map_err(EncodeError::Json)
}
#[cfg(feature = "json-base64")]
pub fn decode_json_base64(bytes: &[u8]) -> Result<Self, DecodeError> {
let mut value: serde_json::Value =
serde_json::from_slice(bytes).map_err(DecodeError::Json)?;
base64_transform::decode_byte_arrays(&mut value);
serde_json::from_value(value).map_err(DecodeError::Json)
}
#[cfg(any(feature = "ffi", feature = "webrtc"))]
pub fn encode_json_intern(&self) -> Result<Vec<u8>, EncodeError> {
let mut value = serde_json::to_value(self).map_err(EncodeError::Json)?;
intern_transform::encode_intern(&mut value);
serde_json::to_vec(&value).map_err(EncodeError::Json)
}
#[cfg(any(feature = "ffi", feature = "webrtc"))]
pub fn decode_json_intern(bytes: &[u8]) -> Result<Self, DecodeError> {
let mut value: serde_json::Value =
serde_json::from_slice(bytes).map_err(DecodeError::Json)?;
intern_transform::decode_intern(&mut value);
serde_json::from_value(value).map_err(DecodeError::Json)
}
#[cfg(feature = "ipc-msgpack")]
pub fn encode_msgpack(&self) -> Result<Vec<u8>, EncodeError> {
rmp_serde::to_vec_named(self).map_err(EncodeError::Msgpack)
}
#[cfg(feature = "ipc-msgpack")]
pub fn decode_msgpack(bytes: &[u8]) -> Result<Self, DecodeError> {
rmp_serde::from_slice(bytes).map_err(DecodeError::Msgpack)
}
#[cfg(feature = "ipc-binary")]
pub fn encode_binary(&self) -> Result<Vec<u8>, EncodeError> {
postcard::to_allocvec(self).map_err(EncodeError::Binary)
}
#[cfg(feature = "ipc-binary")]
pub fn decode_binary(bytes: &[u8]) -> Result<Self, DecodeError> {
postcard::from_bytes(bytes).map_err(DecodeError::Binary)
}
}
#[cfg(any(
feature = "ffi",
feature = "webrtc",
feature = "ipc-binary",
feature = "ipc-msgpack"
))]
#[derive(Debug)]
pub enum EncodeError {
#[cfg(any(feature = "ffi", feature = "webrtc"))]
Json(serde_json::Error),
#[cfg(feature = "ipc-msgpack")]
Msgpack(rmp_serde::encode::Error),
#[cfg(feature = "ipc-binary")]
Binary(postcard::Error),
}
#[cfg(any(
feature = "ffi",
feature = "webrtc",
feature = "ipc-binary",
feature = "ipc-msgpack"
))]
impl fmt::Display for EncodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
#[cfg(any(feature = "ffi", feature = "webrtc"))]
Self::Json(e) => write!(f, "JSON encode: {e}"),
#[cfg(feature = "ipc-msgpack")]
Self::Msgpack(e) => write!(f, "MessagePack encode: {e}"),
#[cfg(feature = "ipc-binary")]
Self::Binary(e) => write!(f, "binary encode: {e}"),
}
}
}
#[cfg(any(
feature = "ffi",
feature = "webrtc",
feature = "ipc-binary",
feature = "ipc-msgpack"
))]
impl std::error::Error for EncodeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
#[cfg(any(feature = "ffi", feature = "webrtc"))]
Self::Json(e) => Some(e),
#[cfg(feature = "ipc-msgpack")]
Self::Msgpack(e) => Some(e),
#[cfg(feature = "ipc-binary")]
Self::Binary(e) => Some(e),
}
}
}
#[cfg(any(feature = "ffi", feature = "webrtc"))]
impl From<serde_json::Error> for EncodeError {
fn from(e: serde_json::Error) -> Self {
Self::Json(e)
}
}
#[cfg(feature = "ipc-msgpack")]
impl From<rmp_serde::encode::Error> for EncodeError {
fn from(e: rmp_serde::encode::Error) -> Self {
Self::Msgpack(e)
}
}
#[cfg(feature = "ipc-binary")]
impl From<postcard::Error> for EncodeError {
fn from(e: postcard::Error) -> Self {
Self::Binary(e)
}
}
#[cfg(any(
feature = "ffi",
feature = "webrtc",
feature = "ipc-binary",
feature = "ipc-msgpack"
))]
#[derive(Debug)]
pub enum DecodeError {
#[cfg(any(feature = "ffi", feature = "webrtc"))]
Json(serde_json::Error),
#[cfg(feature = "ipc-msgpack")]
Msgpack(rmp_serde::decode::Error),
#[cfg(feature = "ipc-binary")]
Binary(postcard::Error),
}
#[cfg(any(
feature = "ffi",
feature = "webrtc",
feature = "ipc-binary",
feature = "ipc-msgpack"
))]
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
#[cfg(any(feature = "ffi", feature = "webrtc"))]
Self::Json(e) => write!(f, "JSON decode: {e}"),
#[cfg(feature = "ipc-msgpack")]
Self::Msgpack(e) => write!(f, "MessagePack decode: {e}"),
#[cfg(feature = "ipc-binary")]
Self::Binary(e) => write!(f, "binary decode: {e}"),
}
}
}
#[cfg(any(
feature = "ffi",
feature = "webrtc",
feature = "ipc-binary",
feature = "ipc-msgpack"
))]
impl std::error::Error for DecodeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
#[cfg(any(feature = "ffi", feature = "webrtc"))]
Self::Json(e) => Some(e),
#[cfg(feature = "ipc-msgpack")]
Self::Msgpack(e) => Some(e),
#[cfg(feature = "ipc-binary")]
Self::Binary(e) => Some(e),
}
}
}
#[cfg(any(feature = "ffi", feature = "webrtc"))]
impl From<serde_json::Error> for DecodeError {
fn from(e: serde_json::Error) -> Self {
Self::Json(e)
}
}
#[cfg(feature = "ipc-msgpack")]
impl From<rmp_serde::decode::Error> for DecodeError {
fn from(e: rmp_serde::decode::Error) -> Self {
Self::Msgpack(e)
}
}
#[cfg(feature = "ipc-binary")]
impl From<postcard::Error> for DecodeError {
fn from(e: postcard::Error) -> Self {
Self::Binary(e)
}
}
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()))
}
fn send_crdt_sync(&mut self, sync: &CrdtSync) -> Result<(), Self::Error> {
self.send(&IpcMessage::CrdtSync(sync.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),
backend: BlobBackendKind::Shm,
})
}
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"),
)
}
#[cfg(feature = "json-base64")]
mod base64_transform {
use base64::{Engine as _, engine::general_purpose};
use serde_json::Value;
const BYTE_FIELDS: [&str; 2] = ["Inline", "Payload"];
pub fn encode_byte_arrays(value: &mut Value) {
match value {
Value::Object(map) => {
for field in BYTE_FIELDS {
let replacement = map.get(field).filter(|v| v.is_array()).map(|arr| {
let bytes = json_array_to_bytes(arr);
Value::String(general_purpose::STANDARD.encode(&bytes))
});
if let Some(encoded) = replacement {
map.insert((*field).to_string(), encoded);
}
}
for (_, v) in map.iter_mut() {
encode_byte_arrays(v);
}
}
Value::Array(items) => {
for item in items.iter_mut() {
encode_byte_arrays(item);
}
}
_ => {}
}
}
pub fn decode_byte_arrays(value: &mut Value) {
match value {
Value::Object(map) => {
for field in BYTE_FIELDS {
let replacement = map
.get(field)
.and_then(|s| s.as_str())
.and_then(|text| general_purpose::STANDARD.decode(text).ok())
.map(|bytes| {
Value::Array(
bytes
.into_iter()
.map(|b| Value::from(u64::from(b)))
.collect(),
)
});
if let Some(arr) = replacement {
map.insert((*field).to_string(), arr);
}
}
for (_, v) in map.iter_mut() {
decode_byte_arrays(v);
}
}
Value::Array(items) => {
for item in items.iter_mut() {
decode_byte_arrays(item);
}
}
_ => {}
}
}
fn json_array_to_bytes(value: &Value) -> Vec<u8> {
value
.as_array()
.expect("checked array")
.iter()
.map(|n| n.as_u64().unwrap_or(0) as u8)
.collect()
}
}
#[cfg(any(feature = "ffi", feature = "webrtc"))]
mod intern_transform {
use std::collections::HashMap;
use serde_json::{Map, Value};
const TYPE_TAG: &str = "type_tag";
const TYPE_TAG_ID: &str = "type_tag_id";
const INTERN: &str = "intern";
pub fn encode_intern(value: &mut Value) {
let Some(root) = value.as_object_mut() else {
return;
};
let Some(batch) = root.values_mut().next() else {
return;
};
let Some(batch_map) = batch.as_object_mut() else {
return;
};
let mut table: Vec<String> = Vec::new();
let mut index: HashMap<String, usize> = HashMap::new();
walk_encode(batch_map, &mut table, &mut index);
if !table.is_empty() {
let strings: Vec<Value> = table.into_iter().map(Value::String).collect();
let mut intern = Map::new();
intern.insert("strings".to_string(), Value::Array(strings));
batch_map.insert(INTERN.to_string(), Value::Object(intern));
}
}
pub fn decode_intern(value: &mut Value) {
let Some(root) = value.as_object_mut() else {
return;
};
let Some(batch) = root.values_mut().next() else {
return;
};
let Some(batch_map) = batch.as_object_mut() else {
return;
};
let strings: Vec<String> = batch_map
.remove(INTERN)
.and_then(|v| v.get("strings").cloned())
.and_then(|s| s.as_array().map(|a| a.to_vec()))
.map(|arr| {
arr.into_iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
.unwrap_or_default();
if !strings.is_empty() {
walk_decode(batch_map, &strings);
}
}
fn walk_encode(
map: &mut Map<String, Value>,
table: &mut Vec<String>,
index: &mut HashMap<String, usize>,
) {
if let Some(tag) = map.get(TYPE_TAG).and_then(|v| v.as_str()).map(String::from) {
let id = *index.entry(tag).or_insert_with(|| {
let id = table.len();
table.push(map.get(TYPE_TAG).unwrap().as_str().unwrap().to_string());
id
});
map.remove(TYPE_TAG);
map.insert(TYPE_TAG_ID.to_string(), Value::from(id));
}
for (_, v) in map.iter_mut() {
if let Some(child) = v.as_object_mut() {
walk_encode(child, table, index);
} else if let Some(items) = v.as_array_mut() {
for item in items.iter_mut() {
if let Some(child) = item.as_object_mut() {
walk_encode(child, table, index);
}
}
}
}
}
fn walk_decode(map: &mut Map<String, Value>, strings: &[String]) {
if let Some(tag) = map
.remove(TYPE_TAG_ID)
.and_then(|v| v.as_u64())
.and_then(|id| strings.get(id as usize))
{
map.insert(TYPE_TAG.to_string(), Value::String(tag.clone()));
}
for (_, v) in map.iter_mut() {
if let Some(child) = v.as_object_mut() {
walk_decode(child, strings);
} else if let Some(items) = v.as_array_mut() {
for item in items.iter_mut() {
if let Some(child) = item.as_object_mut() {
walk_decode(child, strings);
}
}
}
}
}
}