clone

a self-hosted sync node for lofi apps: jazz sync over iroh, ticket-gated, with no static ip or cloud needed

feat/browser-cors-sync-tickets
lofi-node / native / iroh-js / src / lofi_ext.rs
lofi_ext.rs 2.4K
1//! lofi-node extension module — NOT upstream code (everything else in src/ is2//! vendored from n0-computer/iroh-ffi iroh-js; see UPSTREAM.md).3//!4//! Buffer-based length-prefixed framing over QUIC streams. Upstream stream I/O5//! moves bytes as `Vec<u8>` ⇄ JS `Array<number>` (measured 7.1 MiB/s loopback6//! round-trip at gate 0) and needs two awaited hops per length-prefixed7//! message. These free functions use napi `Buffer` (zero-copy at the boundary)8//! and one await per frame: u32-BE length prefix + payload.9//!10//! Deletes if upstream grows Buffer-based stream I/O.1112use napi::bindgen_prelude::*;13use napi_derive::napi;1415use crate::endpoint::{RecvStream, SendStream};1617/// Hard frame cap: sized for Jazz sync payloads, not inherited from anywhere.18pub const MAX_FRAME: u32 = 16 * 1024 * 1024;1920/// Returns the frame cap so the TS side never hardcodes it.21#[napi]22pub fn max_frame() -> u32 {23 MAX_FRAME24}2526/// Write one framed message: u32-BE length prefix, then the payload.27#[napi]28pub async fn write_frame(stream: &SendStream, data: Buffer) -> Result<()> {29 let bytes: &[u8] = &data;30 if bytes.len() > MAX_FRAME as usize {31 return Err(anyhow::anyhow!("frame of {} bytes exceeds MAX_FRAME ({MAX_FRAME})", bytes.len()).into());32 }33 let mut s = stream.0.lock().await;34 s.write_all(&(bytes.len() as u32).to_be_bytes())35 .await36 .map_err(|e| anyhow::anyhow!("{e:?}"))?;37 s.write_all(bytes).await.map_err(|e| anyhow::anyhow!("{e:?}"))?;38 Ok(())39}4041/// Read one framed message. `None` when the stream ends (finish/reset/close)42/// at a frame boundary — the TS side's end-of-stream contract. A stream that43/// dies MID-frame is an error, not `None`.44#[napi]45pub async fn read_frame(stream: &RecvStream, max: Option<u32>) -> Result<Option<Buffer>> {46 let max = max.unwrap_or(MAX_FRAME).min(MAX_FRAME);47 let mut r = stream.0.lock().await;48 let mut len_buf = [0u8; 4];49 if r.read_exact(&mut len_buf).await.is_err() {50 // Ended before a new frame started: clean end-of-stream.51 return Ok(None);52 }53 let len = u32::from_be_bytes(len_buf);54 if len > max {55 return Err(anyhow::anyhow!("incoming frame of {len} bytes exceeds cap ({max})").into());56 }57 let mut buf = vec![0u8; len as usize];58 r.read_exact(&mut buf)59 .await60 .map_err(|e| anyhow::anyhow!("stream ended mid-frame: {e:?}"))?;61 Ok(Some(buf.into()))62}