use core::iter::FusedIterator;
use core::ops::Range;
use crate::{Error, LengthRequirement, Result};
pub(crate) const AEAD_IV_LENGTH: usize = 12;
pub(crate) const AEAD_TAG_LENGTH: usize = 16;
pub(crate) const AEAD_MAX_SEGMENTS: u64 = 1 << 40;
pub(crate) const FLOE_IV_LENGTH: usize = 32;
pub(crate) const ENCODED_PARAMETERS_LENGTH: usize = 10;
pub(crate) const HEADER_TAG_LENGTH: usize = 32;
pub(crate) const HEADER_LENGTH: usize =
ENCODED_PARAMETERS_LENGTH + FLOE_IV_LENGTH + HEADER_TAG_LENGTH;
const _: () = assert!(HEADER_LENGTH == 74, "unexpected size of HEADER");
const _: () = assert!(
usize::BITS == 32 || usize::BITS == 64,
"fast-floe supports only 32-bit and 64-bit targets"
);
pub const SEGMENT_PREFIX_LENGTH: usize = 4;
pub const SEGMENT_PAYLOAD_OFFSET: usize = SEGMENT_PREFIX_LENGTH + AEAD_IV_LENGTH;
pub(crate) const SEGMENT_OVERHEAD: usize = SEGMENT_PAYLOAD_OFFSET + AEAD_TAG_LENGTH;
const ROTATION_BITS: u8 = 20;
const ROTATION_MASK: u64 = !((1_u64 << ROTATION_BITS) - 1);
const FLOE_IV_LENGTH_U32: u32 = 32;
const _: () = assert!(length_u32_to_usize(FLOE_IV_LENGTH_U32) == FLOE_IV_LENGTH);
pub(crate) const SEGMENT_OVERHEAD_U32: u32 = 32;
const _: () = assert!(length_u32_to_usize(SEGMENT_OVERHEAD_U32) == SEGMENT_OVERHEAD);
#[inline]
pub(crate) const fn length_u32_to_usize(value: u32) -> usize {
value as usize
}
#[inline]
pub(crate) const fn length_usize_to_u64(value: usize) -> u64 {
value as u64
}
#[inline]
pub(crate) fn length_u64_to_usize_saturating(value: u64) -> usize {
usize::try_from(value).unwrap_or(usize::MAX)
}
pub(crate) const HEADER_LENGTH_U64: u64 = length_usize_to_u64(HEADER_LENGTH);
pub(crate) const SEGMENT_OVERHEAD_U64: u64 = length_usize_to_u64(SEGMENT_OVERHEAD);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Parameters {
ciphertext_segment_length: u32,
#[cfg(test)]
rotation_mask: u64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SegmentKind {
NonFinal,
Final,
}
impl SegmentKind {
#[must_use]
pub const fn is_final(self) -> bool {
matches!(self, Self::Final)
}
pub(crate) const fn indicator(self) -> u8 {
match self {
Self::NonFinal => 0,
Self::Final => 1,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct MessageLayout {
parameters: Parameters,
plaintext_length: u64,
ciphertext_length: u64,
segment_count: u64,
}
impl MessageLayout {
#[must_use]
#[allow(clippy::missing_panics_doc)] pub fn final_segment(self) -> SegmentLayout {
self.segment_for_position(self.segment_count - 1)
.expect("every FLOE message layout contains one final segment")
}
#[must_use]
pub const fn parameters(self) -> Parameters {
self.parameters
}
#[must_use]
pub const fn plaintext_length(self) -> u64 {
self.plaintext_length
}
#[must_use]
pub const fn ciphertext_length(self) -> u64 {
self.ciphertext_length
}
#[must_use]
pub const fn segment_count(self) -> u64 {
self.segment_count
}
#[must_use]
pub fn segments(self) -> Segments {
Segments {
layout: self,
positions: 0..self.segment_count,
}
}
#[must_use]
pub fn segment_for_position(self, position: u64) -> Option<SegmentLayout> {
if position >= self.segment_count {
return None;
}
let plaintext_segment_length = self.parameters.plaintext_segment_length();
let plaintext_segment_length_u64 =
u64::from(self.parameters.plaintext_segment_length_u32());
let ciphertext_segment_length = self.parameters.ciphertext_segment_length();
let ciphertext_segment_length_u64 =
u64::from(self.parameters.ciphertext_segment_length_u32());
let plaintext_offset = position * plaintext_segment_length_u64;
let kind = if position + 1 == self.segment_count {
SegmentKind::Final
} else {
SegmentKind::NonFinal
};
let (plaintext_length, ciphertext_length) = match kind {
SegmentKind::NonFinal => (plaintext_segment_length, ciphertext_segment_length),
SegmentKind::Final => {
let plaintext_length =
usize::try_from(self.plaintext_length - plaintext_offset).ok()?;
(plaintext_length, SEGMENT_OVERHEAD + plaintext_length)
}
};
let ciphertext_offset = HEADER_LENGTH_U64 + position * ciphertext_segment_length_u64;
Some(SegmentLayout {
parameters: self.parameters,
position,
plaintext_offset,
plaintext_length,
ciphertext_offset,
ciphertext_length,
kind,
})
}
pub(crate) fn position_for_plaintext_offset(self, offset: u64) -> u64 {
offset / u64::from(self.parameters.plaintext_segment_length_u32())
}
}
impl IntoIterator for MessageLayout {
type Item = SegmentLayout;
type IntoIter = Segments;
fn into_iter(self) -> Self::IntoIter {
self.segments()
}
}
#[derive(Clone, Debug)]
pub struct Segments {
layout: MessageLayout,
positions: Range<u64>,
}
impl Segments {
fn segment_at(&self, position: u64) -> SegmentLayout {
self.layout
.segment_for_position(position)
.expect("a layout iterator only produces valid segment positions")
}
}
impl Iterator for Segments {
type Item = SegmentLayout;
fn next(&mut self) -> Option<Self::Item> {
self.positions
.next()
.map(|position| self.segment_at(position))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.positions.size_hint()
}
}
impl DoubleEndedIterator for Segments {
fn next_back(&mut self) -> Option<Self::Item> {
self.positions
.next_back()
.map(|position| self.segment_at(position))
}
}
impl FusedIterator for Segments {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SegmentLayout {
parameters: Parameters,
position: u64,
plaintext_offset: u64,
plaintext_length: usize,
ciphertext_offset: u64,
ciphertext_length: usize,
kind: SegmentKind,
}
impl SegmentLayout {
#[must_use]
pub const fn position(self) -> u64 {
self.position
}
#[must_use]
pub const fn plaintext_offset(self) -> u64 {
self.plaintext_offset
}
#[must_use]
pub const fn plaintext_length(self) -> usize {
self.plaintext_length
}
#[must_use]
pub const fn ciphertext_offset(self) -> u64 {
self.ciphertext_offset
}
#[must_use]
pub const fn ciphertext_length(self) -> usize {
self.ciphertext_length
}
#[must_use]
pub const fn is_final(self) -> bool {
self.kind.is_final()
}
#[must_use]
pub const fn kind(self) -> SegmentKind {
self.kind
}
pub(crate) const fn parameters(self) -> Parameters {
self.parameters
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SegmentFraming {
ciphertext_length: usize,
plaintext_length: usize,
kind: SegmentKind,
}
impl SegmentFraming {
pub fn decode(parameters: Parameters, prefix: [u8; SEGMENT_PREFIX_LENGTH]) -> Result<Self> {
let encoded = u32::from_be_bytes(prefix);
let (ciphertext_length, kind) = if encoded == u32::MAX {
(
parameters.ciphertext_segment_length(),
SegmentKind::NonFinal,
)
} else {
let maximum = parameters.ciphertext_segment_length_u32();
if !(SEGMENT_OVERHEAD_U32..=maximum).contains(&encoded) {
return Err(Error::InvalidCiphertextLength {
actual: length_u32_to_usize(encoded),
required: LengthRequirement::Between {
minimum: SEGMENT_OVERHEAD,
maximum: parameters.ciphertext_segment_length(),
},
});
}
(length_u32_to_usize(encoded), SegmentKind::Final)
};
Ok(Self {
ciphertext_length,
plaintext_length: ciphertext_length - SEGMENT_OVERHEAD,
kind,
})
}
#[must_use]
pub const fn ciphertext_length(self) -> usize {
self.ciphertext_length
}
#[must_use]
pub const fn plaintext_length(self) -> usize {
self.plaintext_length
}
#[must_use]
pub const fn is_final(self) -> bool {
self.kind.is_final()
}
#[must_use]
pub const fn kind(self) -> SegmentKind {
self.kind
}
}
impl Parameters {
pub const VALID_SEGMENT_LENGTHS: Range<u32> = 64..u32::MAX;
pub const SEGMENT_64_B: Self = Self::with_segment_length_unchecked(64);
pub const SEGMENT_4_KIB: Self = Self::with_segment_length_unchecked(4 * 1024);
pub const SEGMENT_1_MIB: Self = Self::with_segment_length_unchecked(1024 * 1024);
pub const SEGMENT_4_MIB: Self = Self::with_segment_length_unchecked(4 * 1024 * 1024);
pub const SEGMENT_5_MIB: Self = Self::with_segment_length_unchecked(5 * 1024 * 1024);
pub const SEGMENT_8_MIB: Self = Self::with_segment_length_unchecked(8 * 1024 * 1024);
pub const SEGMENT_16_MIB: Self = Self::with_segment_length_unchecked(16 * 1024 * 1024);
pub fn with_segment_length(segment_len: u32) -> Result<Self> {
if !Self::VALID_SEGMENT_LENGTHS.contains(&segment_len) {
return Err(Error::InvalidSegmentLength {
actual: segment_len,
});
}
Ok(Self::with_segment_length_unchecked(segment_len))
}
const fn with_segment_length_unchecked(segment_len: u32) -> Self {
Self {
ciphertext_segment_length: segment_len,
#[cfg(test)]
rotation_mask: ROTATION_MASK,
}
}
#[cfg(test)]
pub(crate) fn with_rotation_mask_for_test(segment_len: u32, rotation_mask: u64) -> Self {
let mut parameters = Self::with_segment_length_unchecked(segment_len);
parameters.rotation_mask = rotation_mask;
parameters
}
#[must_use]
#[inline]
pub const fn ciphertext_segment_length(self) -> usize {
length_u32_to_usize(self.ciphertext_segment_length)
}
pub(crate) const fn ciphertext_segment_length_u32(self) -> u32 {
self.ciphertext_segment_length
}
#[must_use]
#[inline]
pub const fn plaintext_segment_length(self) -> usize {
length_u32_to_usize(self.plaintext_segment_length_u32())
}
pub(crate) const fn plaintext_segment_length_u32(self) -> u32 {
self.ciphertext_segment_length - SEGMENT_OVERHEAD_U32
}
pub(crate) fn validate_ciphertext_segment_length(self, actual: usize) -> Result<()> {
let maximum = self.ciphertext_segment_length();
if (SEGMENT_OVERHEAD..=maximum).contains(&actual) {
Ok(())
} else {
Err(Error::InvalidCiphertextLength {
actual,
required: LengthRequirement::Between {
minimum: SEGMENT_OVERHEAD,
maximum,
},
})
}
}
pub fn plaintext_layout(self, plaintext_length: u64) -> Result<MessageLayout> {
let plaintext_segment_length = u64::from(self.plaintext_segment_length_u32());
let segment_count = plaintext_length.div_ceil(plaintext_segment_length).max(1);
if segment_count > AEAD_MAX_SEGMENTS {
return Err(Error::SegmentLimit);
}
let framing_length = segment_count
.checked_mul(SEGMENT_OVERHEAD_U64)
.ok_or(Error::LengthOverflow)?;
let ciphertext_length = HEADER_LENGTH_U64
.checked_add(plaintext_length)
.and_then(|length| length.checked_add(framing_length))
.ok_or(Error::LengthOverflow)?;
Ok(MessageLayout {
parameters: self,
plaintext_length,
ciphertext_length,
segment_count,
})
}
pub fn ciphertext_layout(self, ciphertext_length: u64) -> Result<MessageLayout> {
let body_length = ciphertext_length
.checked_sub(HEADER_LENGTH_U64)
.ok_or_else(|| Error::InvalidHeaderLength {
actual: length_u64_to_usize_saturating(ciphertext_length),
})?;
if body_length == 0 {
return Err(Error::Truncated);
}
let ciphertext_segment_length = u64::from(self.ciphertext_segment_length_u32());
let segment_count = body_length.div_ceil(ciphertext_segment_length);
if segment_count > AEAD_MAX_SEGMENTS {
return Err(Error::SegmentLimit);
}
let preceding_length = (segment_count - 1) * ciphertext_segment_length;
let final_length = body_length - preceding_length;
if final_length < SEGMENT_OVERHEAD_U64 {
return Err(Error::InvalidCiphertextLength {
actual: length_u64_to_usize_saturating(final_length),
required: LengthRequirement::Between {
minimum: SEGMENT_OVERHEAD,
maximum: self.ciphertext_segment_length(),
},
});
}
let framing_length = segment_count
.checked_mul(SEGMENT_OVERHEAD_U64)
.ok_or(Error::LengthOverflow)?;
let plaintext_length = body_length
.checked_sub(framing_length)
.ok_or(Error::LengthOverflow)?;
Ok(MessageLayout {
parameters: self,
plaintext_length,
ciphertext_length,
segment_count,
})
}
#[must_use]
#[inline]
pub(crate) const fn encode(self) -> [u8; ENCODED_PARAMETERS_LENGTH] {
let segment_length = self.ciphertext_segment_length.to_be_bytes();
let iv_length = FLOE_IV_LENGTH_U32.to_be_bytes();
[
0,
0,
segment_length[0],
segment_length[1],
segment_length[2],
segment_length[3],
iv_length[0],
iv_length[1],
iv_length[2],
iv_length[3],
]
}
pub(crate) fn decode(encoded: [u8; ENCODED_PARAMETERS_LENGTH]) -> Result<Self> {
let mut seg_len_bytes = [0u8; 4];
seg_len_bytes.copy_from_slice(&encoded[2..6]);
let segment_length = u32::from_be_bytes(seg_len_bytes);
let parameters = Self::with_segment_length(segment_length)
.map_err(|_| Error::InvalidHeaderParameters)?;
if parameters.encode() == encoded {
Ok(parameters)
} else {
Err(Error::InvalidHeaderParameters)
}
}
#[inline]
#[cfg(not(test))]
pub(crate) const fn masked_position(self, position: u64) -> u64 {
let _ = self;
position & ROTATION_MASK
}
#[cfg(test)]
pub(crate) const fn masked_position(self, position: u64) -> u64 {
position & self.rotation_mask
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parameter_encoding_matches_specification() {
assert_eq!(
Parameters::SEGMENT_4_KIB.encode(),
hex::decode("00000000100000000020").unwrap().as_slice()
);
assert_eq!(
Parameters::SEGMENT_1_MIB.encode(),
hex::decode("00000010000000000020").unwrap().as_slice()
);
assert_eq!(
Parameters::SEGMENT_4_KIB.ciphertext_segment_length(),
4 * 1024
);
assert_eq!(
Parameters::SEGMENT_1_MIB.ciphertext_segment_length(),
1024 * 1024
);
}
#[test]
fn parameters_accept_every_valid_segment_length() {
let valid_range = Parameters::VALID_SEGMENT_LENGTHS;
let first_valid = valid_range.start;
let last_valid = valid_range.end - 1;
for segment_length in [
first_valid,
first_valid + 1,
4 * 1024,
64 * 1024,
1_000_000,
1024 * 1024,
last_valid,
] {
assert!(valid_range.contains(&segment_length));
let parameters = Parameters::with_segment_length(segment_length).unwrap();
assert_eq!(
parameters.ciphertext_segment_length(),
usize::try_from(segment_length).unwrap()
);
assert_eq!(Parameters::decode(parameters.encode()), Ok(parameters));
}
}
#[test]
fn parameters_reject_segment_lengths_outside_valid_range() {
let valid_range = Parameters::VALID_SEGMENT_LENGTHS;
let first_valid = valid_range.start;
for segment_length in [0, first_valid - 1, valid_range.end] {
assert!(!valid_range.contains(&segment_length));
assert_eq!(
Parameters::with_segment_length(segment_length),
Err(Error::InvalidSegmentLength {
actual: segment_length
})
);
let mut encoded = Parameters::SEGMENT_4_KIB.encode();
encoded[2..6].copy_from_slice(&segment_length.to_be_bytes());
assert_eq!(
Parameters::decode(encoded),
Err(Error::InvalidHeaderParameters)
);
}
}
#[test]
fn invalid_segment_length_error_names_the_value_and_bounds() {
let error = Parameters::with_segment_length(63).unwrap_err();
assert_eq!(error, Error::InvalidSegmentLength { actual: 63 });
let message = error.to_string();
assert!(message.contains("63"), "missing value: {message}");
assert!(message.contains("64"), "missing minimum: {message}");
assert!(
message.contains((u32::MAX - 1).to_string().as_str()),
"missing maximum: {message}"
);
assert!(
!message.contains("do not match"),
"reads as a mismatch: {message}"
);
}
#[test]
fn message_layouts_cover_plaintext_boundaries() {
let parameters = Parameters::SEGMENT_4_KIB;
let plaintext_segment_length =
u64::try_from(parameters.plaintext_segment_length()).unwrap();
let ciphertext_segment_length =
u64::try_from(parameters.ciphertext_segment_length()).unwrap();
let header_length = u64::try_from(HEADER_LENGTH).unwrap();
let overhead = u64::try_from(SEGMENT_OVERHEAD).unwrap();
for plaintext_length in [
0,
1,
plaintext_segment_length - 1,
plaintext_segment_length,
plaintext_segment_length + 1,
2 * plaintext_segment_length,
2 * plaintext_segment_length + 7,
] {
let layout = parameters.plaintext_layout(plaintext_length).unwrap();
let expected_count = if plaintext_length == 0 {
1
} else {
(plaintext_length - 1) / plaintext_segment_length + 1
};
assert_eq!(layout.parameters(), parameters);
assert_eq!(layout.plaintext_length(), plaintext_length);
assert_eq!(layout.segment_count(), expected_count);
assert_eq!(
layout.ciphertext_length(),
header_length + plaintext_length + expected_count * overhead
);
assert_eq!(
parameters
.ciphertext_layout(layout.ciphertext_length())
.unwrap(),
layout
);
let segments: Vec<_> = layout.segments().collect();
assert_eq!(u64::try_from(segments.len()).unwrap(), expected_count);
assert_eq!(layout.into_iter().collect::<Vec<_>>(), segments);
assert_eq!(
layout.segments().next_back(),
layout.segment_for_position(expected_count - 1)
);
for segment in segments {
let position = segment.position();
assert_eq!(Some(segment), layout.segment_for_position(position));
assert_eq!(segment.position(), position);
assert_eq!(
segment.plaintext_offset(),
position * plaintext_segment_length
);
assert_eq!(
segment.ciphertext_offset(),
header_length + position * ciphertext_segment_length
);
assert_eq!(
u64::try_from(segment.ciphertext_length()).unwrap(),
u64::try_from(segment.plaintext_length()).unwrap() + overhead
);
assert_eq!(segment.is_final(), position + 1 == expected_count);
assert_eq!(
segment.kind(),
if segment.is_final() {
SegmentKind::Final
} else {
SegmentKind::NonFinal
}
);
}
assert_eq!(layout.segment_for_position(layout.segment_count()), None);
}
}
#[test]
fn message_layouts_enforce_segment_limit() {
let parameters = Parameters::SEGMENT_4_KIB;
let plaintext_segment_length =
u64::try_from(parameters.plaintext_segment_length()).unwrap();
let maximum_plaintext_length = AEAD_MAX_SEGMENTS * plaintext_segment_length;
let maximum = parameters
.plaintext_layout(maximum_plaintext_length)
.unwrap();
assert_eq!(maximum.segment_count(), AEAD_MAX_SEGMENTS);
assert!(
maximum
.segment_for_position(AEAD_MAX_SEGMENTS - 1)
.unwrap()
.is_final()
);
assert_eq!(
parameters.plaintext_layout(maximum_plaintext_length + 1),
Err(Error::SegmentLimit)
);
assert_eq!(
parameters.ciphertext_layout(maximum.ciphertext_length() + 1),
Err(Error::SegmentLimit)
);
}
#[test]
fn ciphertext_layouts_classify_short_lengths() {
let parameters = Parameters::SEGMENT_4_KIB;
let header_length = u64::try_from(HEADER_LENGTH).unwrap();
let overhead = u64::try_from(SEGMENT_OVERHEAD).unwrap();
assert!(matches!(
parameters.ciphertext_layout(header_length - 1),
Err(Error::InvalidHeaderLength { .. })
));
assert_eq!(
parameters.ciphertext_layout(header_length),
Err(Error::Truncated)
);
assert!(matches!(
parameters.ciphertext_layout(header_length + overhead - 1),
Err(Error::InvalidCiphertextLength { .. })
));
assert_eq!(
parameters
.ciphertext_layout(header_length + overhead)
.unwrap(),
parameters.plaintext_layout(0).unwrap()
);
}
#[test]
fn ciphertext_layout_accepts_length_valid_empty_final_segment() {
let parameters = Parameters::SEGMENT_4_KIB;
let header_length = u64::try_from(HEADER_LENGTH).unwrap();
let ciphertext_segment_length =
u64::try_from(parameters.ciphertext_segment_length()).unwrap();
let overhead = u64::try_from(SEGMENT_OVERHEAD).unwrap();
let ciphertext_length = header_length + ciphertext_segment_length + overhead;
let layout = parameters.ciphertext_layout(ciphertext_length).unwrap();
assert_eq!(layout.segment_count(), 2);
assert_eq!(
layout.plaintext_length(),
u64::try_from(parameters.plaintext_segment_length()).unwrap()
);
let first = layout.segment_for_position(0).unwrap();
assert!(!first.is_final());
assert_eq!(
first.ciphertext_length(),
parameters.ciphertext_segment_length()
);
assert_eq!(
first.plaintext_length(),
parameters.plaintext_segment_length()
);
let final_segment = layout.segment_for_position(1).unwrap();
assert!(final_segment.is_final());
assert_eq!(final_segment.plaintext_length(), 0);
assert_eq!(final_segment.ciphertext_length(), SEGMENT_OVERHEAD);
let canonical = parameters
.plaintext_layout(layout.plaintext_length())
.unwrap();
assert_eq!(canonical.segment_count(), 1);
assert_ne!(canonical.ciphertext_length(), ciphertext_length);
}
#[test]
fn segment_prefixes_classify_final_and_non_final_framing() {
let parameters = Parameters::SEGMENT_4_KIB;
let non_final = SegmentFraming::decode(parameters, u32::MAX.to_be_bytes()).unwrap();
assert_eq!(non_final.kind(), SegmentKind::NonFinal);
assert!(!non_final.is_final());
assert_eq!(
non_final.ciphertext_length(),
parameters.ciphertext_segment_length()
);
assert_eq!(
non_final.plaintext_length(),
parameters.plaintext_segment_length()
);
for encrypted_length in [
SEGMENT_OVERHEAD,
SEGMENT_OVERHEAD + 7,
parameters.ciphertext_segment_length(),
] {
let prefix = u32::try_from(encrypted_length).unwrap().to_be_bytes();
let final_segment = SegmentFraming::decode(parameters, prefix).unwrap();
assert_eq!(final_segment.kind(), SegmentKind::Final);
assert!(final_segment.is_final());
assert_eq!(final_segment.ciphertext_length(), encrypted_length);
assert_eq!(
final_segment.plaintext_length(),
encrypted_length - SEGMENT_OVERHEAD
);
}
}
#[test]
fn segment_framing_rejects_lengths_outside_final_range() {
let parameters = Parameters::SEGMENT_4_KIB;
for invalid in [
SEGMENT_OVERHEAD - 1,
parameters.ciphertext_segment_length() + 1,
] {
let prefix = u32::try_from(invalid).unwrap().to_be_bytes();
assert!(matches!(
SegmentFraming::decode(parameters, prefix),
Err(Error::InvalidCiphertextLength { .. })
));
}
}
#[test]
fn segment_framing_rejects_prefix_whose_low_bits_look_valid() {
let parameters = Parameters::SEGMENT_4_KIB;
for forged in [69_632_u32, 1_048_576 + 4_096] {
assert!(matches!(
SegmentFraming::decode(parameters, forged.to_be_bytes()),
Err(Error::InvalidCiphertextLength { .. })
));
}
}
#[test]
fn segment_payload_offset_follows_prefix_and_nonce() {
assert_eq!(
SEGMENT_PAYLOAD_OFFSET,
SEGMENT_PREFIX_LENGTH + AEAD_IV_LENGTH
);
}
#[test]
fn masked_positions_rotate_at_specification_interval() {
const ROTATION_INTERVAL: u64 = 1 << 20;
let parameters = Parameters::SEGMENT_4_KIB;
assert_eq!(parameters.masked_position(ROTATION_INTERVAL - 1), 0);
assert_eq!(
parameters.masked_position(ROTATION_INTERVAL),
ROTATION_INTERVAL
);
assert_eq!(
parameters.masked_position(AEAD_MAX_SEGMENTS - 1),
AEAD_MAX_SEGMENTS - ROTATION_INTERVAL
);
}
}