// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use aes_gcm::aead::{Aead, Payload};
use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
use chacha20poly1305::ChaCha20Poly1305;
use hmac::{Hmac, Mac};
use pin_project_lite::pin_project;
use rand::RngExt;
use rustfs_rio::{EtagResolvable, HashReaderDetector, HashReaderMut, Index, TryGetIndex, multipart_part_nonce};
use sha2::Sha256;
use std::cmp::min;
use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, ReadBuf};

const DARE_VERSION_20: u8 = 0x20;
const DARE_CIPHER_AES_256_GCM: u8 = 0x00;
const DARE_CIPHER_CHACHA20_POLY1305: u8 = 0x01;
const DARE_HEADER_SIZE: usize = 16;
const DARE_TAG_SIZE: usize = 16;
const DARE_PAYLOAD_SIZE: usize = 64 * 1024;

type HmacSha256 = Hmac<Sha256>;

enum DareDecryptCipher {
    Aes256Gcm(Box<Aes256Gcm>),
    ChaCha20Poly1305(ChaCha20Poly1305),
}

impl DareDecryptCipher {
    fn new(cipher_id: u8, key: [u8; 32]) -> io::Result<Self> {
        match cipher_id {
            DARE_CIPHER_AES_256_GCM => Aes256Gcm::new_from_slice(&key)
                .map(Box::new)
                .map(Self::Aes256Gcm)
                .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, format!("invalid AES-GCM key: {err}"))),
            DARE_CIPHER_CHACHA20_POLY1305 => ChaCha20Poly1305::new_from_slice(&key)
                .map(Self::ChaCha20Poly1305)
                .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, format!("invalid ChaCha20-Poly1305 key: {err}"))),
            _ => Err(io::Error::new(io::ErrorKind::InvalidData, "unsupported DARE cipher suite")),
        }
    }

    fn cipher_id(&self) -> u8 {
        match self {
            Self::Aes256Gcm(_) => DARE_CIPHER_AES_256_GCM,
            Self::ChaCha20Poly1305(_) => DARE_CIPHER_CHACHA20_POLY1305,
        }
    }

    fn decrypt(&self, nonce: &[u8; 12], ciphertext: &[u8], aad: &[u8]) -> Result<Vec<u8>, aes_gcm::aead::Error> {
        match self {
            Self::Aes256Gcm(cipher) => cipher.decrypt(&Nonce::from(*nonce), Payload { msg: ciphertext, aad }),
            Self::ChaCha20Poly1305(cipher) => {
                cipher.decrypt(&chacha20poly1305::Nonce::from(*nonce), Payload { msg: ciphertext, aad })
            }
        }
    }
}

#[derive(Clone, Copy)]
enum MultipartKeySource {
    LegacyNonce { base_nonce: [u8; 12] },
    ObjectKey { object_key: [u8; 32] },
}

pin_project! {
    pub struct EncryptReader<R> {
        #[pin]
        inner: R,
        cipher: Aes256Gcm,
        base_nonce: [u8; 12],
        sequence_number: u32,
        temp_buffer: Vec<u8>,
        read_buffer: Vec<u8>,
        output_buffer: Vec<u8>,
        output_pos: usize,
        finished: bool,
    }
}

impl<R> EncryptReader<R>
where
    R: AsyncRead + Unpin + Send + Sync,
{
    pub fn new_with_object_key(inner: R, object_key: [u8; 32]) -> Self {
        Self::new(inner, object_key, random_stream_nonce())
    }

    pub fn new(inner: R, key: [u8; 32], nonce: [u8; 12]) -> Self {
        Self::new_with_sequence(inner, key, nonce, 0)
    }

    pub fn new_with_sequence(inner: R, key: [u8; 32], nonce: [u8; 12], sequence_number: u32) -> Self {
        Self {
            inner,
            cipher: Aes256Gcm::new_from_slice(&key).expect("valid AES-256-GCM key"),
            base_nonce: nonce,
            sequence_number,
            temp_buffer: Vec::with_capacity(DARE_PAYLOAD_SIZE + 1),
            read_buffer: vec![0u8; DARE_PAYLOAD_SIZE + 1],
            output_buffer: Vec::new(),
            output_pos: 0,
            finished: false,
        }
    }

    pub fn new_multipart(inner: R, key: [u8; 32], base_nonce: [u8; 12], part_number: usize) -> Self {
        Self::new(inner, key, multipart_part_nonce(base_nonce, part_number))
    }

    pub fn new_multipart_with_object_key(inner: R, object_key: [u8; 32], part_number: u32) -> Self {
        Self::new(inner, derive_part_key(object_key, part_number), random_stream_nonce())
    }

    pub fn new_multipart_with_sequence(
        inner: R,
        key: [u8; 32],
        base_nonce: [u8; 12],
        part_number: usize,
        sequence_number: u32,
    ) -> Self {
        Self::new_with_sequence(inner, key, multipart_part_nonce(base_nonce, part_number), sequence_number)
    }
}

impl<R> AsyncRead for EncryptReader<R>
where
    R: AsyncRead + Unpin + Send + Sync,
{
    fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll<io::Result<()>> {
        let mut this = self.project();

        if *this.output_pos < this.output_buffer.len() {
            let to_copy = min(buf.remaining(), this.output_buffer.len() - *this.output_pos);
            buf.put_slice(&this.output_buffer[*this.output_pos..*this.output_pos + to_copy]);
            *this.output_pos += to_copy;
            if *this.output_pos == this.output_buffer.len() {
                this.output_buffer.clear();
                *this.output_pos = 0;
            }
            return Poll::Ready(Ok(()));
        }

        if *this.finished {
            return Poll::Ready(Ok(()));
        }

        loop {
            if this.temp_buffer.len() > DARE_PAYLOAD_SIZE {
                let package = build_dare_package(
                    this.cipher,
                    *this.sequence_number,
                    *this.base_nonce,
                    &this.temp_buffer[..DARE_PAYLOAD_SIZE],
                    false,
                )?;
                let carry = this.temp_buffer.split_off(DARE_PAYLOAD_SIZE);
                *this.temp_buffer = carry;
                *this.sequence_number = this.sequence_number.wrapping_add(1);
                *this.output_buffer = package;
                break;
            }

            let remaining = DARE_PAYLOAD_SIZE + 1 - this.temp_buffer.len();
            let mut read_buf = ReadBuf::new(&mut this.read_buffer[..remaining]);
            match this.inner.as_mut().poll_read(cx, &mut read_buf) {
                Poll::Pending => {
                    if this.temp_buffer.len() > DARE_PAYLOAD_SIZE {
                        continue;
                    }
                    return Poll::Pending;
                }
                Poll::Ready(Ok(())) => {
                    let n = read_buf.filled().len();
                    if n == 0 {
                        if this.temp_buffer.is_empty() {
                            *this.finished = true;
                            return Poll::Ready(Ok(()));
                        }

                        let package =
                            build_dare_package(this.cipher, *this.sequence_number, *this.base_nonce, this.temp_buffer, true)?;
                        this.temp_buffer.clear();
                        *this.sequence_number = this.sequence_number.wrapping_add(1);
                        *this.output_buffer = package;
                        *this.finished = true;
                        break;
                    }
                    this.temp_buffer.extend_from_slice(read_buf.filled());
                }
                Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
            }
        }

        let to_copy = min(buf.remaining(), this.output_buffer.len());
        buf.put_slice(&this.output_buffer[..to_copy]);
        *this.output_pos += to_copy;
        if *this.output_pos == this.output_buffer.len() {
            this.output_buffer.clear();
            *this.output_pos = 0;
        }

        Poll::Ready(Ok(()))
    }
}

impl<R> EtagResolvable for EncryptReader<R>
where
    R: EtagResolvable,
{
    fn try_resolve_etag(&mut self) -> Option<String> {
        self.inner.try_resolve_etag()
    }
}

impl<R> HashReaderDetector for EncryptReader<R>
where
    R: HashReaderDetector,
{
    fn is_hash_reader(&self) -> bool {
        self.inner.is_hash_reader()
    }

    fn as_hash_reader_mut(&mut self) -> Option<&mut dyn HashReaderMut> {
        self.inner.as_hash_reader_mut()
    }
}

impl<R> TryGetIndex for EncryptReader<R>
where
    R: TryGetIndex,
{
    fn try_get_index(&self) -> Option<&Index> {
        self.inner.try_get_index()
    }
}

pin_project! {
    pub struct DecryptReader<R> {
        #[pin]
        inner: R,
        key: [u8; 32],
        cipher: Option<DareDecryptCipher>,
        expected_base_nonce: Option<[u8; 12]>,
        sequence_number: u32,
        multipart_parts: Vec<usize>,
        current_part_index: usize,
        multipart_mode: bool,
        multipart_key_source: Option<MultipartKeySource>,
        header_buf: [u8; DARE_HEADER_SIZE],
        header_read: usize,
        ciphertext_buf: Vec<u8>,
        ciphertext_len: usize,
        ciphertext_read: usize,
        ref_nonce: Option<[u8; 12]>,
        finalized: bool,
        finished: bool,
        output_buffer: Vec<u8>,
        output_pos: usize,
    }
}

impl<R> DecryptReader<R>
where
    R: AsyncRead + Unpin + Send + Sync,
{
    pub fn new_with_object_key(inner: R, object_key: [u8; 32]) -> Self {
        Self::new_with_object_key_and_sequence(inner, object_key, 0)
    }

    pub fn new(inner: R, key: [u8; 32], nonce: [u8; 12]) -> Self {
        Self::new_with_sequence(inner, key, nonce, 0)
    }

    pub fn new_with_object_key_and_sequence(inner: R, object_key: [u8; 32], sequence_number: u32) -> Self {
        Self {
            inner,
            key: object_key,
            cipher: None,
            expected_base_nonce: None,
            sequence_number,
            multipart_parts: Vec::new(),
            current_part_index: 0,
            multipart_mode: false,
            multipart_key_source: None,
            header_buf: [0u8; DARE_HEADER_SIZE],
            header_read: 0,
            ciphertext_buf: Vec::new(),
            ciphertext_len: 0,
            ciphertext_read: 0,
            ref_nonce: None,
            finalized: false,
            finished: false,
            output_buffer: Vec::new(),
            output_pos: 0,
        }
    }

    pub fn new_with_sequence(inner: R, key: [u8; 32], nonce: [u8; 12], sequence_number: u32) -> Self {
        Self {
            inner,
            key,
            cipher: None,
            expected_base_nonce: Some(nonce),
            sequence_number,
            multipart_parts: Vec::new(),
            current_part_index: 0,
            multipart_mode: false,
            multipart_key_source: None,
            header_buf: [0u8; DARE_HEADER_SIZE],
            header_read: 0,
            ciphertext_buf: Vec::new(),
            ciphertext_len: 0,
            ciphertext_read: 0,
            ref_nonce: None,
            finalized: false,
            finished: false,
            output_buffer: Vec::new(),
            output_pos: 0,
        }
    }

    pub fn new_multipart(inner: R, key: [u8; 32], base_nonce: [u8; 12], multipart_parts: Vec<usize>) -> Self {
        Self::new_multipart_with_sequence(inner, key, base_nonce, multipart_parts, 0)
    }

    pub fn new_multipart_with_object_key(inner: R, object_key: [u8; 32], multipart_parts: Vec<usize>) -> Self {
        Self::new_multipart_with_object_key_and_sequence(inner, object_key, multipart_parts, 0)
    }

    pub fn new_multipart_with_sequence(
        inner: R,
        key: [u8; 32],
        base_nonce: [u8; 12],
        multipart_parts: Vec<usize>,
        sequence_number: u32,
    ) -> Self {
        let first_part = multipart_parts.first().copied().unwrap_or(1);
        Self {
            inner,
            key,
            cipher: None,
            expected_base_nonce: Some(multipart_part_nonce(base_nonce, first_part)),
            sequence_number,
            multipart_parts,
            current_part_index: 0,
            multipart_mode: true,
            multipart_key_source: Some(MultipartKeySource::LegacyNonce { base_nonce }),
            header_buf: [0u8; DARE_HEADER_SIZE],
            header_read: 0,
            ciphertext_buf: Vec::new(),
            ciphertext_len: 0,
            ciphertext_read: 0,
            ref_nonce: None,
            finalized: false,
            finished: false,
            output_buffer: Vec::new(),
            output_pos: 0,
        }
    }

    pub fn new_multipart_with_object_key_and_sequence(
        inner: R,
        object_key: [u8; 32],
        multipart_parts: Vec<usize>,
        sequence_number: u32,
    ) -> Self {
        let first_part = multipart_parts.first().copied().unwrap_or(1);
        let first_key = derive_part_key(object_key, first_part as u32);
        Self {
            inner,
            key: first_key,
            cipher: None,
            expected_base_nonce: None,
            sequence_number,
            multipart_parts,
            current_part_index: 0,
            multipart_mode: true,
            multipart_key_source: Some(MultipartKeySource::ObjectKey { object_key }),
            header_buf: [0u8; DARE_HEADER_SIZE],
            header_read: 0,
            ciphertext_buf: Vec::new(),
            ciphertext_len: 0,
            ciphertext_read: 0,
            ref_nonce: None,
            finalized: false,
            finished: false,
            output_buffer: Vec::new(),
            output_pos: 0,
        }
    }
}

impl<R> AsyncRead for DecryptReader<R>
where
    R: AsyncRead + Unpin + Send + Sync,
{
    fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll<io::Result<()>> {
        let mut this = self.project();

        if *this.output_pos < this.output_buffer.len() {
            let to_copy = min(buf.remaining(), this.output_buffer.len() - *this.output_pos);
            buf.put_slice(&this.output_buffer[*this.output_pos..*this.output_pos + to_copy]);
            *this.output_pos += to_copy;
            if *this.output_pos == this.output_buffer.len() {
                this.output_buffer.clear();
                *this.output_pos = 0;
            }
            return Poll::Ready(Ok(()));
        }

        loop {
            if *this.finished {
                return Poll::Ready(Ok(()));
            }

            if *this.finalized {
                if !this.multipart_parts.is_empty() && *this.current_part_index + 1 < this.multipart_parts.len() {
                    let next_part = this.multipart_parts[*this.current_part_index + 1];
                    *this.current_part_index += 1;
                    match this.multipart_key_source.as_ref().copied() {
                        Some(MultipartKeySource::LegacyNonce { base_nonce }) => {
                            *this.expected_base_nonce = Some(multipart_part_nonce(base_nonce, next_part));
                        }
                        Some(MultipartKeySource::ObjectKey { object_key }) => {
                            let part_key = derive_part_key(object_key, next_part as u32);
                            *this.key = part_key;
                            *this.cipher = None;
                            *this.expected_base_nonce = None;
                        }
                        None => {}
                    }
                    *this.sequence_number = 0;
                    *this.ref_nonce = None;
                    *this.finalized = false;
                    continue;
                }

                *this.finished = true;
                return Poll::Ready(Ok(()));
            }

            while *this.header_read < DARE_HEADER_SIZE {
                let mut read_buf = ReadBuf::new(&mut this.header_buf[*this.header_read..]);
                match this.inner.as_mut().poll_read(cx, &mut read_buf) {
                    Poll::Pending => return Poll::Pending,
                    Poll::Ready(Ok(())) => {
                        let n = read_buf.filled().len();
                        if n == 0 {
                            if *this.header_read == 0 {
                                // Clean EOF at a package boundary. Execution only reaches here
                                // with `finalized == false` (the finalized case is consumed at the
                                // loop top). If at least one package of the current part has been
                                // decrypted (`ref_nonce.is_some()`) but we never saw a final-flagged
                                // package, the final package is missing => DARE truncation. Zero
                                // decrypted packages (`ref_nonce.is_none()`) is a legitimately empty
                                // object (encrypt emits no packages for empty plaintext), so accept.
                                if this.ref_nonce.is_some() {
                                    return Poll::Ready(Err(io::Error::new(
                                        io::ErrorKind::UnexpectedEof,
                                        "DARE stream truncated before a finalized package",
                                    )));
                                }
                                *this.finished = true;
                                return Poll::Ready(Ok(()));
                            }
                            return Poll::Ready(Err(io::Error::new(
                                io::ErrorKind::UnexpectedEof,
                                "unexpected EOF while reading DARE header",
                            )));
                        }
                        *this.header_read += n;
                    }
                    Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
                }
            }

            let header = this.header_buf;
            if header[0] != DARE_VERSION_20 {
                return Poll::Ready(Err(io::Error::new(io::ErrorKind::InvalidData, "unsupported DARE version")));
            }
            if !matches!(header[1], DARE_CIPHER_AES_256_GCM | DARE_CIPHER_CHACHA20_POLY1305) {
                return Poll::Ready(Err(io::Error::new(io::ErrorKind::InvalidData, "unsupported DARE cipher suite")));
            }
            let payload_len = usize::from(u16::from_le_bytes([header[2], header[3]])) + 1;
            let package_len = payload_len + DARE_TAG_SIZE;
            if payload_len == 0 || payload_len > DARE_PAYLOAD_SIZE {
                return Poll::Ready(Err(io::Error::new(io::ErrorKind::InvalidData, "invalid DARE payload size")));
            }
            if !is_final_header(*header) && payload_len != DARE_PAYLOAD_SIZE {
                return Poll::Ready(Err(io::Error::new(
                    io::ErrorKind::InvalidData,
                    format!(
                        "non-final DARE package must carry a full 64KiB payload: cipher={}, payload_len={}, sequence_number={}, header={:02x?}",
                        header[1], payload_len, *this.sequence_number, header
                    ),
                )));
            }
            if this.ciphertext_buf.len() < package_len {
                this.ciphertext_buf.resize(package_len, 0);
            }
            *this.ciphertext_len = package_len;
            *this.ciphertext_read = 0;

            while *this.ciphertext_read < *this.ciphertext_len {
                let mut read_buf = ReadBuf::new(&mut this.ciphertext_buf[*this.ciphertext_read..*this.ciphertext_len]);
                match this.inner.as_mut().poll_read(cx, &mut read_buf) {
                    Poll::Pending => return Poll::Pending,
                    Poll::Ready(Ok(())) => {
                        let n = read_buf.filled().len();
                        if n == 0 {
                            return Poll::Ready(Err(io::Error::new(
                                io::ErrorKind::UnexpectedEof,
                                "unexpected EOF while reading DARE ciphertext",
                            )));
                        }
                        *this.ciphertext_read += n;
                    }
                    Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
                }
            }

            match open_dare_package(
                *this.key,
                this.cipher,
                *this.sequence_number,
                *this.expected_base_nonce,
                *header,
                &this.ciphertext_buf[..*this.ciphertext_len],
                this.ref_nonce,
            ) {
                Ok((plaintext, ref_nonce, finalized)) => {
                    *this.ref_nonce = Some(ref_nonce);
                    *this.finalized = finalized;
                    *this.header_read = 0;
                    *this.ciphertext_len = 0;
                    *this.ciphertext_read = 0;
                    *this.sequence_number = this.sequence_number.wrapping_add(1);
                    *this.output_buffer = plaintext;
                    let to_copy = min(buf.remaining(), this.output_buffer.len());
                    buf.put_slice(&this.output_buffer[..to_copy]);
                    *this.output_pos += to_copy;
                    if *this.output_pos == this.output_buffer.len() {
                        this.output_buffer.clear();
                        *this.output_pos = 0;
                    }
                    return Poll::Ready(Ok(()));
                }
                Err(err) => return Poll::Ready(Err(err)),
            }
        }
    }
}

impl<R> EtagResolvable for DecryptReader<R>
where
    R: EtagResolvable,
{
    fn try_resolve_etag(&mut self) -> Option<String> {
        self.inner.try_resolve_etag()
    }
}

impl<R> HashReaderDetector for DecryptReader<R>
where
    R: HashReaderDetector,
{
    fn is_hash_reader(&self) -> bool {
        self.inner.is_hash_reader()
    }

    fn as_hash_reader_mut(&mut self) -> Option<&mut dyn HashReaderMut> {
        self.inner.as_hash_reader_mut()
    }
}

impl<R> TryGetIndex for DecryptReader<R>
where
    R: TryGetIndex,
{
    fn try_get_index(&self) -> Option<&Index> {
        self.inner.try_get_index()
    }
}

fn build_dare_package(
    cipher: &Aes256Gcm,
    sequence_number: u32,
    base_nonce: [u8; 12],
    plaintext: &[u8],
    final_package: bool,
) -> io::Result<Vec<u8>> {
    let mut header = [0u8; DARE_HEADER_SIZE];
    header[0] = DARE_VERSION_20;
    header[1] = DARE_CIPHER_AES_256_GCM;
    header[2..4].copy_from_slice(
        &u16::try_from(plaintext.len() - 1)
            .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "DARE payload too large for Version 2.0 framing"))?
            .to_le_bytes(),
    );
    header[4..16].copy_from_slice(&base_nonce);
    if final_package {
        header[4] |= 0x80;
    } else {
        header[4] &= 0x7F;
    }

    let mut package_nonce = header[4..16].try_into().expect("nonce slice");
    xor_sequence_into_nonce(&mut package_nonce, sequence_number);
    let nonce = Nonce::try_from(package_nonce.as_slice())
        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid DARE nonce length"))?;
    let ciphertext = cipher
        .encrypt(
            &nonce,
            aes_gcm::aead::Payload {
                msg: plaintext,
                aad: &header[..4],
            },
        )
        .map_err(|err| io::Error::other(format!("failed to encrypt DARE package: {err}")))?;

    let mut package = Vec::with_capacity(DARE_HEADER_SIZE + ciphertext.len());
    package.extend_from_slice(&header);
    package.extend_from_slice(&ciphertext);
    Ok(package)
}

fn open_dare_package(
    key: [u8; 32],
    cipher: &mut Option<DareDecryptCipher>,
    sequence_number: u32,
    expected_base_nonce: Option<[u8; 12]>,
    header: [u8; DARE_HEADER_SIZE],
    ciphertext: &[u8],
    ref_nonce: &mut Option<[u8; 12]>,
) -> io::Result<(Vec<u8>, [u8; 12], bool)> {
    if header[0] != DARE_VERSION_20 {
        return Err(io::Error::new(io::ErrorKind::InvalidData, "unsupported DARE version"));
    }
    if !matches!(header[1], DARE_CIPHER_AES_256_GCM | DARE_CIPHER_CHACHA20_POLY1305) {
        return Err(io::Error::new(io::ErrorKind::InvalidData, "unsupported DARE cipher suite"));
    }
    if let Some(cipher) = cipher.as_ref() {
        if cipher.cipher_id() != header[1] {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "DARE package cipher suite does not match the stream reference cipher",
            ));
        }
    } else {
        *cipher = Some(DareDecryptCipher::new(header[1], key)?);
    }
    let header_nonce: [u8; 12] = header[4..16].try_into().expect("nonce slice");
    if let Some(expected_base_nonce) = expected_base_nonce {
        let masked_expected = apply_final_flag(expected_base_nonce, is_final_header(header));
        if header_nonce != masked_expected {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "DARE package nonce does not match the configured stream nonce",
            ));
        }
    }

    let current_ref = ref_nonce.get_or_insert(header_nonce);
    let expected_ref = apply_final_flag(*current_ref, is_final_header(header));
    if header_nonce != expected_ref {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            "DARE package nonce does not match the stream reference nonce",
        ));
    }

    let mut package_nonce = header_nonce;
    xor_sequence_into_nonce(&mut package_nonce, sequence_number);
    let plaintext = cipher
        .as_ref()
        .expect("DARE cipher is initialized")
        .decrypt(&package_nonce, ciphertext, &header[..4])
        .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, format!("DARE authentication failed: {err}")))?;

    Ok((plaintext, *current_ref, is_final_header(header)))
}

fn xor_sequence_into_nonce(nonce: &mut [u8; 12], sequence_number: u32) {
    let last = u32::from_le_bytes([nonce[8], nonce[9], nonce[10], nonce[11]]) ^ sequence_number;
    nonce[8..12].copy_from_slice(&last.to_le_bytes());
}

fn apply_final_flag(mut nonce: [u8; 12], final_package: bool) -> [u8; 12] {
    if final_package {
        nonce[0] |= 0x80;
    } else {
        nonce[0] &= 0x7F;
    }
    nonce
}

fn is_final_header(header: [u8; DARE_HEADER_SIZE]) -> bool {
    header[4] & 0x80 != 0
}

pub fn derive_part_key(object_key: [u8; 32], part_number: u32) -> [u8; 32] {
    let mut mac = HmacSha256::new_from_slice(&object_key).expect("HMAC-SHA256 accepts 32-byte object keys");
    mac.update(&part_number.to_le_bytes());

    let mut part_key = [0u8; 32];
    part_key.copy_from_slice(mac.finalize().into_bytes().as_slice());
    part_key
}

fn random_stream_nonce() -> [u8; 12] {
    let mut nonce = [0u8; 12];
    rand::rng().fill(&mut nonce);
    nonce
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io::Cursor;
    use tokio::io::AsyncReadExt;

    const DARE_PACKAGE_SIZE: usize = DARE_HEADER_SIZE + DARE_PAYLOAD_SIZE + DARE_TAG_SIZE;

    fn hex_encode(data: impl AsRef<[u8]>) -> String {
        hex_simd::encode_to_string(data, hex_simd::AsciiCase::Lower)
    }

    #[tokio::test]
    async fn decrypt_reader_can_start_from_non_zero_sequence_number() {
        let plaintext = vec![0xAB; DARE_PAYLOAD_SIZE * 2 + 19];
        let key_bytes = [0x44u8; 32];
        let base_nonce = [0x66u8; 12];

        let mut encrypted = Vec::new();
        EncryptReader::new(Cursor::new(plaintext.clone()), key_bytes, base_nonce)
            .read_to_end(&mut encrypted)
            .await
            .expect("encrypt plaintext");

        let tail = encrypted[DARE_PACKAGE_SIZE..].to_vec();
        let mut decrypted = Vec::new();
        DecryptReader::new_with_sequence(Cursor::new(tail), key_bytes, base_nonce, 1)
            .read_to_end(&mut decrypted)
            .await
            .expect("decrypt tail packages with non-zero sequence");

        assert_eq!(decrypted, plaintext[DARE_PAYLOAD_SIZE..]);
    }

    #[tokio::test]
    async fn singlepart_object_key_roundtrip_uses_header_nonce() {
        let object_key = [0x51u8; 32];
        let plaintext = vec![0xAC; DARE_PAYLOAD_SIZE + 17];

        let mut encrypted = Vec::new();
        EncryptReader::new_with_object_key(Cursor::new(plaintext.clone()), object_key)
            .read_to_end(&mut encrypted)
            .await
            .expect("encrypt object-key singlepart stream");

        let mut decrypted = Vec::new();
        DecryptReader::new_with_object_key(Cursor::new(encrypted), object_key)
            .read_to_end(&mut decrypted)
            .await
            .expect("decrypt object-key singlepart stream");

        assert_eq!(decrypted, plaintext);
    }

    #[tokio::test]
    async fn decrypt_reader_accepts_chacha20_poly1305_dare_v2_package() {
        let object_key = [0x72u8; 32];
        let plaintext = b"minio may emit ChaCha20-Poly1305 DARE packages".to_vec();

        let mut header = [0u8; DARE_HEADER_SIZE];
        header[0] = DARE_VERSION_20;
        header[1] = DARE_CIPHER_CHACHA20_POLY1305;
        header[2..4].copy_from_slice(&(u16::try_from(plaintext.len() - 1).expect("test payload fits")).to_le_bytes());
        header[4] = 0x80;
        header[5..16].copy_from_slice(&[0x27; 11]);

        let cipher = ChaCha20Poly1305::new_from_slice(&object_key).expect("valid test key");
        let nonce = chacha20poly1305::Nonce::try_from(&header[4..16]).expect("valid test nonce");
        let ciphertext = cipher
            .encrypt(
                &nonce,
                Payload {
                    msg: &plaintext,
                    aad: &header[..4],
                },
            )
            .expect("encrypt chacha DARE package");

        let mut encrypted = header.to_vec();
        encrypted.extend_from_slice(&ciphertext);

        let mut decrypted = Vec::new();
        DecryptReader::new_with_object_key(Cursor::new(encrypted), object_key)
            .read_to_end(&mut decrypted)
            .await
            .expect("decrypt chacha DARE package");

        assert_eq!(decrypted, plaintext);
    }

    #[test]
    fn derive_part_key_matches_minio_test_vectors() {
        assert_eq!(
            hex_encode(derive_part_key([0u8; 32], 0)),
            "aa7855e13839dd767cd5da7c1ff5036540c9264b7a803029315e55375287b4af"
        );
        assert_eq!(
            hex_encode(derive_part_key([0u8; 32], 1)),
            "a3e7181c6eed030fd52f79537c56c4d07da92e56d374ff1dd2043350785b37d8"
        );
        assert_eq!(
            hex_encode(derive_part_key([0u8; 32], 10_000)),
            "f86e65c396ed52d204ee44bd1a0bbd86eb8b01b7354e67a3b3ae0e34dd5bd115"
        );
    }

    #[tokio::test]
    async fn multipart_object_key_roundtrip_resets_sequence_per_part() {
        let object_key = [0x19u8; 32];
        let part_one_plaintext = vec![0xA1; DARE_PAYLOAD_SIZE + 31];
        let part_two_plaintext = vec![0xB2; DARE_PAYLOAD_SIZE * 2 + 7];

        let mut encrypted_one = Vec::new();
        EncryptReader::new_multipart_with_object_key(Cursor::new(part_one_plaintext.clone()), object_key, 1)
            .read_to_end(&mut encrypted_one)
            .await
            .expect("encrypt multipart part one with object key");

        let mut encrypted_two = Vec::new();
        EncryptReader::new_multipart_with_object_key(Cursor::new(part_two_plaintext.clone()), object_key, 2)
            .read_to_end(&mut encrypted_two)
            .await
            .expect("encrypt multipart part two with object key");

        let mut encrypted = encrypted_one.clone();
        encrypted.extend_from_slice(&encrypted_two);

        let mut decrypted = Vec::new();
        DecryptReader::new_multipart_with_object_key(Cursor::new(encrypted), object_key, vec![1, 2])
            .read_to_end(&mut decrypted)
            .await
            .expect("decrypt multipart object-key stream");

        let mut expected = part_one_plaintext;
        expected.extend_from_slice(&part_two_plaintext);

        assert_eq!(decrypted, expected);
        assert_ne!(encrypted_one, encrypted_two[..encrypted_one.len()]);
    }

    #[tokio::test]
    async fn decrypt_reader_rejects_stream_truncated_at_package_boundary() {
        // Plaintext spans three packages: two full non-final 64KiB packages plus a
        // short final-flagged package. Dropping the final package at the boundary
        // between complete packages must be detected as DARE truncation.
        let plaintext = vec![0xCD; DARE_PAYLOAD_SIZE * 2 + 19];
        let key_bytes = [0x21u8; 32];
        let base_nonce = [0x37u8; 12];

        let mut encrypted = Vec::new();
        EncryptReader::new(Cursor::new(plaintext.clone()), key_bytes, base_nonce)
            .read_to_end(&mut encrypted)
            .await
            .expect("encrypt multi-package plaintext");

        // Two full non-final packages precede the final package; truncate at their boundary.
        let truncated = encrypted[..DARE_PACKAGE_SIZE * 2].to_vec();
        assert!(truncated.len() < encrypted.len(), "truncation must drop the final package");

        let mut decrypted = Vec::new();
        let err = DecryptReader::new(Cursor::new(truncated), key_bytes, base_nonce)
            .read_to_end(&mut decrypted)
            .await
            .expect_err("truncated DARE stream must fail to decrypt");
        assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof);
    }

    #[tokio::test]
    async fn empty_plaintext_roundtrips_without_truncation_error() {
        // Empty plaintext emits zero DARE packages; decrypt must accept it as an
        // empty object rather than a truncated stream (no false positive).
        let key_bytes = [0x5Au8; 32];
        let base_nonce = [0x11u8; 12];

        let mut encrypted = Vec::new();
        EncryptReader::new(Cursor::new(Vec::new()), key_bytes, base_nonce)
            .read_to_end(&mut encrypted)
            .await
            .expect("encrypt empty plaintext");
        assert!(encrypted.is_empty(), "empty plaintext must emit no DARE packages");

        let mut decrypted = Vec::new();
        DecryptReader::new(Cursor::new(encrypted), key_bytes, base_nonce)
            .read_to_end(&mut decrypted)
            .await
            .expect("decrypt empty DARE stream");
        assert!(decrypted.is_empty());
    }

    #[tokio::test]
    async fn multi_package_stream_full_roundtrip_succeeds() {
        // Regression: a complete multi-package stream (ending with a final-flagged
        // package) still decrypts to the original plaintext.
        let plaintext = vec![0x7Eu8; DARE_PAYLOAD_SIZE * 2 + 123];
        let key_bytes = [0x93u8; 32];
        let base_nonce = [0x4Cu8; 12];

        let mut encrypted = Vec::new();
        EncryptReader::new(Cursor::new(plaintext.clone()), key_bytes, base_nonce)
            .read_to_end(&mut encrypted)
            .await
            .expect("encrypt multi-package plaintext");

        let mut decrypted = Vec::new();
        DecryptReader::new(Cursor::new(encrypted), key_bytes, base_nonce)
            .read_to_end(&mut decrypted)
            .await
            .expect("decrypt complete multi-package stream");
        assert_eq!(decrypted, plaintext);
    }

    #[tokio::test]
    async fn multipart_stream_full_roundtrip_and_truncation() {
        let object_key = [0x64u8; 32];
        let part_one_plaintext = vec![0xA1; DARE_PAYLOAD_SIZE + 31];
        // Part two spans multiple packages so it can be truncated within the part.
        let part_two_plaintext = vec![0xB2; DARE_PAYLOAD_SIZE * 2 + 7];

        let mut encrypted_one = Vec::new();
        EncryptReader::new_multipart_with_object_key(Cursor::new(part_one_plaintext.clone()), object_key, 1)
            .read_to_end(&mut encrypted_one)
            .await
            .expect("encrypt multipart part one");

        let mut encrypted_two = Vec::new();
        EncryptReader::new_multipart_with_object_key(Cursor::new(part_two_plaintext.clone()), object_key, 2)
            .read_to_end(&mut encrypted_two)
            .await
            .expect("encrypt multipart part two");

        let mut encrypted = encrypted_one.clone();
        encrypted.extend_from_slice(&encrypted_two);

        // Full multipart stream decrypts back to the concatenated plaintext.
        let mut decrypted = Vec::new();
        DecryptReader::new_multipart_with_object_key(Cursor::new(encrypted.clone()), object_key, vec![1, 2])
            .read_to_end(&mut decrypted)
            .await
            .expect("decrypt complete multipart stream");
        let mut expected = part_one_plaintext.clone();
        expected.extend_from_slice(&part_two_plaintext);
        assert_eq!(decrypted, expected);

        // Truncate within part two at its first internal package boundary, dropping its
        // final package. Part one still ends via `finalized`; the failure surfaces only
        // once part two hits EOF with at least one decrypted package and no final flag.
        let truncated = encrypted[..encrypted_one.len() + DARE_PACKAGE_SIZE].to_vec();
        assert!(truncated.len() < encrypted.len(), "truncation must drop part two's final package");

        let mut sink = Vec::new();
        let err = DecryptReader::new_multipart_with_object_key(Cursor::new(truncated), object_key, vec![1, 2])
            .read_to_end(&mut sink)
            .await
            .expect_err("multipart stream truncated within a part must fail");
        assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof);
    }
}
