Skip to main content

appcore_dnt/
compression.rs

1// =============================================================================
2//        #######
3//     ###       ###     F: compression.rs
4//    ##   ## ##   ##    P: AppCore-Runtime
5//         ## ##
6//                       C: 2026/08/02 10:29:16 by dnettoRaw
7//    ##   ## ##   ##    U: 2026/08/02 12:07:11 by dnettoRaw
8//      ###########      S: 1.0.1-rc.8
9// =============================================================================
10
11//! Optional payload compaction for DNT envelopes.
12
13use crate::flags::{validate_flags, DNT_FLAG_PAYLOAD_DEFLATE};
14use crate::{DntError, DntResult};
15use flate2::read::ZlibDecoder;
16use flate2::write::ZlibEncoder;
17use flate2::Compression;
18use std::io::{Read, Write};
19use zeroize::Zeroize;
20
21/// Payload storage transform recorded in the authenticated DNT header.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum DntCompression {
24    /// Store the codec output directly.
25    None,
26    /// Store the codec output through zlib-wrapped DEFLATE at balanced compression.
27    Deflate,
28}
29
30impl DntCompression {
31    pub(crate) fn from_flags(flags: u32) -> DntResult<Self> {
32        validate_flags(flags)?;
33        if flags & DNT_FLAG_PAYLOAD_DEFLATE != 0 {
34            return Ok(Self::Deflate);
35        }
36        Ok(Self::None)
37    }
38
39    /// Returns true when the payload is compacted before encryption.
40    pub fn is_compacted(self) -> bool {
41        self != Self::None
42    }
43}
44
45pub(crate) fn encode_payload(
46    flags: u32,
47    mut encoded_payload: Vec<u8>,
48    max_payload_bytes: Option<u64>,
49) -> DntResult<Vec<u8>> {
50    match DntCompression::from_flags(flags)? {
51        DntCompression::None => Ok(encoded_payload),
52        DntCompression::Deflate => {
53            let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
54            let write_result = encoder.write_all(&encoded_payload);
55            encoded_payload.zeroize();
56            write_result.map_err(|_| DntError::CodecFailed)?;
57            let compacted = encoder.finish().map_err(|_| DntError::CodecFailed)?;
58            enforce_max(compacted.len() as u64, max_payload_bytes)?;
59            Ok(compacted)
60        }
61    }
62}
63
64pub(crate) fn decode_payload(
65    flags: u32,
66    stored_payload: &[u8],
67    max_payload_bytes: Option<u64>,
68) -> DntResult<Vec<u8>> {
69    match DntCompression::from_flags(flags)? {
70        DntCompression::None => Ok(stored_payload.to_vec()),
71        DntCompression::Deflate => inflate_bounded(stored_payload, max_payload_bytes),
72    }
73}
74
75fn inflate_bounded(input: &[u8], max_payload_bytes: Option<u64>) -> DntResult<Vec<u8>> {
76    let Some(max_payload_bytes) = max_payload_bytes else {
77        return Err(DntError::PayloadTooLarge);
78    };
79    let read_limit = max_payload_bytes.saturating_add(1);
80    let mut decoder = ZlibDecoder::new(input).take(read_limit);
81    let mut output = Vec::new();
82    if decoder.read_to_end(&mut output).is_err() {
83        output.zeroize();
84        return Err(DntError::CodecFailed);
85    }
86    if let Err(error) = enforce_max(output.len() as u64, Some(max_payload_bytes)) {
87        output.zeroize();
88        return Err(error);
89    }
90    Ok(output)
91}
92
93fn enforce_max(actual: u64, max: Option<u64>) -> DntResult<()> {
94    if max.is_some_and(|max| actual > max) {
95        return Err(DntError::PayloadTooLarge);
96    }
97    Ok(())
98}