1use core::iter::FusedIterator;
2use core::ops::Range;
3
4use crate::{Error, LengthRequirement, Result};
5
6pub(crate) const AEAD_IV_LENGTH: usize = 12;
7pub(crate) const AEAD_TAG_LENGTH: usize = 16;
8pub(crate) const AEAD_MAX_SEGMENTS: u64 = 1 << 40;
9
10pub(crate) const FLOE_IV_LENGTH: usize = 32;
11pub(crate) const ENCODED_PARAMETERS_LENGTH: usize = 10;
12pub(crate) const HEADER_TAG_LENGTH: usize = 32;
13pub(crate) const HEADER_LENGTH: usize =
14 ENCODED_PARAMETERS_LENGTH + FLOE_IV_LENGTH + HEADER_TAG_LENGTH;
15
16const _: () = assert!(HEADER_LENGTH == 74, "unexpected size of HEADER");
17
18const _: () = assert!(
19 usize::BITS == 32 || usize::BITS == 64,
20 "fast-floe supports only 32-bit and 64-bit targets"
21);
22
23pub const SEGMENT_PREFIX_LENGTH: usize = 4;
25
26pub const SEGMENT_PAYLOAD_OFFSET: usize = SEGMENT_PREFIX_LENGTH + AEAD_IV_LENGTH;
30
31pub(crate) const SEGMENT_OVERHEAD: usize = SEGMENT_PAYLOAD_OFFSET + AEAD_TAG_LENGTH;
32
33const ROTATION_BITS: u8 = 20;
34const ROTATION_MASK: u64 = !((1_u64 << ROTATION_BITS) - 1);
35const FLOE_IV_LENGTH_U32: u32 = 32;
36const _: () = assert!(length_u32_to_usize(FLOE_IV_LENGTH_U32) == FLOE_IV_LENGTH);
37
38pub(crate) const SEGMENT_OVERHEAD_U32: u32 = 32;
39const _: () = assert!(length_u32_to_usize(SEGMENT_OVERHEAD_U32) == SEGMENT_OVERHEAD);
40
41#[inline]
43pub(crate) const fn length_u32_to_usize(value: u32) -> usize {
44 value as usize
45}
46
47#[inline]
50pub(crate) const fn length_usize_to_u64(value: usize) -> u64 {
51 value as u64
52}
53
54#[inline]
56pub(crate) fn length_u64_to_usize_saturating(value: u64) -> usize {
57 usize::try_from(value).unwrap_or(usize::MAX)
58}
59
60pub(crate) const HEADER_LENGTH_U64: u64 = length_usize_to_u64(HEADER_LENGTH);
61pub(crate) const SEGMENT_OVERHEAD_U64: u64 = length_usize_to_u64(SEGMENT_OVERHEAD);
62
63#[derive(Clone, Copy, Debug, Eq, PartialEq)]
72pub struct Parameters {
73 ciphertext_segment_length: u32,
74 #[cfg(test)]
75 rotation_mask: u64,
76}
77
78#[derive(Clone, Copy, Debug, Eq, PartialEq)]
80pub enum SegmentKind {
81 NonFinal,
83 Final,
85}
86
87impl SegmentKind {
88 #[must_use]
90 pub const fn is_final(self) -> bool {
91 matches!(self, Self::Final)
92 }
93
94 pub(crate) const fn indicator(self) -> u8 {
95 match self {
96 Self::NonFinal => 0,
97 Self::Final => 1,
98 }
99 }
100}
101
102#[derive(Clone, Copy, Debug, Eq, PartialEq)]
112pub struct MessageLayout {
113 parameters: Parameters,
114 plaintext_length: u64,
115 ciphertext_length: u64,
116 segment_count: u64,
117}
118
119impl MessageLayout {
120 #[must_use]
122 #[allow(clippy::missing_panics_doc)] pub fn final_segment(self) -> SegmentLayout {
124 self.segment_for_position(self.segment_count - 1)
125 .expect("every FLOE message layout contains one final segment")
126 }
127
128 #[must_use]
130 pub const fn parameters(self) -> Parameters {
131 self.parameters
132 }
133
134 #[must_use]
136 pub const fn plaintext_length(self) -> u64 {
137 self.plaintext_length
138 }
139
140 #[must_use]
142 pub const fn ciphertext_length(self) -> u64 {
143 self.ciphertext_length
144 }
145
146 #[must_use]
148 pub const fn segment_count(self) -> u64 {
149 self.segment_count
150 }
151
152 #[must_use]
157 pub fn segments(self) -> Segments {
158 Segments {
159 layout: self,
160 positions: 0..self.segment_count,
161 }
162 }
163
164 #[must_use]
169 pub fn segment_for_position(self, position: u64) -> Option<SegmentLayout> {
170 if position >= self.segment_count {
171 return None;
172 }
173
174 let plaintext_segment_length = self.parameters.plaintext_segment_length();
175 let plaintext_segment_length_u64 =
176 u64::from(self.parameters.plaintext_segment_length_u32());
177 let ciphertext_segment_length = self.parameters.ciphertext_segment_length();
178 let ciphertext_segment_length_u64 =
179 u64::from(self.parameters.ciphertext_segment_length_u32());
180 let plaintext_offset = position * plaintext_segment_length_u64;
181 let kind = if position + 1 == self.segment_count {
182 SegmentKind::Final
183 } else {
184 SegmentKind::NonFinal
185 };
186
187 let (plaintext_length, ciphertext_length) = match kind {
188 SegmentKind::NonFinal => (plaintext_segment_length, ciphertext_segment_length),
189 SegmentKind::Final => {
190 let plaintext_length =
191 usize::try_from(self.plaintext_length - plaintext_offset).ok()?;
192 (plaintext_length, SEGMENT_OVERHEAD + plaintext_length)
193 }
194 };
195
196 let ciphertext_offset = HEADER_LENGTH_U64 + position * ciphertext_segment_length_u64;
197
198 Some(SegmentLayout {
199 parameters: self.parameters,
200 position,
201 plaintext_offset,
202 plaintext_length,
203 ciphertext_offset,
204 ciphertext_length,
205 kind,
206 })
207 }
208
209 pub(crate) fn position_for_plaintext_offset(self, offset: u64) -> u64 {
213 offset / u64::from(self.parameters.plaintext_segment_length_u32())
214 }
215}
216
217impl IntoIterator for MessageLayout {
218 type Item = SegmentLayout;
219 type IntoIter = Segments;
220
221 fn into_iter(self) -> Self::IntoIter {
222 self.segments()
223 }
224}
225
226#[derive(Clone, Debug)]
231pub struct Segments {
232 layout: MessageLayout,
233 positions: Range<u64>,
234}
235
236impl Segments {
237 fn segment_at(&self, position: u64) -> SegmentLayout {
238 self.layout
239 .segment_for_position(position)
240 .expect("a layout iterator only produces valid segment positions")
241 }
242}
243
244impl Iterator for Segments {
245 type Item = SegmentLayout;
246
247 fn next(&mut self) -> Option<Self::Item> {
248 self.positions
249 .next()
250 .map(|position| self.segment_at(position))
251 }
252
253 fn size_hint(&self) -> (usize, Option<usize>) {
254 self.positions.size_hint()
255 }
256}
257
258impl DoubleEndedIterator for Segments {
259 fn next_back(&mut self) -> Option<Self::Item> {
260 self.positions
261 .next_back()
262 .map(|position| self.segment_at(position))
263 }
264}
265
266impl FusedIterator for Segments {}
267
268#[derive(Clone, Copy, Debug, Eq, PartialEq)]
270pub struct SegmentLayout {
271 parameters: Parameters,
272 position: u64,
273 plaintext_offset: u64,
274 plaintext_length: usize,
275 ciphertext_offset: u64,
276 ciphertext_length: usize,
277 kind: SegmentKind,
278}
279
280impl SegmentLayout {
281 #[must_use]
283 pub const fn position(self) -> u64 {
284 self.position
285 }
286
287 #[must_use]
289 pub const fn plaintext_offset(self) -> u64 {
290 self.plaintext_offset
291 }
292
293 #[must_use]
295 pub const fn plaintext_length(self) -> usize {
296 self.plaintext_length
297 }
298
299 #[must_use]
302 pub const fn ciphertext_offset(self) -> u64 {
303 self.ciphertext_offset
304 }
305
306 #[must_use]
308 pub const fn ciphertext_length(self) -> usize {
309 self.ciphertext_length
310 }
311
312 #[must_use]
314 pub const fn is_final(self) -> bool {
315 self.kind.is_final()
316 }
317
318 #[must_use]
320 pub const fn kind(self) -> SegmentKind {
321 self.kind
322 }
323
324 pub(crate) const fn parameters(self) -> Parameters {
325 self.parameters
326 }
327}
328
329#[derive(Clone, Copy, Debug, Eq, PartialEq)]
335pub struct SegmentFraming {
336 ciphertext_length: usize,
337 plaintext_length: usize,
338 kind: SegmentKind,
339}
340
341impl SegmentFraming {
342 pub fn decode(parameters: Parameters, prefix: [u8; SEGMENT_PREFIX_LENGTH]) -> Result<Self> {
352 let encoded = u32::from_be_bytes(prefix);
353
354 let (ciphertext_length, kind) = if encoded == u32::MAX {
355 (
356 parameters.ciphertext_segment_length(),
357 SegmentKind::NonFinal,
358 )
359 } else {
360 let maximum = parameters.ciphertext_segment_length_u32();
364 if !(SEGMENT_OVERHEAD_U32..=maximum).contains(&encoded) {
365 return Err(Error::InvalidCiphertextLength {
366 actual: length_u32_to_usize(encoded),
367 required: LengthRequirement::Between {
368 minimum: SEGMENT_OVERHEAD,
369 maximum: parameters.ciphertext_segment_length(),
370 },
371 });
372 }
373 (length_u32_to_usize(encoded), SegmentKind::Final)
374 };
375
376 Ok(Self {
377 ciphertext_length,
378 plaintext_length: ciphertext_length - SEGMENT_OVERHEAD,
379 kind,
380 })
381 }
382
383 #[must_use]
385 pub const fn ciphertext_length(self) -> usize {
386 self.ciphertext_length
387 }
388
389 #[must_use]
391 pub const fn plaintext_length(self) -> usize {
392 self.plaintext_length
393 }
394
395 #[must_use]
397 pub const fn is_final(self) -> bool {
398 self.kind.is_final()
399 }
400
401 #[must_use]
403 pub const fn kind(self) -> SegmentKind {
404 self.kind
405 }
406}
407
408impl Parameters {
409 pub const VALID_SEGMENT_LENGTHS: Range<u32> = 64..u32::MAX;
412
413 pub const SEGMENT_64_B: Self = Self::with_segment_length_unchecked(64);
415
416 pub const SEGMENT_4_KIB: Self = Self::with_segment_length_unchecked(4 * 1024);
418
419 pub const SEGMENT_1_MIB: Self = Self::with_segment_length_unchecked(1024 * 1024);
421
422 pub const SEGMENT_4_MIB: Self = Self::with_segment_length_unchecked(4 * 1024 * 1024);
424
425 pub const SEGMENT_5_MIB: Self = Self::with_segment_length_unchecked(5 * 1024 * 1024);
427
428 pub const SEGMENT_8_MIB: Self = Self::with_segment_length_unchecked(8 * 1024 * 1024);
430
431 pub const SEGMENT_16_MIB: Self = Self::with_segment_length_unchecked(16 * 1024 * 1024);
433
434 pub fn with_segment_length(segment_len: u32) -> Result<Self> {
442 if !Self::VALID_SEGMENT_LENGTHS.contains(&segment_len) {
443 return Err(Error::InvalidSegmentLength {
444 actual: segment_len,
445 });
446 }
447
448 Ok(Self::with_segment_length_unchecked(segment_len))
449 }
450
451 const fn with_segment_length_unchecked(segment_len: u32) -> Self {
452 Self {
453 ciphertext_segment_length: segment_len,
454 #[cfg(test)]
455 rotation_mask: ROTATION_MASK,
456 }
457 }
458
459 #[cfg(test)]
462 pub(crate) fn with_rotation_mask_for_test(segment_len: u32, rotation_mask: u64) -> Self {
463 let mut parameters = Self::with_segment_length_unchecked(segment_len);
464 parameters.rotation_mask = rotation_mask;
465 parameters
466 }
467
468 #[must_use]
470 #[inline]
471 pub const fn ciphertext_segment_length(self) -> usize {
472 length_u32_to_usize(self.ciphertext_segment_length)
473 }
474
475 pub(crate) const fn ciphertext_segment_length_u32(self) -> u32 {
476 self.ciphertext_segment_length
477 }
478
479 #[must_use]
482 #[inline]
483 pub const fn plaintext_segment_length(self) -> usize {
484 length_u32_to_usize(self.plaintext_segment_length_u32())
485 }
486
487 pub(crate) const fn plaintext_segment_length_u32(self) -> u32 {
488 self.ciphertext_segment_length - SEGMENT_OVERHEAD_U32
489 }
490
491 pub(crate) fn validate_ciphertext_segment_length(self, actual: usize) -> Result<()> {
495 let maximum = self.ciphertext_segment_length();
496 if (SEGMENT_OVERHEAD..=maximum).contains(&actual) {
497 Ok(())
498 } else {
499 Err(Error::InvalidCiphertextLength {
500 actual,
501 required: LengthRequirement::Between {
502 minimum: SEGMENT_OVERHEAD,
503 maximum,
504 },
505 })
506 }
507 }
508
509 pub fn plaintext_layout(self, plaintext_length: u64) -> Result<MessageLayout> {
517 let plaintext_segment_length = u64::from(self.plaintext_segment_length_u32());
518
519 let segment_count = plaintext_length.div_ceil(plaintext_segment_length).max(1);
521
522 if segment_count > AEAD_MAX_SEGMENTS {
523 return Err(Error::SegmentLimit);
524 }
525
526 let framing_length = segment_count
527 .checked_mul(SEGMENT_OVERHEAD_U64)
528 .ok_or(Error::LengthOverflow)?;
529
530 let ciphertext_length = HEADER_LENGTH_U64
531 .checked_add(plaintext_length)
532 .and_then(|length| length.checked_add(framing_length))
533 .ok_or(Error::LengthOverflow)?;
534
535 Ok(MessageLayout {
536 parameters: self,
537 plaintext_length,
538 ciphertext_length,
539 segment_count,
540 })
541 }
542
543 pub fn ciphertext_layout(self, ciphertext_length: u64) -> Result<MessageLayout> {
556 let body_length = ciphertext_length
557 .checked_sub(HEADER_LENGTH_U64)
558 .ok_or_else(|| Error::InvalidHeaderLength {
559 actual: length_u64_to_usize_saturating(ciphertext_length),
560 })?;
561
562 if body_length == 0 {
563 return Err(Error::Truncated);
564 }
565
566 let ciphertext_segment_length = u64::from(self.ciphertext_segment_length_u32());
567
568 let segment_count = body_length.div_ceil(ciphertext_segment_length);
569
570 if segment_count > AEAD_MAX_SEGMENTS {
571 return Err(Error::SegmentLimit);
572 }
573
574 let preceding_length = (segment_count - 1) * ciphertext_segment_length;
575 let final_length = body_length - preceding_length;
576
577 if final_length < SEGMENT_OVERHEAD_U64 {
578 return Err(Error::InvalidCiphertextLength {
579 actual: length_u64_to_usize_saturating(final_length),
580 required: LengthRequirement::Between {
581 minimum: SEGMENT_OVERHEAD,
582 maximum: self.ciphertext_segment_length(),
583 },
584 });
585 }
586
587 let framing_length = segment_count
588 .checked_mul(SEGMENT_OVERHEAD_U64)
589 .ok_or(Error::LengthOverflow)?;
590
591 let plaintext_length = body_length
592 .checked_sub(framing_length)
593 .ok_or(Error::LengthOverflow)?;
594
595 Ok(MessageLayout {
596 parameters: self,
597 plaintext_length,
598 ciphertext_length,
599 segment_count,
600 })
601 }
602
603 #[must_use]
605 #[inline]
606 pub(crate) const fn encode(self) -> [u8; ENCODED_PARAMETERS_LENGTH] {
607 let segment_length = self.ciphertext_segment_length.to_be_bytes();
608 let iv_length = FLOE_IV_LENGTH_U32.to_be_bytes();
609 [
610 0,
611 0,
612 segment_length[0],
613 segment_length[1],
614 segment_length[2],
615 segment_length[3],
616 iv_length[0],
617 iv_length[1],
618 iv_length[2],
619 iv_length[3],
620 ]
621 }
622
623 pub(crate) fn decode(encoded: [u8; ENCODED_PARAMETERS_LENGTH]) -> Result<Self> {
624 let mut seg_len_bytes = [0u8; 4];
625 seg_len_bytes.copy_from_slice(&encoded[2..6]);
626
627 let segment_length = u32::from_be_bytes(seg_len_bytes);
628 let parameters = Self::with_segment_length(segment_length)
629 .map_err(|_| Error::InvalidHeaderParameters)?;
630
631 if parameters.encode() == encoded {
632 Ok(parameters)
633 } else {
634 Err(Error::InvalidHeaderParameters)
635 }
636 }
637
638 #[inline]
639 #[cfg(not(test))]
640 pub(crate) const fn masked_position(self, position: u64) -> u64 {
641 let _ = self;
642 position & ROTATION_MASK
643 }
644
645 #[cfg(test)]
646 pub(crate) const fn masked_position(self, position: u64) -> u64 {
647 position & self.rotation_mask
648 }
649}
650
651#[cfg(test)]
652mod tests {
653 use super::*;
654
655 #[test]
656 fn parameter_encoding_matches_specification() {
657 assert_eq!(
662 Parameters::SEGMENT_4_KIB.encode(),
663 hex::decode("00000000100000000020").unwrap().as_slice()
664 );
665 assert_eq!(
666 Parameters::SEGMENT_1_MIB.encode(),
667 hex::decode("00000010000000000020").unwrap().as_slice()
668 );
669 assert_eq!(
670 Parameters::SEGMENT_4_KIB.ciphertext_segment_length(),
671 4 * 1024
672 );
673 assert_eq!(
674 Parameters::SEGMENT_1_MIB.ciphertext_segment_length(),
675 1024 * 1024
676 );
677 }
678
679 #[test]
680 fn parameters_accept_every_valid_segment_length() {
681 let valid_range = Parameters::VALID_SEGMENT_LENGTHS;
684 let first_valid = valid_range.start;
685 let last_valid = valid_range.end - 1;
686
687 for segment_length in [
688 first_valid,
689 first_valid + 1,
690 4 * 1024,
691 64 * 1024,
692 1_000_000,
693 1024 * 1024,
694 last_valid,
695 ] {
696 assert!(valid_range.contains(&segment_length));
697
698 let parameters = Parameters::with_segment_length(segment_length).unwrap();
700
701 assert_eq!(
704 parameters.ciphertext_segment_length(),
705 usize::try_from(segment_length).unwrap()
706 );
707 assert_eq!(Parameters::decode(parameters.encode()), Ok(parameters));
708 }
709 }
710
711 #[test]
712 fn parameters_reject_segment_lengths_outside_valid_range() {
713 let valid_range = Parameters::VALID_SEGMENT_LENGTHS;
716 let first_valid = valid_range.start;
717
718 for segment_length in [0, first_valid - 1, valid_range.end] {
720 assert!(!valid_range.contains(&segment_length));
721
722 assert_eq!(
725 Parameters::with_segment_length(segment_length),
726 Err(Error::InvalidSegmentLength {
727 actual: segment_length
728 })
729 );
730
731 let mut encoded = Parameters::SEGMENT_4_KIB.encode();
734 encoded[2..6].copy_from_slice(&segment_length.to_be_bytes());
735 assert_eq!(
736 Parameters::decode(encoded),
737 Err(Error::InvalidHeaderParameters)
738 );
739 }
740 }
741
742 #[test]
743 fn invalid_segment_length_error_names_the_value_and_bounds() {
744 let error = Parameters::with_segment_length(63).unwrap_err();
746
747 assert_eq!(error, Error::InvalidSegmentLength { actual: 63 });
750 let message = error.to_string();
751 assert!(message.contains("63"), "missing value: {message}");
752 assert!(message.contains("64"), "missing minimum: {message}");
753 assert!(
754 message.contains((u32::MAX - 1).to_string().as_str()),
755 "missing maximum: {message}"
756 );
757 assert!(
758 !message.contains("do not match"),
759 "reads as a mismatch: {message}"
760 );
761 }
762
763 #[test]
764 fn message_layouts_cover_plaintext_boundaries() {
765 let parameters = Parameters::SEGMENT_4_KIB;
767 let plaintext_segment_length =
768 u64::try_from(parameters.plaintext_segment_length()).unwrap();
769 let ciphertext_segment_length =
770 u64::try_from(parameters.ciphertext_segment_length()).unwrap();
771 let header_length = u64::try_from(HEADER_LENGTH).unwrap();
772 let overhead = u64::try_from(SEGMENT_OVERHEAD).unwrap();
773
774 for plaintext_length in [
775 0,
776 1,
777 plaintext_segment_length - 1,
778 plaintext_segment_length,
779 plaintext_segment_length + 1,
780 2 * plaintext_segment_length,
781 2 * plaintext_segment_length + 7,
782 ] {
783 let layout = parameters.plaintext_layout(plaintext_length).unwrap();
785 let expected_count = if plaintext_length == 0 {
786 1
787 } else {
788 (plaintext_length - 1) / plaintext_segment_length + 1
789 };
790
791 assert_eq!(layout.parameters(), parameters);
794 assert_eq!(layout.plaintext_length(), plaintext_length);
795 assert_eq!(layout.segment_count(), expected_count);
796 assert_eq!(
797 layout.ciphertext_length(),
798 header_length + plaintext_length + expected_count * overhead
799 );
800 assert_eq!(
801 parameters
802 .ciphertext_layout(layout.ciphertext_length())
803 .unwrap(),
804 layout
805 );
806
807 let segments: Vec<_> = layout.segments().collect();
809 assert_eq!(u64::try_from(segments.len()).unwrap(), expected_count);
810 assert_eq!(layout.into_iter().collect::<Vec<_>>(), segments);
811 assert_eq!(
812 layout.segments().next_back(),
813 layout.segment_for_position(expected_count - 1)
814 );
815
816 for segment in segments {
819 let position = segment.position();
820 assert_eq!(Some(segment), layout.segment_for_position(position));
821 assert_eq!(segment.position(), position);
822 assert_eq!(
823 segment.plaintext_offset(),
824 position * plaintext_segment_length
825 );
826 assert_eq!(
827 segment.ciphertext_offset(),
828 header_length + position * ciphertext_segment_length
829 );
830 assert_eq!(
831 u64::try_from(segment.ciphertext_length()).unwrap(),
832 u64::try_from(segment.plaintext_length()).unwrap() + overhead
833 );
834 assert_eq!(segment.is_final(), position + 1 == expected_count);
835 assert_eq!(
836 segment.kind(),
837 if segment.is_final() {
838 SegmentKind::Final
839 } else {
840 SegmentKind::NonFinal
841 }
842 );
843 }
844
845 assert_eq!(layout.segment_for_position(layout.segment_count()), None);
847 }
848 }
849
850 #[test]
851 fn message_layouts_enforce_segment_limit() {
852 let parameters = Parameters::SEGMENT_4_KIB;
854 let plaintext_segment_length =
855 u64::try_from(parameters.plaintext_segment_length()).unwrap();
856 let maximum_plaintext_length = AEAD_MAX_SEGMENTS * plaintext_segment_length;
857
858 let maximum = parameters
860 .plaintext_layout(maximum_plaintext_length)
861 .unwrap();
862
863 assert_eq!(maximum.segment_count(), AEAD_MAX_SEGMENTS);
865 assert!(
866 maximum
867 .segment_for_position(AEAD_MAX_SEGMENTS - 1)
868 .unwrap()
869 .is_final()
870 );
871
872 assert_eq!(
875 parameters.plaintext_layout(maximum_plaintext_length + 1),
876 Err(Error::SegmentLimit)
877 );
878 assert_eq!(
879 parameters.ciphertext_layout(maximum.ciphertext_length() + 1),
880 Err(Error::SegmentLimit)
881 );
882 }
883
884 #[test]
885 fn ciphertext_layouts_classify_short_lengths() {
886 let parameters = Parameters::SEGMENT_4_KIB;
889 let header_length = u64::try_from(HEADER_LENGTH).unwrap();
890 let overhead = u64::try_from(SEGMENT_OVERHEAD).unwrap();
891
892 assert!(matches!(
895 parameters.ciphertext_layout(header_length - 1),
896 Err(Error::InvalidHeaderLength { .. })
897 ));
898
899 assert_eq!(
902 parameters.ciphertext_layout(header_length),
903 Err(Error::Truncated)
904 );
905
906 assert!(matches!(
909 parameters.ciphertext_layout(header_length + overhead - 1),
910 Err(Error::InvalidCiphertextLength { .. })
911 ));
912
913 assert_eq!(
916 parameters
917 .ciphertext_layout(header_length + overhead)
918 .unwrap(),
919 parameters.plaintext_layout(0).unwrap()
920 );
921 }
922
923 #[test]
924 fn ciphertext_layout_accepts_length_valid_empty_final_segment() {
925 let parameters = Parameters::SEGMENT_4_KIB;
928 let header_length = u64::try_from(HEADER_LENGTH).unwrap();
929 let ciphertext_segment_length =
930 u64::try_from(parameters.ciphertext_segment_length()).unwrap();
931 let overhead = u64::try_from(SEGMENT_OVERHEAD).unwrap();
932 let ciphertext_length = header_length + ciphertext_segment_length + overhead;
933
934 let layout = parameters.ciphertext_layout(ciphertext_length).unwrap();
936
937 assert_eq!(layout.segment_count(), 2);
939 assert_eq!(
940 layout.plaintext_length(),
941 u64::try_from(parameters.plaintext_segment_length()).unwrap()
942 );
943
944 let first = layout.segment_for_position(0).unwrap();
945 assert!(!first.is_final());
946 assert_eq!(
947 first.ciphertext_length(),
948 parameters.ciphertext_segment_length()
949 );
950 assert_eq!(
951 first.plaintext_length(),
952 parameters.plaintext_segment_length()
953 );
954
955 let final_segment = layout.segment_for_position(1).unwrap();
956 assert!(final_segment.is_final());
957 assert_eq!(final_segment.plaintext_length(), 0);
958 assert_eq!(final_segment.ciphertext_length(), SEGMENT_OVERHEAD);
959
960 let canonical = parameters
963 .plaintext_layout(layout.plaintext_length())
964 .unwrap();
965 assert_eq!(canonical.segment_count(), 1);
966 assert_ne!(canonical.ciphertext_length(), ciphertext_length);
967 }
968
969 #[test]
970 fn segment_prefixes_classify_final_and_non_final_framing() {
971 let parameters = Parameters::SEGMENT_4_KIB;
973
974 let non_final = SegmentFraming::decode(parameters, u32::MAX.to_be_bytes()).unwrap();
976
977 assert_eq!(non_final.kind(), SegmentKind::NonFinal);
979 assert!(!non_final.is_final());
980 assert_eq!(
981 non_final.ciphertext_length(),
982 parameters.ciphertext_segment_length()
983 );
984 assert_eq!(
985 non_final.plaintext_length(),
986 parameters.plaintext_segment_length()
987 );
988
989 for encrypted_length in [
991 SEGMENT_OVERHEAD,
992 SEGMENT_OVERHEAD + 7,
993 parameters.ciphertext_segment_length(),
994 ] {
995 let prefix = u32::try_from(encrypted_length).unwrap().to_be_bytes();
997 let final_segment = SegmentFraming::decode(parameters, prefix).unwrap();
998
999 assert_eq!(final_segment.kind(), SegmentKind::Final);
1001 assert!(final_segment.is_final());
1002 assert_eq!(final_segment.ciphertext_length(), encrypted_length);
1003 assert_eq!(
1004 final_segment.plaintext_length(),
1005 encrypted_length - SEGMENT_OVERHEAD
1006 );
1007 }
1008 }
1009
1010 #[test]
1011 fn segment_framing_rejects_lengths_outside_final_range() {
1012 let parameters = Parameters::SEGMENT_4_KIB;
1015 for invalid in [
1016 SEGMENT_OVERHEAD - 1,
1017 parameters.ciphertext_segment_length() + 1,
1018 ] {
1019 let prefix = u32::try_from(invalid).unwrap().to_be_bytes();
1022 assert!(matches!(
1023 SegmentFraming::decode(parameters, prefix),
1024 Err(Error::InvalidCiphertextLength { .. })
1025 ));
1026 }
1027 }
1028
1029 #[test]
1030 fn segment_framing_rejects_prefix_whose_low_bits_look_valid() {
1031 let parameters = Parameters::SEGMENT_4_KIB;
1034 for forged in [69_632_u32, 1_048_576 + 4_096] {
1035 assert!(matches!(
1039 SegmentFraming::decode(parameters, forged.to_be_bytes()),
1040 Err(Error::InvalidCiphertextLength { .. })
1041 ));
1042 }
1043 }
1044
1045 #[test]
1046 fn segment_payload_offset_follows_prefix_and_nonce() {
1047 assert_eq!(
1050 SEGMENT_PAYLOAD_OFFSET,
1051 SEGMENT_PREFIX_LENGTH + AEAD_IV_LENGTH
1052 );
1053 }
1054
1055 #[test]
1056 fn masked_positions_rotate_at_specification_interval() {
1057 const ROTATION_INTERVAL: u64 = 1 << 20;
1058
1059 let parameters = Parameters::SEGMENT_4_KIB;
1061
1062 assert_eq!(parameters.masked_position(ROTATION_INTERVAL - 1), 0);
1065 assert_eq!(
1066 parameters.masked_position(ROTATION_INTERVAL),
1067 ROTATION_INTERVAL
1068 );
1069 assert_eq!(
1070 parameters.masked_position(AEAD_MAX_SEGMENTS - 1),
1071 AEAD_MAX_SEGMENTS - ROTATION_INTERVAL
1072 );
1073 }
1074}