use aead::{
AeadCore, AeadInOut, Nonce, Tag,
array::{ArraySize, typenum::Unsigned},
};
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32, Unaligned};
use crate::{EncryptionError, result::SegmentDecodeError};
pub(crate) const SEGMENT_HEADER_LENGTH: usize = size_of::<u32>();
pub(crate) const NON_FINAL_SEGMENT_HEADER: u32 = u32::MAX;
#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C)]
struct InnerSegment<A>
where
A: AeadCore,
{
header: U32<BigEndian>,
nonce: Nonce<A>,
ciphertext: [u8],
}
pub struct Segment<'a, A>
where
A: AeadCore,
{
header: &'a U32<BigEndian>,
nonce: &'a Nonce<A>,
ciphertext: &'a [u8],
tag: &'a Tag<A>,
}
impl<'a, A> Segment<'a, A>
where
A: AeadCore,
{
pub fn from_bytes(bytes: &'a [u8]) -> Result<Self, SegmentDecodeError>
where
A: 'a,
<<A as AeadCore>::TagSize as ArraySize>::ArrayType<u8>: FromBytes + Immutable,
<<A as AeadCore>::NonceSize as ArraySize>::ArrayType<u8>: FromBytes + Immutable,
{
let invalid_length_err = || SegmentDecodeError::InvalidSliceLength {
expected: Segment::<A>::overhead(),
got: bytes.len(),
};
let (rest, tag) = Tag::<A>::ref_from_suffix(bytes).map_err(|_| invalid_length_err())?;
let InnerSegment { header, nonce, ciphertext } =
InnerSegment::<A>::ref_from_bytes(rest).map_err(|_| invalid_length_err())?;
let segment = Segment { header, nonce, ciphertext, tag };
if segment.is_final() {
let length: usize =
segment.header().try_into().map_err(|_| SegmentDecodeError::MalformedSegment)?;
if length != bytes.len() {
return Err(SegmentDecodeError::MalformedSegment);
}
}
Ok(segment)
}
pub fn header(&self) -> u32 {
self.header.get()
}
pub fn nonce(&self) -> &Nonce<A> {
self.nonce
}
pub fn tag(&self) -> &Tag<A> {
self.tag
}
pub fn ciphertext(&self) -> &[u8] {
self.ciphertext
}
pub fn is_final(&self) -> bool {
*self.header != NON_FINAL_SEGMENT_HEADER
}
pub const fn overhead() -> usize {
SEGMENT_HEADER_LENGTH + A::NonceSize::USIZE + A::TagSize::USIZE
}
pub const fn plaintext_size(&self) -> usize {
self.ciphertext.len()
}
}
pub(crate) struct SegmentMut<'a, A>
where
A: AeadInOut,
{
pub(crate) header: &'a mut U32<BigEndian>,
pub(crate) nonce: &'a mut Nonce<A>,
pub(crate) ciphertext: &'a mut [u8],
pub(crate) tag: &'a mut Tag<A>,
}
impl<'a, A> SegmentMut<'a, A>
where
A: AeadInOut + 'a,
{
pub(crate) const fn output_size(plaintext: &[u8]) -> usize {
plaintext.len() + Segment::<A>::overhead()
}
pub(crate) fn from_buffer_and_plaintext(
plaintext: &[u8],
buffer: &'a mut [u8],
) -> Result<Self, EncryptionError>
where
<<A as AeadCore>::TagSize as ArraySize>::ArrayType<u8>: FromBytes + IntoBytes + Immutable,
<<A as AeadCore>::NonceSize as ArraySize>::ArrayType<u8>:
FromBytes + IntoBytes + Unaligned + Immutable,
{
let buffer_length = buffer.len();
let invalid_length_err = || EncryptionError::InvalidBuffer {
expected: Self::output_size(plaintext),
got: buffer_length,
};
let (rest, tag) = Tag::<A>::mut_from_suffix(buffer).map_err(|_| invalid_length_err())?;
let InnerSegment { header, nonce, ciphertext } =
InnerSegment::<A>::mut_from_bytes(rest).map_err(|_| invalid_length_err())?;
let segment = SegmentMut { header, nonce, ciphertext, tag };
segment.ciphertext.copy_from_slice(plaintext);
Ok(segment)
}
}