pub mod advertisement;
pub mod assemble_incoming;
pub mod assembly;
pub mod build_outgoing;
pub mod control;
pub mod receive;
pub mod send;
mod send_plan;
pub mod serve_outgoing;
pub mod streamed_open;
pub mod table;
pub use send_plan::{ResourceSegmentPlan, ResourceSendPlan, ResourceSendPlanError};
use crate::crypto::SHA256_OUTPUT_LEN;
use crate::engine::CommandId;
use crate::routing::links::data::LINK_MDU;
use crate::routing::links::request::RequestId;
use crate::routing::links::LinkId;
use crate::wire::{BROADCAST_MTU, HEADER_MAX_LEN, IFAC_MIN_LEN};
use sha2::{Digest, Sha256};
pub const MAP_HASH_LEN: usize = 4;
pub const RESOURCE_NONCE_LEN: usize = 4;
pub const RESOURCE_HASH_LEN: usize = SHA256_OUTPUT_LEN;
pub const WINDOW_MAX: usize = 75;
pub const WINDOW_START: usize = 4;
pub const WINDOW_MIN: usize = 2;
pub const WINDOW_MAX_SLOW: usize = 10;
pub const WINDOW_FLEXIBILITY: usize = 4;
pub const PART_REQUEST_MAX_RETRIES: u8 = 16;
pub const MAX_ADVERTISEMENT_RETRIES: u8 = 4;
pub const PART_TIMEOUT_FACTOR: u64 = 4;
pub const PART_TIMEOUT_FACTOR_AFTER_RTT: u64 = 2;
pub const RATE_FAST_BYTES_PER_SECOND: u64 = 50 * 1000 / 8;
pub const RATE_VERY_SLOW_BYTES_PER_SECOND: u64 = 2 * 1000 / 8;
pub const WINDOW_MAX_VERY_SLOW: usize = 4;
pub const FAST_RATE_THRESHOLD: u8 = (WINDOW_MAX_SLOW - WINDOW_START - 2) as u8;
pub const VERY_SLOW_RATE_THRESHOLD: u8 = 2;
pub const ESTABLISHMENT_COST_ESTIMATE_BYTES: u64 = 86 + 118 + 83;
pub const PROOF_TIMEOUT_FACTOR: u64 = 3;
pub const PROCESSING_GRACE_MS: u64 = 1_000;
pub const RETRY_GRACE_MS: u64 = 250;
pub const PER_RETRY_DELAY_MS: u64 = 500;
pub const SENDER_GRACE_MS: u64 = 10_000;
pub const DECOMPRESSION_GRACE_MS: u64 = 10_000;
pub const OPEN_VERDICT_GRACE_MS: u64 = 1_000;
pub const ADVERTISEMENT_OVERHEAD: usize = 134;
pub const HASHMAP_MAX_LEN: usize = (LINK_MDU - ADVERTISEMENT_OVERHEAD) / MAP_HASH_LEN;
pub const COLLISION_GUARD_SIZE: usize = 2 * WINDOW_MAX + HASHMAP_MAX_LEN;
pub const MAX_EFFICIENT_SIZE: usize = 1024 * 1024 - 1;
pub const METADATA_MAX_SIZE: usize = 16 * 1024 * 1024 - 1;
pub const METADATA_PREFIX_LEN: usize = 3;
pub const fn resource_sdu(mtu: usize) -> usize {
mtu - HEADER_MAX_LEN - IFAC_MIN_LEN
}
pub const fn sealed_transfer_bytes(stream_len: usize) -> usize {
let padded = ((stream_len + RESOURCE_NONCE_LEN) / 16 + 1) * 16;
16 + padded + 32
}
pub const fn max_part_count(transfer_capacity: usize) -> usize {
transfer_capacity.div_ceil(resource_sdu(BROADCAST_MTU))
}
pub const fn max_outgoing_resource_reaction_frames(transfer_capacity: usize) -> usize {
let parts = max_part_count(transfer_capacity);
if parts < WINDOW_MAX {
parts + 1
} else {
WINDOW_MAX + 1
}
}
#[cfg(test)]
mod reaction_capacity_tests {
use super::*;
#[test]
fn outbound_reaction_capacity_tracks_small_stores_and_caps_at_one_full_window() {
let part = resource_sdu(BROADCAST_MTU);
assert_eq!(max_outgoing_resource_reaction_frames(0), 1);
assert_eq!(max_outgoing_resource_reaction_frames(18 * part), 19);
assert_eq!(max_outgoing_resource_reaction_frames(WINDOW_MAX * part), 76);
assert_eq!(max_outgoing_resource_reaction_frames(100 * part), 76);
}
}
pub fn map_hash(part: &[u8], salt_nonce: &SaltNonce) -> [u8; MAP_HASH_LEN] {
let mut hasher = Sha256::new();
hasher.update(part);
hasher.update(salt_nonce.as_bytes());
let digest = hasher.finalize();
[digest[0], digest[1], digest[2], digest[3]]
}
pub(crate) fn map_hash_name_word(name: &[u8]) -> u32 {
u32::from_ne_bytes([name[0], name[1], name[2], name[3]])
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SaltNonce([u8; RESOURCE_NONCE_LEN]);
impl SaltNonce {
#[must_use]
pub const fn new(bytes: [u8; RESOURCE_NONCE_LEN]) -> Self {
Self(bytes)
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8; RESOURCE_NONCE_LEN] {
&self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ResourceStrategy {
#[default]
AcceptNone,
Accept {
max_uncompressed_bytes: u64,
accept_compressed: bool,
},
AcceptIf,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResourceOffer {
pub link_id: LinkId,
pub remote_identity: Option<crate::identity::IdentityHash>,
pub hash: ResourceHash,
pub uncompressed_data_bytes: u64,
pub sealed_transfer_bytes: usize,
pub part_count: usize,
pub segment_index: u64,
pub total_segment_count: u64,
pub compression: ResourceCompression,
pub has_metadata: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResourceCompression {
Uncompressed,
Bz2,
}
impl ResourceCompression {
#[must_use]
pub const fn wire_flag(self) -> bool {
match self {
Self::Uncompressed => false,
Self::Bz2 => true,
}
}
#[must_use]
pub const fn from_wire_flag(compressed: bool) -> Self {
if compressed {
Self::Bz2
} else {
Self::Uncompressed
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResourceSend<'a> {
pub id: CommandId,
pub link_id: LinkId,
pub body: ResourceBody<'a>,
pub correlation: ResourceCorrelation,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResourceBody<'a> {
pub data: &'a [u8],
pub compressed_candidate: Option<&'a [u8]>,
pub metadata: ResourceMetadata<'a>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ResourceMetadata<'a> {
#[default]
None,
Packed(&'a [u8]),
SentInFirstSegment { packed_len: u32 },
}
impl<'a> ResourceMetadata<'a> {
#[must_use]
pub const fn block_len(&self) -> usize {
match self {
Self::None => 0,
Self::Packed(packed) => METADATA_PREFIX_LEN + packed.len(),
Self::SentInFirstSegment { packed_len } => METADATA_PREFIX_LEN + *packed_len as usize,
}
}
#[must_use]
pub const fn travels(&self) -> bool {
!matches!(self, Self::None)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResourceSegment {
pub index: u64,
pub total_segments: u64,
pub total_data_bytes: u64,
}
impl ResourceSegment {
#[must_use]
pub fn whole(data_len: u64) -> Self {
Self {
index: 1,
total_segments: 1,
total_data_bytes: data_len,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResourcePartRequest<'a> {
pub link_id: LinkId,
pub hash: ResourceHash,
pub requested: &'a [u8],
pub last_known_map_hash: Option<[u8; MAP_HASH_LEN]>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ResourceCorrelation {
#[default]
Unsolicited,
Request {
id: RequestId,
response_timeout: crate::engine::RequestResponseTimeout,
maximum_response_bytes: crate::units::ByteLimit,
},
Response(RequestId),
}
impl ResourceCorrelation {
#[must_use]
pub fn request_id(self) -> Option<RequestId> {
match self {
Self::Unsolicited => None,
Self::Request { id, .. } | Self::Response(id) => Some(id),
}
}
#[must_use]
pub const fn is_request(self) -> bool {
matches!(self, Self::Request { .. })
}
#[must_use]
pub const fn is_response(self) -> bool {
matches!(self, Self::Response(_))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResourceFailureCause {
CancelledBySender,
RefusedHashmapUpdate(table::ApplyHashmapUpdateError),
RetriesExhausted,
LinkVanished,
TransferUnopenable,
TransferCorrupt,
ProofUnsendable,
DecompressionFailed,
DecompressionTimedOut,
OpenTimedOut,
MetadataOverrun,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResourceHash([u8; RESOURCE_HASH_LEN]);
impl ResourceHash {
#[must_use]
pub const fn new(bytes: [u8; RESOURCE_HASH_LEN]) -> Self {
Self(bytes)
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8; RESOURCE_HASH_LEN] {
&self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResourceProof([u8; RESOURCE_HASH_LEN]);
impl ResourceProof {
#[must_use]
pub const fn new(bytes: [u8; RESOURCE_HASH_LEN]) -> Self {
Self(bytes)
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8; RESOURCE_HASH_LEN] {
&self.0
}
}