use crate::engine::CommandId;
use crate::routing::links::request::RequestId;
use crate::routing::links::resources::ResourceHash;
use crate::routing::links::LinkId;
pub trait IncomingAssemblyTable {
fn capacity(&self) -> usize;
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
fn link_ids(&self) -> &[LinkId];
fn original_hashes(&self) -> &[ResourceHash];
fn total_segments(&self) -> &[u64];
fn segments_received(&self) -> &[u64];
fn received_totals(&self) -> &[u64];
fn push(&mut self, link_id: LinkId, original_hash: ResourceHash, total_segments: u64);
fn set_progress(&mut self, index: usize, segments_received: u64, received_total: u64);
fn swap_remove(&mut self, index: usize);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SegmentFit {
Expected,
Unexpected,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AssemblyProgress {
Assembling,
Complete { total_size_bytes: u64 },
}
#[derive(Debug, Default)]
pub struct IncomingAssemblies<C: IncomingAssemblyTable> {
table: C,
}
impl<C: IncomingAssemblyTable> IncomingAssemblies<C> {
pub fn begin(&mut self, link_id: LinkId, original_hash: ResourceHash, total_segments: u64) {
if let Some(index) = self.index_of(&link_id) {
self.table.swap_remove(index);
}
if self.table.len() < self.table.capacity() {
self.table.push(link_id, original_hash, total_segments);
}
}
pub fn fit(
&self,
link_id: &LinkId,
original_hash: &ResourceHash,
segment_index: u64,
) -> SegmentFit {
let matches = self.index_of(link_id).is_some_and(|index| {
self.table.original_hashes()[index] == *original_hash
&& segment_index == self.table.segments_received()[index] + 1
});
if matches {
SegmentFit::Expected
} else {
SegmentFit::Unexpected
}
}
pub fn advance(&mut self, link_id: &LinkId, segment_bytes: u64) -> Option<AssemblyProgress> {
let index = self.index_of(link_id)?;
let segments_received = self.table.segments_received()[index] + 1;
let received_total = self.table.received_totals()[index].saturating_add(segment_bytes);
self.table
.set_progress(index, segments_received, received_total);
if segments_received >= self.table.total_segments()[index] {
Some(AssemblyProgress::Complete {
total_size_bytes: received_total,
})
} else {
Some(AssemblyProgress::Assembling)
}
}
pub fn original_hash(&self, link_id: &LinkId) -> Option<ResourceHash> {
self.index_of(link_id)
.map(|index| self.table.original_hashes()[index])
}
pub fn clear(&mut self, link_id: &LinkId) {
if let Some(index) = self.index_of(link_id) {
self.table.swap_remove(index);
}
}
fn index_of(&self, link_id: &LinkId) -> Option<usize> {
self.table
.link_ids()
.iter()
.position(|candidate| candidate == link_id)
}
}
pub trait OutgoingAssemblyTable {
fn capacity(&self) -> usize;
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
fn link_ids(&self) -> &[LinkId];
fn original_hashes(&self) -> &[ResourceHash];
fn supports_static_continuations(&self) -> bool {
false
}
fn static_continuation(&self, _index: usize) -> Option<StaticResponseContinuation> {
None
}
fn set_static_continuation(
&mut self,
_index: usize,
_continuation: StaticResponseContinuation,
) -> bool {
false
}
fn push(&mut self, link_id: LinkId, original_hash: ResourceHash);
fn swap_remove(&mut self, index: usize);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StaticResponseContinuation {
pub command_id: CommandId,
pub request_id: RequestId,
pub bytes: &'static [u8],
pub next_offset: usize,
pub next_segment_index: u64,
pub total_segments: u64,
pub total_data_bytes: u64,
pub metadata_packed_len: u32,
pub segment_stream_bytes: usize,
}
#[derive(Debug, Default)]
pub struct OutgoingAssemblies<C: OutgoingAssemblyTable> {
table: C,
}
impl<C: OutgoingAssemblyTable> OutgoingAssemblies<C> {
pub fn begin(&mut self, link_id: LinkId, original_hash: ResourceHash) {
if let Some(index) = self.index_of(&link_id) {
self.table.swap_remove(index);
}
if self.table.len() < self.table.capacity() {
self.table.push(link_id, original_hash);
}
}
pub fn supports_static_continuations(&self) -> bool {
self.table.supports_static_continuations()
}
pub fn set_static_continuation(
&mut self,
link_id: &LinkId,
continuation: StaticResponseContinuation,
) -> bool {
let Some(index) = self.index_of(link_id) else {
return false;
};
self.table.set_static_continuation(index, continuation)
}
pub fn static_continuation(&self, link_id: &LinkId) -> Option<StaticResponseContinuation> {
self.index_of(link_id)
.and_then(|index| self.table.static_continuation(index))
}
pub fn original_hash(&self, link_id: &LinkId) -> Option<ResourceHash> {
self.index_of(link_id)
.map(|index| self.table.original_hashes()[index])
}
pub fn clear(&mut self, link_id: &LinkId) {
if let Some(index) = self.index_of(link_id) {
self.table.swap_remove(index);
}
}
fn index_of(&self, link_id: &LinkId) -> Option<usize> {
self.table
.link_ids()
.iter()
.position(|candidate| candidate == link_id)
}
}
#[cfg(test)]
mod tests {
use super::super::*;
use super::*;
fn link(byte: u8) -> LinkId {
LinkId::new([byte; 16])
}
fn hash(byte: u8) -> ResourceHash {
ResourceHash::new([byte; 32])
}
fn table() -> IncomingAssemblies<FixedIncomingAssemblyTable<4>> {
IncomingAssemblies::default()
}
#[test]
fn advance_assembles_until_the_last_segment_completes() {
let mut assemblies = table();
assemblies.begin(link(1), hash(0xA), 3);
assert_eq!(
assemblies.advance(&link(1), 100),
Some(AssemblyProgress::Assembling)
);
assert_eq!(
assemblies.advance(&link(1), 100),
Some(AssemblyProgress::Assembling)
);
assert_eq!(
assemblies.advance(&link(1), 50),
Some(AssemblyProgress::Complete {
total_size_bytes: 250
})
);
}
#[test]
fn fit_expects_the_next_segment_of_the_right_chain() {
let mut assemblies = table();
assemblies.begin(link(1), hash(0xA), 3);
assemblies.advance(&link(1), 100);
assert_eq!(
assemblies.fit(&link(1), &hash(0xA), 2),
SegmentFit::Expected
);
assert_eq!(
assemblies.fit(&link(1), &hash(0xA), 3),
SegmentFit::Unexpected
);
assert_eq!(
assemblies.fit(&link(1), &hash(0xB), 2),
SegmentFit::Unexpected
);
assert_eq!(
assemblies.fit(&link(2), &hash(0xA), 2),
SegmentFit::Unexpected
);
}
#[test]
fn clear_retires_the_chain() {
let mut assemblies = table();
assemblies.begin(link(1), hash(0xA), 3);
assemblies.clear(&link(1));
assert_eq!(assemblies.advance(&link(1), 100), None);
assert_eq!(assemblies.original_hash(&link(1)), None);
}
#[test]
fn begin_replaces_a_prior_chain_on_the_same_link() {
let mut assemblies = table();
assemblies.begin(link(1), hash(0xA), 2);
assemblies.begin(link(1), hash(0xB), 3);
assert_eq!(assemblies.original_hash(&link(1)), Some(hash(0xB)));
}
}