Files
2024-03-27 16:04:51 +00:00

1146 lines
40 KiB
Rust

// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::ChunkingError;
use nym_sphinx_params::{SerializedFragmentIdentifier, FRAG_ID_LEN};
use std::fmt::{self, Debug, Formatter};
// Personal reflection: In hindsight I've spent too much time on relatively too little
// gain here, as even though I might have saved couple of bytes per packet, the gain
// is negligible in the context of having to include SURB-ACKs and reply-SURBs in the packets.
//
// However, if we really cared about those tiny optimisations, `UNLINKED_FRAGMENTED_HEADER`
// could be further compressed: if current_fragment != 1 && current_fragment != 255, you don't
// need to use the tail byte to indicate lack of linking as it can be implied from the fragment
// position.
// TODO for later: with the removal of 'unfragmented' fragments, the first bit of each header
// is completely useless, we should then think how to make the set_id become u32 instead of i32.
// (the current limitation for making the seemingly trivial change is "linked id" which
// has to have same amount of space available and right now it only has 31 bits available)
/// When the underlying message has to be split into multiple Fragments, but still manages to fit
/// into a single `FragmentSet`, each `FragmentHeader` needs to hold additional information to allow
/// for correct message reconstruction: 4 bytes for set id, 1 byte to represent total number
/// of fragments in the set, 1 byte to represent position of the current fragment in the set
/// and finally an extra byte to indicate the fragment has no links to other sets.
pub const UNLINKED_FRAGMENTED_HEADER_LEN: usize = 7;
/// Logically almost identical to `UNLINKED_FRAGMENTED_HEADER_LEN`, however, the extra three
/// bytes are due to changing the final byte that used to indicate the `Fragment` is not linked
/// into 4 byte id of either previous or the next set.
/// Note that the linked headers can potentially be used only for very first and very last
/// `Fragment` in a `FragmentSet`.
pub const LINKED_FRAGMENTED_HEADER_LEN: usize = 10;
/// Maximum size of payload of each fragment is always the maximum amount of plaintext data
/// we can put into a sphinx packet minus length of respective fragment header.
pub const fn unlinked_fragment_payload_max_len(max_plaintext_size: usize) -> usize {
max_plaintext_size - UNLINKED_FRAGMENTED_HEADER_LEN
}
/// Maximum size of payload of each fragment is always the maximum amount of plaintext data
/// we can put into a sphinx packet minus length of respective fragment header.
pub const fn linked_fragment_payload_max_len(max_plaintext_size: usize) -> usize {
max_plaintext_size - LINKED_FRAGMENTED_HEADER_LEN
}
// TODO: should this be defined in this module or in `cover`? I can see arguments for both options...
/// A special `FragmentIdentifier` that is not valid in all cases unless if it's used in a loop
/// cover message.
pub const COVER_FRAG_ID: FragmentIdentifier = FragmentIdentifier {
set_id: 0,
fragment_position: 0,
};
/// Identifier to uniquely identify a fragment. It represents 31bit ID of given `FragmentSet`
/// and u8 position of the `Fragment` in the set.
// TODO: this should really be redesigned, especially how cover and reply messages are really
// "abusing" this. They should work with it natively instead.
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct FragmentIdentifier {
set_id: i32,
fragment_position: u8,
}
impl fmt::Display for FragmentIdentifier {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"Fragment Identifier: id: {} position: {}",
self.set_id, self.fragment_position
)
}
}
impl FragmentIdentifier {
pub fn to_bytes(self) -> SerializedFragmentIdentifier {
debug_assert_eq!(FRAG_ID_LEN, 5);
let set_id_bytes = self.set_id.to_be_bytes();
[
set_id_bytes[0],
set_id_bytes[1],
set_id_bytes[2],
set_id_bytes[3],
self.fragment_position,
]
}
pub fn try_from_bytes(b: SerializedFragmentIdentifier) -> Result<Self, ChunkingError> {
debug_assert_eq!(FRAG_ID_LEN, 5);
let set_id = i32::from_be_bytes([b[0], b[1], b[2], b[3]]);
// set_id == 0 is valid for COVER_FRAG_ID and replies
if set_id < 0 {
return Err(ChunkingError::MalformedFragmentIdentifier { received: set_id });
}
Ok(FragmentIdentifier {
set_id,
fragment_position: b[4],
})
}
}
/// The basic unit of division of underlying bytes message sent through the mix network.
/// Each `Fragment` after being marshaled is guaranteed to fit into a single sphinx packet.
/// The `Fragment` itself consists of part, or whole of, message to be sent as well as additional
/// header used to reconstruct the message after being received.
#[derive(PartialEq, Clone)]
pub struct Fragment {
header: FragmentHeader,
payload: Vec<u8>,
}
// manual implementation to hide detailed payload that we don't care about
impl Debug for Fragment {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Fragment")
.field("header", &self.header)
.field("payload length", &self.payload.len())
.finish()
}
}
impl Fragment {
/// Tries to encapsulate provided payload slice and metadata into a `Fragment`.
/// It can fail if payload would not fully fit in a single `Fragment` or some of the metadata
/// is malformed or self-contradictory, for example if current_fragment > total_fragments.
pub(crate) fn try_new(
payload: &[u8],
id: i32,
total_fragments: u8,
current_fragment: u8,
previous_fragments_set_id: Option<i32>,
next_fragments_set_id: Option<i32>,
max_plaintext_size: usize,
) -> Result<Self, ChunkingError> {
let header = FragmentHeader::try_new(
id,
total_fragments,
current_fragment,
previous_fragments_set_id,
next_fragments_set_id,
)?;
// check for whether payload has expected length, which depend on whether fragment is linked
// and if it's the only one or the last one in the set (then lower bound is removed)
let max_linked_len = linked_fragment_payload_max_len(max_plaintext_size);
let max_unlinked_len = unlinked_fragment_payload_max_len(max_plaintext_size);
if previous_fragments_set_id.is_some() {
if total_fragments > 1 {
if payload.len() != max_linked_len {
return Err(ChunkingError::InvalidPayloadLengthError {
received: payload.len(),
expected: max_linked_len,
});
}
} else if payload.len() > max_linked_len {
return Err(ChunkingError::TooLongPayloadLengthError {
received: payload.len(),
expected_at_most: max_linked_len,
});
}
} else if next_fragments_set_id.is_some() {
if payload.len() != max_linked_len {
return Err(ChunkingError::InvalidPayloadLengthError {
received: payload.len(),
expected: max_linked_len,
});
}
} else if total_fragments != current_fragment {
if payload.len() != max_unlinked_len {
return Err(ChunkingError::InvalidPayloadLengthError {
received: payload.len(),
expected: max_unlinked_len,
});
}
} else if payload.len() > max_unlinked_len {
return Err(ChunkingError::TooLongPayloadLengthError {
received: payload.len(),
expected_at_most: max_unlinked_len,
});
}
Ok(Fragment {
header,
payload: payload.to_vec(),
})
}
/// based on the size of the embedded data, determines which predefined `PacketSize`
/// was used for construction of this `Fragment`
pub fn serialized_size(&self) -> usize {
// TODO: optimisation: determine the size of the header without actually serializing it...
let header_size = self.header.to_bytes().len();
header_size + self.payload_size()
}
/// Convert this `Fragment` into vector of bytes which can be put into a sphinx packet.
pub fn into_bytes(self) -> Vec<u8> {
self.header
.to_bytes()
.into_iter()
.chain(self.payload)
.collect()
}
/// Derive identifier unique for this particular fragment
pub fn fragment_identifier(&self) -> FragmentIdentifier {
FragmentIdentifier {
set_id: self.header.id,
fragment_position: self.header.current_fragment,
}
}
/// Gets the size of payload contained in this `Fragment`.
pub fn payload_size(&self) -> usize {
self.payload.len()
}
/// Extracts id of this `Fragment`.
pub fn id(&self) -> i32 {
self.header.id
}
/// Extracts total number of fragments associated with this particular `Fragment` (belonging to
/// the same `FragmentSet`).
pub fn total_fragments(&self) -> u8 {
self.header.total_fragments
}
/// Extracts position of this `Fragment` in a `FragmentSet`.
pub fn current_fragment(&self) -> u8 {
self.header.current_fragment
}
/// Extracts information regarding id of pre-linked `FragmentSet`
pub fn previous_fragments_set_id(&self) -> Option<i32> {
self.header.previous_fragments_set_id
}
/// Extracts information regarding id of post-linked `FragmentSet`
pub fn next_fragments_set_id(&self) -> Option<i32> {
self.header.next_fragments_set_id
}
/// Consumes `self` to obtain payload (i.e. part of original message) associated with this
/// `Fragment`.
pub(crate) fn extract_payload(self) -> Vec<u8> {
self.payload
}
/// Tries to recover `Fragment` from slice of bytes extracted from received sphinx packet.
/// It can fail if payload would not fully fit in a single `Fragment` or some of the metadata
/// is malformed or self-contradictory, for example if current_fragment > total_fragments.
pub fn try_from_bytes(b: &[u8]) -> Result<Self, ChunkingError> {
let (header, n) = FragmentHeader::try_from_bytes(b)?;
// there's no sane way to decide if payload has correct range anymore as
// it's no longer fixed
Ok(Fragment {
header,
payload: b[n..].to_vec(),
})
}
}
/// In order to be able to re-assemble fragmented message sent through a mix-network, some
/// metadata is attached alongside the actual message payload. The idea is to include as little
/// of that data as possible due to computationally very costly process of sphinx encapsulation.
///
/// The generic `FragmentHeader` is represented as follows:
/// IF flag || 31 bit ID || TotalFragments || CurrentFragment || LID flag || 31 bit Linked ID
/// note that LID is a valid flag only for first and
/// last fragment (if TotalFragments == CurrentFragment == 255) in given set.
///
/// further note if LID is not set,
/// then the Linked ID bytes in the header are used as payload.
///
/// Hence after marshaling `FragmentHeader` into bytes,
/// the following three alternatives are possible:
///
/// 7 byte long sequence representing that this `Fragment` is one of multiple ones in the set.
/// However, the set is not linked to any other sets:
/// '1'bit || 31-bit ID || 1-byte TF || 1 byte CF || '0'byte
///
/// 10 byte sequence representing first (or last) fragment in the set,
/// where the set is linked to either preceding data (TF == 1) or proceeding data (TF == CF == 255)
/// '1'bit || 31-bit ID || 1-byte TF || 1 byte CF || '1'bit || 31-bit LID
///
/// And hence for messages larger than `max_plaintext_size` but small enough
/// to avoid set division (which happens if message has to be fragmented into more than 255 fragments)
/// there is 7 bytes of overhead inside each sphinx packet sent
/// and for the longest messages, without upper bound, there is usually also only 7 bytes
/// of overhead apart from first and last fragments in each set that instead have 10 bytes of overhead.
#[derive(PartialEq, Clone, Debug)]
pub(crate) struct FragmentHeader {
/// ID associated with `FragmentSet` to which this particular `Fragment` belongs.
/// Its value is restricted to (0, i32::max_value()].
/// Note that it *excludes* 0, but *includes* i32::max_value().
/// This allows the field to be represented using 31 bits.
id: i32,
/// Total number of `Fragment`s in `FragmentSet` used to be able to determine if entire
/// set was fully received as well as to perform bound checks.
total_fragments: u8,
/// Since message is always fragmented into payloads of constant lengths
/// (apart from possibly the last one), there's no need to use offsets like ipv4/ipv6
/// and we can just simply enumerate the fragments to later reconstruct the message.
current_fragment: u8,
/// Optional ID of previous `FragmentSet` into which the original message was split.
/// Note, this option is only valid of `current_fragment == 1`
previous_fragments_set_id: Option<i32>,
/// Optional ID of next `FragmentSet` into which the original message was split.
/// Note, this option is only valid of `current_fragment == total_fragments == u8::max_value()`
next_fragments_set_id: Option<i32>,
}
impl FragmentHeader {
/// Tries to create a new `FragmentHeader` using provided metadata. Bunch of logical
/// checks are performed to see if the data is not self-contradictory,
/// for example if current_fragment > total_fragments.
fn try_new(
id: i32,
total_fragments: u8,
current_fragment: u8,
previous_fragments_set_id: Option<i32>,
next_fragments_set_id: Option<i32>,
) -> Result<Self, ChunkingError> {
if id <= 0 {
return Err(ChunkingError::MalformedHeaderError);
}
if total_fragments < current_fragment {
return Err(ChunkingError::MalformedHeaderError);
}
if total_fragments == 0 {
return Err(ChunkingError::MalformedHeaderError);
}
if current_fragment == 0 {
return Err(ChunkingError::MalformedHeaderError);
}
if let Some(pfid) = previous_fragments_set_id {
if pfid <= 0 || current_fragment != 1 || pfid == id {
return Err(ChunkingError::MalformedHeaderError);
}
}
if let Some(nfid) = next_fragments_set_id {
if nfid <= 0 || current_fragment != total_fragments || nfid == id {
return Err(ChunkingError::MalformedHeaderError);
}
}
Ok(FragmentHeader {
id,
total_fragments,
current_fragment,
previous_fragments_set_id,
next_fragments_set_id,
})
}
/// Tries to recover `FragmentHeader` from slice of bytes extracted from received sphinx packet.
/// If successful, returns `Self` and number of bytes used, as those can differ based on the
/// type of header (unlinked or linked).
fn try_from_bytes(b: &[u8]) -> Result<(Self, usize), ChunkingError> {
// header needs to be at least 7 bytes long
if b.len() < UNLINKED_FRAGMENTED_HEADER_LEN {
return Err(ChunkingError::TooShortFragmentHeader {
received: b.len(),
expected: UNLINKED_FRAGMENTED_HEADER_LEN,
});
}
let frag_id = i32::from_be_bytes(b[0..4].try_into().unwrap());
// sanity check for the fragmentation flag
if ((frag_id >> 31) & 1) == 0 {
return Err(ChunkingError::MalformedHeaderError);
}
let id = frag_id & !(1 << 31); // make sure to clear the flag bit to parse id correctly
let total_fragments = b[4];
let current_fragment = b[5];
if total_fragments == 0 || current_fragment == 0 || current_fragment > total_fragments {
return Err(ChunkingError::MalformedHeaderError);
}
let mut previous_fragments_set_id = None;
let mut next_fragments_set_id = None;
// check if the linking id flag might be set
let read_bytes = if b[6] != 0 {
// there's linking ID supposedly attached, make sure we have enough bytes to parse
if b.len() < LINKED_FRAGMENTED_HEADER_LEN {
return Err(ChunkingError::TooShortFragmentHeader {
received: b.len(),
expected: LINKED_FRAGMENTED_HEADER_LEN,
});
}
let flagged_linked_id = i32::from_be_bytes(b[6..10].try_into().unwrap());
// sanity check for the linked flag
if ((flagged_linked_id >> 31) & 1) == 0 {
return Err(ChunkingError::MalformedHeaderError);
}
let linked_id = flagged_linked_id & !(1 << 31); // make sure to clear the flag bit to parse id correctly
if current_fragment == 1 {
previous_fragments_set_id = Some(linked_id);
} else if total_fragments == current_fragment && current_fragment == u8::max_value() {
next_fragments_set_id = Some(linked_id);
} else {
return Err(ChunkingError::MalformedHeaderError);
}
10
} else {
7
};
Ok((
Self::try_new(
id,
total_fragments,
current_fragment,
previous_fragments_set_id,
next_fragments_set_id,
)?,
read_bytes,
))
}
/// Marshal this `FragmentHeader` into vector of bytes which can be put into a sphinx packet.
fn to_bytes(&self) -> Vec<u8> {
let frag_id = self.id | (1 << 31);
let frag_id_bytes = frag_id.to_be_bytes();
let bytes_prefix_iter = frag_id_bytes
.into_iter()
.chain(std::iter::once(self.total_fragments))
.chain(std::iter::once(self.current_fragment));
let is_linked =
self.previous_fragments_set_id.is_some() || self.next_fragments_set_id.is_some();
if is_linked {
let linked_id = self
.previous_fragments_set_id
.unwrap_or_else(|| self.next_fragments_set_id.unwrap());
let linked_id_entry = linked_id | (1 << 31);
let linked_id_bytes = linked_id_entry.to_be_bytes();
bytes_prefix_iter
.chain(linked_id_bytes.iter().cloned())
.collect()
} else {
bytes_prefix_iter.chain(std::iter::once(0)).collect()
}
}
}
// everything below are tests
#[cfg(test)]
mod fragment_tests {
use super::*;
use nym_sphinx_params::packet_sizes::PacketSize;
use rand::{thread_rng, RngCore};
fn max_plaintext_size() -> usize {
PacketSize::default().plaintext_size() - PacketSize::AckPacket.size()
}
#[test]
fn can_be_converted_to_and_from_bytes_for_unfragmented_payload() {
let mut rng = thread_rng();
let mlen = 40;
let mut valid_message = vec![0u8; mlen];
rng.fill_bytes(&mut valid_message);
let valid_unfragmented_packet = Fragment {
header: FragmentHeader::try_new(12345, 1, 1, None, None).unwrap(),
payload: valid_message,
};
let packet_bytes = valid_unfragmented_packet.clone().into_bytes();
assert_eq!(
valid_unfragmented_packet,
Fragment::try_from_bytes(&packet_bytes).unwrap()
);
let empty_unfragmented_packet = Fragment {
header: FragmentHeader::try_new(12345, 1, 1, None, None).unwrap(),
payload: Vec::new(),
};
let packet_bytes = empty_unfragmented_packet.clone().into_bytes();
assert_eq!(
empty_unfragmented_packet,
Fragment::try_from_bytes(&packet_bytes).unwrap()
);
}
#[test]
fn can_be_converted_to_and_from_bytes_for_unlinked_fragmented_payload() {
let mut rng = thread_rng();
let mut msg = vec![0u8; unlinked_fragment_payload_max_len(max_plaintext_size())];
rng.fill_bytes(&mut msg);
let non_last_packet = Fragment {
header: FragmentHeader::try_new(12345, 10, 5, None, None).unwrap(),
payload: msg,
};
let packet_bytes = non_last_packet.clone().into_bytes();
assert_eq!(
non_last_packet,
Fragment::try_from_bytes(&packet_bytes).unwrap()
);
let mut msg = vec![0u8; unlinked_fragment_payload_max_len(max_plaintext_size())];
rng.fill_bytes(&mut msg);
let last_full_packet = Fragment {
header: FragmentHeader::try_new(12345, 10, 10, None, None).unwrap(),
payload: msg,
};
let packet_bytes = last_full_packet.clone().into_bytes();
assert_eq!(
last_full_packet,
Fragment::try_from_bytes(&packet_bytes).unwrap()
);
let mut msg = vec![0u8; unlinked_fragment_payload_max_len(max_plaintext_size()) - 20];
rng.fill_bytes(&mut msg);
let last_non_full_packet = Fragment {
header: FragmentHeader::try_new(12345, 10, 10, None, None).unwrap(),
payload: msg,
};
let packet_bytes = last_non_full_packet.clone().into_bytes();
assert_eq!(
last_non_full_packet,
Fragment::try_from_bytes(&packet_bytes).unwrap()
);
}
#[test]
fn can_be_converted_to_and_from_bytes_for_pre_linked_fragmented_payload() {
let mut rng = thread_rng();
let mut msg = vec![0u8; linked_fragment_payload_max_len(max_plaintext_size())];
rng.fill_bytes(&mut msg);
let fragment = Fragment {
header: FragmentHeader::try_new(12345, 10, 1, Some(1234), None).unwrap(),
payload: msg,
};
let packet_bytes = fragment.clone().into_bytes();
assert_eq!(fragment, Fragment::try_from_bytes(&packet_bytes).unwrap());
let mut msg = vec![0u8; linked_fragment_payload_max_len(max_plaintext_size()) - 20];
rng.fill_bytes(&mut msg);
let fragment = Fragment {
header: FragmentHeader::try_new(12345, 1, 1, Some(1234), None).unwrap(),
payload: msg,
};
let packet_bytes = fragment.clone().into_bytes();
// TODO:
// TODO:
// packet_bytes len assertion
assert_eq!(fragment, Fragment::try_from_bytes(&packet_bytes).unwrap());
}
#[test]
fn can_be_converted_to_and_from_bytes_for_post_linked_fragmented_payload() {
let mut rng = thread_rng();
let mut msg = vec![0u8; linked_fragment_payload_max_len(max_plaintext_size())];
rng.fill_bytes(&mut msg);
let fragment = Fragment {
header: FragmentHeader::try_new(
12345,
u8::max_value(),
u8::max_value(),
None,
Some(1234),
)
.unwrap(),
payload: msg,
};
let packet_bytes = fragment.clone().into_bytes();
assert_eq!(fragment, Fragment::try_from_bytes(&packet_bytes).unwrap());
let mut msg = vec![0u8; linked_fragment_payload_max_len(max_plaintext_size()) - 20];
rng.fill_bytes(&mut msg);
let fragment = Fragment {
header: FragmentHeader::try_new(
12345,
u8::max_value(),
u8::max_value(),
None,
Some(1234),
)
.unwrap(),
payload: msg,
};
let packet_bytes = fragment.clone().into_bytes();
// TODO:
// TODO:
// packet_bytes len assertion
assert_eq!(fragment, Fragment::try_from_bytes(&packet_bytes).unwrap());
}
#[test]
fn unlinked_fragment_can_be_created_with_payload_of_valid_length() {
let id = 12345;
let full_payload = vec![1u8; unlinked_fragment_payload_max_len(max_plaintext_size())];
let non_full_payload =
vec![1u8; unlinked_fragment_payload_max_len(max_plaintext_size()) - 1];
let non_full_payload2 =
vec![1u8; unlinked_fragment_payload_max_len(max_plaintext_size()) - 60];
assert!(
Fragment::try_new(&full_payload, id, 10, 1, None, None, max_plaintext_size()).is_ok()
);
assert!(
Fragment::try_new(&full_payload, id, 10, 5, None, None, max_plaintext_size()).is_ok()
);
assert!(
Fragment::try_new(&full_payload, id, 10, 10, None, None, max_plaintext_size()).is_ok()
);
assert!(
Fragment::try_new(&full_payload, id, 1, 1, None, None, max_plaintext_size()).is_ok()
);
assert!(Fragment::try_new(
&non_full_payload,
id,
10,
10,
None,
None,
max_plaintext_size(),
)
.is_ok());
assert!(Fragment::try_new(
&non_full_payload,
id,
1,
1,
None,
None,
max_plaintext_size(),
)
.is_ok());
assert!(Fragment::try_new(
&non_full_payload2,
id,
10,
10,
None,
None,
max_plaintext_size(),
)
.is_ok());
assert!(Fragment::try_new(
&non_full_payload2,
id,
1,
1,
None,
None,
max_plaintext_size(),
)
.is_ok());
}
#[test]
fn unlinked_fragment_returns_error_when_created_with_payload_of_invalid_length() {
let id = 12345;
let non_full_payload =
vec![1u8; unlinked_fragment_payload_max_len(max_plaintext_size()) - 1];
let non_full_payload2 =
vec![1u8; unlinked_fragment_payload_max_len(max_plaintext_size()) - 20];
let too_much_payload =
vec![1u8; unlinked_fragment_payload_max_len(max_plaintext_size()) + 1];
assert!(Fragment::try_new(
&non_full_payload,
id,
10,
1,
None,
None,
max_plaintext_size(),
)
.is_err());
assert!(Fragment::try_new(
&non_full_payload,
id,
10,
5,
None,
None,
max_plaintext_size(),
)
.is_err());
assert!(Fragment::try_new(
&too_much_payload,
id,
10,
1,
None,
None,
max_plaintext_size(),
)
.is_err());
assert!(Fragment::try_new(
&too_much_payload,
id,
10,
5,
None,
None,
max_plaintext_size(),
)
.is_err());
assert!(Fragment::try_new(
&too_much_payload,
id,
1,
1,
None,
None,
max_plaintext_size(),
)
.is_err());
assert!(Fragment::try_new(
&non_full_payload2,
id,
10,
1,
None,
None,
max_plaintext_size(),
)
.is_err());
assert!(Fragment::try_new(
&non_full_payload2,
id,
10,
5,
None,
None,
max_plaintext_size(),
)
.is_err());
}
#[test]
fn linked_fragment_can_be_created_with_payload_of_valid_length() {
let id = 12345;
let link_id = 1234;
let full_payload = vec![1u8; linked_fragment_payload_max_len(max_plaintext_size())];
let non_full_payload = vec![1u8; linked_fragment_payload_max_len(max_plaintext_size()) - 1];
let non_full_payload2 =
vec![1u8; linked_fragment_payload_max_len(max_plaintext_size()) - 20];
assert!(Fragment::try_new(
&full_payload,
id,
10,
1,
Some(link_id),
None,
max_plaintext_size(),
)
.is_ok());
assert!(Fragment::try_new(
&full_payload,
id,
1,
1,
Some(link_id),
None,
max_plaintext_size(),
)
.is_ok());
assert!(Fragment::try_new(
&non_full_payload,
id,
1,
1,
Some(link_id),
None,
max_plaintext_size(),
)
.is_ok());
assert!(Fragment::try_new(
&non_full_payload2,
id,
1,
1,
Some(link_id),
None,
max_plaintext_size(),
)
.is_ok());
assert!(Fragment::try_new(
&full_payload,
id,
u8::max_value(),
u8::max_value(),
None,
Some(link_id),
max_plaintext_size(),
)
.is_ok());
}
#[test]
fn linked_fragment_returns_error_when_created_with_payload_of_invalid_length() {
let id = 12345;
let link_id = 1234;
let non_full_payload = vec![1u8; linked_fragment_payload_max_len(max_plaintext_size()) - 1];
let non_full_payload2 =
vec![1u8; linked_fragment_payload_max_len(max_plaintext_size()) - 20];
let too_much_payload = vec![1u8; linked_fragment_payload_max_len(max_plaintext_size()) + 1];
assert!(Fragment::try_new(
&non_full_payload,
id,
10,
1,
Some(link_id),
None,
max_plaintext_size(),
)
.is_err());
assert!(Fragment::try_new(
&non_full_payload2,
id,
10,
1,
Some(link_id),
None,
max_plaintext_size(),
)
.is_err());
assert!(Fragment::try_new(
&too_much_payload,
id,
10,
1,
Some(link_id),
None,
max_plaintext_size(),
)
.is_err());
assert!(Fragment::try_new(
&too_much_payload,
id,
1,
1,
Some(link_id),
None,
max_plaintext_size(),
)
.is_err());
assert!(Fragment::try_new(
&non_full_payload,
id,
u8::max_value(),
u8::max_value(),
None,
Some(link_id),
max_plaintext_size(),
)
.is_err());
assert!(Fragment::try_new(
&non_full_payload2,
id,
u8::max_value(),
u8::max_value(),
None,
Some(link_id),
max_plaintext_size(),
)
.is_err());
assert!(Fragment::try_new(
&too_much_payload,
id,
u8::max_value(),
u8::max_value(),
None,
Some(link_id),
max_plaintext_size(),
)
.is_err());
}
}
#[cfg(test)]
mod fragment_header {
use super::*;
#[cfg(test)]
mod unlinked_fragmented_payload {
use super::*;
#[test]
fn can_be_converted_to_and_from_bytes_for_exact_number_of_bytes_provided() {
let fragmented_header = FragmentHeader::try_new(12345, 10, 5, None, None).unwrap();
let header_bytes = fragmented_header.to_bytes();
let (recovered_header, bytes_used) =
FragmentHeader::try_from_bytes(&header_bytes).unwrap();
assert_eq!(fragmented_header, recovered_header);
assert_eq!(UNLINKED_FRAGMENTED_HEADER_LEN, bytes_used);
}
#[test]
fn can_be_converted_to_and_from_bytes_for_more_than_required_number_of_bytes() {
let fragmented_header = FragmentHeader::try_new(12345, 10, 5, None, None).unwrap();
let mut header_bytes = fragmented_header.to_bytes();
header_bytes.append(vec![1, 2, 3, 4, 5].as_mut());
let (recovered_header, bytes_used) =
FragmentHeader::try_from_bytes(&header_bytes).unwrap();
assert_eq!(fragmented_header, recovered_header);
assert_eq!(UNLINKED_FRAGMENTED_HEADER_LEN, bytes_used);
}
#[test]
fn retrieval_from_bytes_fail_for_insufficient_number_of_bytes_provided() {
let fragmented_header = FragmentHeader::try_new(12345, 10, 5, None, None).unwrap();
let header_bytes = fragmented_header.to_bytes();
let header_bytes = &header_bytes[..header_bytes.len() - 1];
assert!(FragmentHeader::try_from_bytes(header_bytes).is_err())
}
#[test]
fn retrieval_from_bytes_fail_for_invalid_fragmentation_flag() {
let fragmented_header = FragmentHeader::try_new(10, 10, 5, None, None).unwrap();
let mut header_bytes_low = fragmented_header.to_bytes();
// clear the fragmentation flag
header_bytes_low[0] &= !(1 << 7);
let mut header_bytes_high = header_bytes_low;
// make sure first byte of id is non-empty (apart from the fragmentation flag)
// note for anyone reading this test in the future: choice of '3' here is arbitrary.
header_bytes_high[0] |= 1 << 3;
// This will have caused an error as there will be a value in the first byte
assert!(FragmentHeader::try_from_bytes(&header_bytes_high).is_err());
}
#[test]
fn retrieval_from_bytes_fail_for_invalid_link_flag() {
let fragmented_header = FragmentHeader::try_new(12345, 10, 5, None, None).unwrap();
let mut header_bytes = fragmented_header.to_bytes();
// set linked flag
header_bytes[6] |= 1 << 7;
assert!(FragmentHeader::try_from_bytes(&header_bytes).is_err());
}
#[test]
fn creation_of_header_fails_if_current_fragment_is_higher_than_total() {
assert!(FragmentHeader::try_new(12345, 10, 11, None, None).is_err());
}
#[test]
fn creation_of_header_fails_if_current_fragment_is_zero() {
assert!(FragmentHeader::try_new(12345, 10, 0, None, None).is_err());
}
#[test]
fn creation_of_header_fails_if_total_fragments_is_zero() {
assert!(FragmentHeader::try_new(12345, 0, 0, None, None).is_err());
}
#[test]
fn creation_of_header_fails_if_id_is_negative() {
assert!(FragmentHeader::try_new(-10, 10, 5, None, None).is_err());
}
#[test]
fn fragmented_header_cannot_be_created_with_zero_id() {
assert!(FragmentHeader::try_new(0, 10, 5, None, None).is_err());
assert!(FragmentHeader::try_new(12345, 10, 5, Some(0), None).is_err());
assert!(FragmentHeader::try_new(
12345,
u8::max_value(),
u8::max_value(),
None,
Some(0),
)
.is_err());
}
#[test]
fn retrieval_from_bytes_fail_if_current_fragment_is_higher_than_total() {
// manually create header to overwrite any constructor checks
let header = FragmentHeader {
id: 1234,
total_fragments: 10,
current_fragment: 11,
previous_fragments_set_id: None,
next_fragments_set_id: None,
};
let header_bytes = header.to_bytes();
assert!(FragmentHeader::try_from_bytes(&header_bytes).is_err());
}
#[test]
fn retrieval_from_bytes_fail_if_current_or_total_fragment_is_zero() {
// manually create header to overwrite any constructor checks
let header = FragmentHeader {
id: 1234,
total_fragments: 0,
current_fragment: 0,
previous_fragments_set_id: None,
next_fragments_set_id: None,
};
let header_bytes = header.to_bytes();
assert!(FragmentHeader::try_from_bytes(&header_bytes).is_err());
}
}
#[cfg(test)]
mod linked_fragmented_payload {
use super::*;
#[test]
fn cannot_be_linked_to_itself() {
assert!(FragmentHeader::try_new(12345, 10, 1, Some(12345), None).is_err());
assert!(FragmentHeader::try_new(12345, 10, 10, None, Some(12345)).is_err());
}
#[test]
fn can_only_be_pre_linked_for_first_fragment() {
assert!(FragmentHeader::try_new(12345, 10, 1, Some(1234), None).is_ok());
assert!(FragmentHeader::try_new(12345, 10, 2, Some(1234), None).is_err());
}
#[test]
fn can_only_be_post_linked_for_last_fragment() {
assert!(FragmentHeader::try_new(12345, 10, 10, None, Some(1234)).is_ok());
assert!(FragmentHeader::try_new(
12345,
u8::max_value(),
u8::max_value(),
None,
Some(1234),
)
.is_ok());
assert!(FragmentHeader::try_new(12345, 10, 2, Some(1234), None).is_err());
}
#[test]
fn pre_linked_can_be_converted_to_and_from_bytes_for_exact_number_of_bytes_provided() {
let fragmented_header =
FragmentHeader::try_new(12345, 10, 1, Some(1234), None).unwrap();
let header_bytes = fragmented_header.to_bytes();
let (recovered_header, bytes_used) =
FragmentHeader::try_from_bytes(&header_bytes).unwrap();
assert_eq!(fragmented_header, recovered_header);
assert_eq!(LINKED_FRAGMENTED_HEADER_LEN, bytes_used);
}
#[test]
fn pre_linked_can_be_converted_to_and_from_bytes_for_more_than_required_number_of_bytes() {
let fragmented_header =
FragmentHeader::try_new(12345, 10, 1, Some(1234), None).unwrap();
let mut header_bytes = fragmented_header.to_bytes();
header_bytes.append(vec![1, 2, 3, 4, 5].as_mut());
let (recovered_header, bytes_used) =
FragmentHeader::try_from_bytes(&header_bytes).unwrap();
assert_eq!(fragmented_header, recovered_header);
assert_eq!(LINKED_FRAGMENTED_HEADER_LEN, bytes_used);
}
#[test]
fn pre_linked_is_successfully_recovered_if_its_both_first_and_final_fragment() {
let fragmented_header = FragmentHeader::try_new(12345, 1, 1, Some(1234), None).unwrap();
let header_bytes = fragmented_header.to_bytes();
let (recovered_header, bytes_used) =
FragmentHeader::try_from_bytes(&header_bytes).unwrap();
assert_eq!(fragmented_header, recovered_header);
assert_eq!(LINKED_FRAGMENTED_HEADER_LEN, bytes_used);
}
#[test]
fn post_linked_can_be_converted_to_and_from_bytes_for_exact_number_of_bytes_provided() {
let fragmented_header =
FragmentHeader::try_new(12345, u8::max_value(), u8::max_value(), None, Some(1234))
.unwrap();
let header_bytes = fragmented_header.to_bytes();
let (recovered_header, bytes_used) =
FragmentHeader::try_from_bytes(&header_bytes).unwrap();
assert_eq!(fragmented_header, recovered_header);
assert_eq!(LINKED_FRAGMENTED_HEADER_LEN, bytes_used);
}
#[test]
fn post_linked_can_be_converted_to_and_from_bytes_for_more_than_required_number_of_bytes() {
let fragmented_header =
FragmentHeader::try_new(12345, u8::max_value(), u8::max_value(), None, Some(1234))
.unwrap();
let mut header_bytes = fragmented_header.to_bytes();
header_bytes.append(vec![1, 2, 3, 4, 5].as_mut());
let (recovered_header, bytes_used) =
FragmentHeader::try_from_bytes(&header_bytes).unwrap();
assert_eq!(fragmented_header, recovered_header);
assert_eq!(LINKED_FRAGMENTED_HEADER_LEN, bytes_used);
}
}
}