use std::cell::RefCell;
use std::collections::{HashMap, VecDeque};
use std::rc::Rc;
use crate::concurrency::marshal::{self, WireSchemaCache, WireTypeRegistry};
use crate::interpreter::{ListRepr, RuntimeValue};
pub(crate) enum RecvSlot {
Decoded(RuntimeValue),
RawRecordList(Rc<Vec<u8>>),
Stream(Rc<Vec<u8>>),
}
pub(crate) struct NetInbox {
pub net: Option<logicaffeine_system::net::Net>,
pub inbox: Option<Rc<String>>,
pub received: VecDeque<(String, RecvSlot)>,
pub send_schema: HashMap<String, WireSchemaCache>,
pub recv_schema: WireSchemaCache,
pub send_msg_id: u64,
pub recv_shards: HashMap<u64, Vec<Vec<u8>>>,
pub my_profile: marshal::PeerProfile,
pub peer_profiles: HashMap<String, marshal::PeerProfile>,
pub handshaked: std::collections::HashSet<String>,
pub my_registry: marshal::WireTypeRegistry,
pub sync_versions: HashMap<String, logicaffeine_data::crdt::VClock>,
pub offline_loopback: VecDeque<(String, Vec<u8>)>,
}
pub fn handshake_topic_for(data_topic: &str) -> String {
format!("{data_topic}#hs")
}
thread_local! {
static NET_OFFLINE: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
pub fn set_net_offline(offline: bool) {
NET_OFFLINE.with(|c| c.set(offline));
}
pub fn net_is_offline() -> bool {
NET_OFFLINE.with(|c| c.get())
}
impl NetInbox {
pub fn new() -> Self {
NetInbox {
net: None,
inbox: None,
received: VecDeque::new(),
send_schema: HashMap::new(),
recv_schema: WireSchemaCache::content_addressed(),
send_msg_id: 0,
recv_shards: HashMap::new(),
my_profile: marshal::PeerProfile::default(),
peer_profiles: HashMap::new(),
handshaked: std::collections::HashSet::new(),
my_registry: WireTypeRegistry::new(Vec::new()),
sync_versions: HashMap::new(),
offline_loopback: VecDeque::new(),
}
}
pub fn set_registry(&mut self, registry: marshal::WireTypeRegistry) {
self.my_profile.registry_epoch = registry.epoch();
self.my_registry = registry;
}
pub fn first_contact_handshake(&mut self, peer: &str) -> Option<Vec<u8>> {
if self.handshaked.insert(peer.to_string()) {
Some(self.my_handshake())
} else {
None
}
}
pub fn my_handshake(&self) -> Vec<u8> {
let from = self.inbox.as_ref().map(|s| s.as_str()).unwrap_or("");
marshal::make_handshake_frame(from, &self.my_profile)
}
pub fn peer_profile(&self, peer: &str) -> marshal::PeerProfile {
self.peer_profiles.get(peer).copied().unwrap_or_else(marshal::PeerProfile::conservative)
}
pub fn negotiation_for(&self, peer: &str) -> marshal::Negotiated {
marshal::negotiate(&self.my_profile, &self.peer_profile(peer))
}
pub fn encode_negotiated(
&self,
from: &str,
value: &RuntimeValue,
dest: &str,
registry: marshal::WireTypeRegistry,
) -> Result<Vec<u8>, String> {
marshal::message_to_wire_negotiated(from, value, &self.negotiation_for(dest), registry)
}
pub fn next_msg_id(&mut self) -> u64 {
let id = self.send_msg_id;
self.send_msg_id = self.send_msg_id.wrapping_add(1);
id
}
pub fn publish(&mut self, topic: &str, bytes: Vec<u8>) -> Result<(), String> {
match self.net.as_ref() {
Some(net) => net.publish(topic, bytes),
None => {
self.offline_loopback.push_back((topic.to_string(), bytes));
Ok(())
}
}
}
pub fn drain(&mut self, registry: WireTypeRegistry) {
let Some(inbox) = self.inbox.clone() else { return };
let drained: Vec<(String, Vec<u8>)> = match self.net.as_mut() {
Some(net) => net.drain(),
None => self.offline_loopback.drain(..).collect(),
};
let my_handshake_topic = handshake_topic_for(&inbox);
marshal::with_type_registry(registry, || {
for (topic, data) in drained {
if topic.as_str() == my_handshake_topic.as_str() {
if let Some((from, profile)) = marshal::parse_handshake_frame(&data) {
self.peer_profiles.insert(from, profile);
}
continue;
}
if topic.as_str() != inbox.as_str() {
continue;
}
if let Some((msg_id, k, _n)) = crate::concurrency::fec::shard_header(&data) {
let buf = self.recv_shards.entry(msg_id).or_default();
buf.push(data);
let recovered = if buf.len() >= k {
crate::concurrency::fec::reconstruct_redundant(buf)
} else {
None
};
if let Some((_, payload)) = recovered {
self.recv_shards.remove(&msg_id);
self.enqueue_received(payload);
}
} else {
self.enqueue_received(data);
}
}
});
}
fn enqueue_received(&mut self, data: Vec<u8>) {
let Some(data) = crate::concurrency::channel::open_active(data) else { return };
if let Some((from, profile)) = marshal::parse_handshake_frame(&data) {
self.peer_profiles.insert(from, profile);
return;
}
if let Some(sender) = marshal::peek_stream_sender(&data) {
self.received.push_back((sender, RecvSlot::Stream(Rc::new(data))));
} else if let Some(sender) = marshal::peek_deferrable_sender(&data) {
self.received.push_back((sender, RecvSlot::RawRecordList(Rc::new(data))));
} else if let Some((from, value)) =
marshal::message_from_wire_cached(&data, &mut self.recv_schema)
{
self.received.push_back((from, RecvSlot::Decoded(value)));
}
}
pub fn try_take_message(&mut self, want: &str, view: bool) -> Option<RuntimeValue> {
let pos = self
.received
.iter()
.position(|(from, slot)| from == want && !matches!(slot, RecvSlot::Stream(_)))?;
Some(self.take_received(pos, view))
}
pub fn try_take_stream(&mut self, want: &str) -> Option<RuntimeValue> {
let pos = self
.received
.iter()
.position(|(from, slot)| from == want && matches!(slot, RecvSlot::Stream(_)))?;
Some(self.take_stream(pos))
}
fn take_received(&mut self, pos: usize, view: bool) -> RuntimeValue {
let (_, slot) = self.received.remove(pos).unwrap();
match slot {
RecvSlot::Decoded(value) => value,
RecvSlot::RawRecordList(bytes) => {
if view {
ListRepr::from_received_view(bytes)
.map(|l| RuntimeValue::List(Rc::new(RefCell::new(l))))
.unwrap_or(RuntimeValue::Nothing)
} else {
marshal::message_from_wire(&bytes)
.map(|(_, v)| v)
.unwrap_or(RuntimeValue::Nothing)
}
}
RecvSlot::Stream(bytes) => RuntimeValue::List(Rc::new(RefCell::new(ListRepr::from_values(
marshal::deframe_stream_message(&bytes).unwrap_or_default(),
)))),
}
}
fn take_stream(&mut self, pos: usize) -> RuntimeValue {
let (_, slot) = self.received.remove(pos).unwrap();
let values = match slot {
RecvSlot::Stream(bytes) => marshal::deframe_stream_message(&bytes).unwrap_or_default(),
_ => Vec::new(),
};
RuntimeValue::List(Rc::new(RefCell::new(ListRepr::from_values(values))))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::concurrency::marshal::{
make_handshake_frame, PeerProfile, ReceiveLimits, WireCompression, FEAT_COMPUTED, FEAT_LZ4,
FEAT_TYPE_ID, FEAT_ZSTD,
};
#[test]
fn absorbs_a_peer_handshake_and_negotiates_to_its_exposed_surface() {
let mut inbox = NetInbox::new();
inbox.my_profile.registry_epoch = 5;
inbox.my_profile.features = FEAT_ZSTD | FEAT_LZ4 | FEAT_TYPE_ID | FEAT_COMPUTED;
assert_eq!(inbox.peer_profile("bob"), PeerProfile::conservative());
let cold = inbox.negotiation_for("bob");
assert!(!cold.use_type_id && !cold.may_send_computed);
assert_eq!(cold.compression, WireCompression::None);
let bob = PeerProfile {
limits: ReceiveLimits { max_bytes: 2048, accept_computed: false, ..Default::default() },
registry_epoch: 5,
features: FEAT_LZ4 | FEAT_TYPE_ID,
};
inbox.enqueue_received(make_handshake_frame("bob", &bob));
assert!(inbox.received.is_empty(), "a handshake is absorbed, never delivered as data");
assert_eq!(inbox.peer_profile("bob"), bob);
let n = inbox.negotiation_for("bob");
assert!(n.use_type_id, "matching epochs + both type-id → names elided");
assert!(!n.may_send_computed, "bob declined computed → never ship code to him");
assert_eq!(n.compression, WireCompression::Lz4, "lz4 is the strongest compression both speak");
assert_eq!(n.peer_max_bytes, 2048, "stay under bob's byte budget");
}
#[test]
fn null_suite_seal_engages_through_enqueue_and_off_is_byte_identical() {
use crate::concurrency::channel::{seal_active, with_suite, SUITE_NULL};
use crate::concurrency::marshal::{message_to_wire_with, WireCodec, WireIntegrity};
let blob =
message_to_wire_with("bob", &RuntimeValue::Int(7), WireCodec::Native, WireIntegrity::Checked)
.expect("encode");
let mut off = NetInbox::new();
let wire_off = seal_active(blob.clone());
assert_eq!(wire_off, blob, "no suite → byte-identical wire");
off.enqueue_received(wire_off);
assert_eq!(off.received.len(), 1, "message delivered with the channel disengaged");
with_suite(Some(SUITE_NULL), || {
let mut on = NetInbox::new();
let wire_on = seal_active(blob.clone());
assert_ne!(wire_on, blob, "active suite → framed envelope on the wire");
on.enqueue_received(wire_on);
assert_eq!(on.received.len(), 1, "sealed message opened + delivered under the null suite");
let mut tampered = seal_active(blob.clone());
let last = tampered.len() - 1;
tampered[last] ^= 0xFF;
on.enqueue_received(tampered);
assert_eq!(on.received.len(), 1, "tampered frame dropped, not delivered");
});
}
#[test]
fn my_handshake_advertises_my_inbox_identity_and_profile() {
let mut inbox = NetInbox::new();
inbox.inbox = Some(Rc::new("alice".to_string()));
inbox.my_profile.registry_epoch = 9;
let frame = inbox.my_handshake();
let (from, prof) = marshal::parse_handshake_frame(&frame).expect("my handshake parses");
assert_eq!(from, "alice", "advertises our inbox identity");
assert_eq!(prof, inbox.my_profile, "advertises our exact profile");
}
}