use crate::control::{
append_frame, frame_size, peek_control_block, ControlHeader, DecodedControl, Target, CTRL_HEADER_SIZE,
};
use crate::region::Regions;
use crate::{ClientVerb, WireFlags, WireStatus};
use std::num::NonZeroU64;
use std::ops::Range;
pub(crate) const SCAN_MULTI_MAX_RELATIONS: usize = 16;
pub const BLIND: u64 = u64::MAX;
pub struct FrameItem<'a> {
pub hdr: ControlHeader,
pub blob: &'a [u8],
pub schema: Option<&'a [u8]>,
pub data: Option<Regions<'a>>,
}
pub fn encode_items(verb: ClientVerb, items: &[FrameItem<'_>]) -> Vec<u8> {
encode_items_under(verb, 0, items)
}
fn encode_items_under(verb: ClientVerb, arg0: u64, items: &[FrameItem<'_>]) -> Vec<u8> {
let size = CTRL_HEADER_SIZE
+ items
.iter()
.map(|it| frame_size(it.blob, it.schema, it.data.as_deref()))
.sum::<usize>();
let mut out = Vec::with_capacity(size);
let prologue = ControlHeader {
flags: WireFlags { verb, ..Default::default() },
arg0,
..Default::default()
};
append_frame(&mut out, &prologue, &[], None, None);
for it in items {
let mut hdr = it.hdr;
hdr.flags.verb = verb;
append_frame(&mut out, &hdr, it.blob, it.schema, it.data.as_deref());
}
debug_assert_eq!(out.len(), size);
out
}
#[derive(Clone, Copy)]
pub(crate) struct ItemShape {
pub(crate) blob: bool,
pub(crate) schema: bool,
pub(crate) data: bool,
pub(crate) cap: usize,
}
pub(crate) const fn item_shape(verb: ClientVerb) -> Option<ItemShape> {
let (blob, schema, data, cap) = match verb {
ClientVerb::DdlTxn => (false, false, true, usize::MAX),
ClientVerb::PushTxn => (false, true, true, usize::MAX),
ClientVerb::ScanMulti => (true, false, false, SCAN_MULTI_MAX_RELATIONS),
ClientVerb::DeltaPoll => (true, false, false, usize::MAX),
_ => return None,
};
Some(ItemShape { blob, schema, data, cap })
}
pub fn decode_items(body: &[u8], verb: ClientVerb) -> Result<Vec<(&[u8], DecodedControl)>, String> {
let Some(ItemShape { blob, schema, data, cap }) = item_shape(verb) else {
return Err(format!("{verb:?} is not a multi-item verb"));
};
let item = |rest: &[u8]| -> Result<DecodedControl, String> {
let ctrl = peek_control_block(rest)?;
if ctrl.hdr.status != WireStatus::Ok {
return Err(format!("an item carries status {:?}", ctrl.hdr.status));
}
if ctrl.hdr.flags.verb != verb {
return Err(format!("an item names verb {:?}", ctrl.hdr.flags.verb));
}
if !ctrl.blob.is_empty() && !blob {
return Err("an item carries a blob".into());
}
if ctrl.schema.is_some() != schema {
return Err(format!(
"an item {} a schema record",
if schema { "lacks" } else { "carries" }
));
}
if ctrl.data.is_some() != data {
return Err(format!(
"an item {} a data block",
if data { "lacks" } else { "carries" }
));
}
Ok(ctrl)
};
if body.is_empty() {
return Err(format!("{verb:?}: empty item list"));
}
let mut rest = body;
let mut out = Vec::new();
while !rest.is_empty() {
if out.len() == cap {
return Err(format!("{verb:?}: too many items (max {cap})"));
}
let mut ctrl = item(rest).map_err(|e| format!("{verb:?} item {}: {e}", out.len()))?;
let end = ctrl.body.start;
ctrl.body = end..end;
out.push((&rest[..end], ctrl));
rest = &rest[end..];
}
Ok(out)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ScanItem<'a> {
pub target: Target,
pub reply_layout: u64,
pub spec: &'a [u8],
}
pub fn encode_scan_multi(scans: &[ScanItem<'_>]) -> Vec<u8> {
let items: Vec<FrameItem> = scans
.iter()
.map(|s| FrameItem {
hdr: ControlHeader::naming(ClientVerb::ScanMulti, s.target, s.reply_layout),
blob: s.spec,
schema: None,
data: None,
})
.collect();
encode_items(ClientVerb::ScanMulti, &items)
}
pub fn decode_scan_multi(body: &[u8]) -> Result<Vec<ScanItem<'_>>, String> {
Ok(decode_items(body, ClientVerb::ScanMulti)?
.into_iter()
.map(|(item, c)| ScanItem {
target: c.hdr.target(),
reply_layout: c.hdr.arg0,
spec: &item[c.blob],
})
.collect())
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct DeltaCursor {
pub tag: u64,
pub tick: NonZeroU64,
}
impl DeltaCursor {
pub fn from_pair(tag: u64, tick: u64) -> Option<DeltaCursor> {
NonZeroU64::new(tick).map(|tick| DeltaCursor { tag, tick })
}
pub fn pair(self) -> (u64, u64) {
(self.tag, self.tick.get())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DeltaPollItem<'a> {
pub view: Target,
pub from: Option<DeltaCursor>,
pub reply_layout: u64,
pub spec: &'a [u8],
}
const CURSOR_SIZE: usize = 16;
pub fn encode_delta_poll(views: &[DeltaPollItem<'_>], keep: Option<u64>) -> Vec<u8> {
let mut blobs = Vec::with_capacity(views.iter().map(|v| CURSOR_SIZE + v.spec.len()).sum());
let ranges: Vec<Range<usize>> = views
.iter()
.map(|v| {
let start = blobs.len();
let (tag, tick) = v.from.map_or((0, 0), DeltaCursor::pair);
blobs.extend_from_slice(&tag.to_le_bytes());
blobs.extend_from_slice(&tick.to_le_bytes());
blobs.extend_from_slice(v.spec);
start..blobs.len()
})
.collect();
let items: Vec<FrameItem> = views
.iter()
.zip(ranges)
.map(|(v, blob)| FrameItem {
hdr: ControlHeader::naming(ClientVerb::DeltaPoll, v.view, v.reply_layout),
blob: &blobs[blob],
schema: None,
data: None,
})
.collect();
encode_items_under(ClientVerb::DeltaPoll, keep.unwrap_or(0), &items)
}
pub fn kept_as(first: u64, item: usize) -> u64 {
first.wrapping_add(item as u64)
}
pub fn delta_poll_kept(prologue: &ControlHeader) -> Option<impl Iterator<Item = u64>> {
let first = prologue.arg0;
(first != 0).then(|| (0..).map(move |item| kept_as(first, item)))
}
pub fn encode_held(ids: impl IntoIterator<Item = u64>) -> Vec<u8> {
ids.into_iter().flat_map(u64::to_le_bytes).collect()
}
pub fn decode_held(blob: &[u8]) -> Result<Vec<u64>, String> {
let (ids, rest) = blob.as_chunks::<8>();
if !rest.is_empty() {
return Err(format!(
"SyncPushed: {} bytes of held ids are no whole number of them",
blob.len()
));
}
Ok(ids.iter().map(|id| u64::from_le_bytes(*id)).collect())
}
pub fn decode_delta_items(body: &[u8]) -> Result<Vec<DeltaPollItem<'_>>, String> {
decode_items(body, ClientVerb::DeltaPoll)?
.into_iter()
.map(|(item, c)| {
if c.hdr.target_id == 0 {
return Err("DeltaPoll: view id 0 names no view".to_string());
}
let Some((cursor, spec)) = item[c.blob.clone()].split_first_chunk::<CURSOR_SIZE>() else {
return Err(format!("DeltaPoll: view {} carries no cursor", c.hdr.target_id));
};
let (tag, tick) = cursor.split_at(8);
Ok(DeltaPollItem {
view: c.hdr.target(),
from: DeltaCursor::from_pair(
u64::from_le_bytes(tag.try_into().unwrap()),
u64::from_le_bytes(tick.try_into().unwrap()),
),
reply_layout: c.hdr.arg0,
spec,
})
})
.collect()
}
#[cfg(test)]
#[path = "tests/txn_frame.rs"]
mod tests;