redoubt_codec_core/collections/
array.rs

1// Copyright (c) 2025-2026 Federico Hoerth <memparanoid@gmail.com>
2// SPDX-License-Identifier: GPL-3.0-only
3// See LICENSE in the repository root for full license text.
4
5use redoubt_zero::{FastZeroizable, ZeroizeMetadata};
6
7use crate::codec_buffer::RedoubtCodecBuffer;
8use crate::error::{DecodeError, EncodeError, OverflowError};
9use crate::traits::{
10    BytesRequired, Decode, DecodeSlice, Encode, EncodeSlice, PreAlloc, TryDecode, TryEncode,
11};
12use crate::zeroizing::Zeroizing;
13
14use super::helpers::{header_size, process_header, write_header};
15
16/// Cleanup function for encode errors. Marked #[cold] to keep it out of the hot path.
17#[cfg(feature = "zeroize")]
18#[cold]
19#[inline(never)]
20fn cleanup_encode_error<T: FastZeroizable + ZeroizeMetadata, const N: usize>(
21    arr: &mut [T; N],
22    buf: &mut RedoubtCodecBuffer,
23) {
24    arr.fast_zeroize();
25    buf.fast_zeroize();
26}
27
28/// Cleanup function for decode errors. Marked #[cold] to keep it out of the hot path.
29#[cfg(feature = "zeroize")]
30#[cold]
31#[inline(never)]
32fn cleanup_decode_error<T: FastZeroizable + ZeroizeMetadata, const N: usize>(
33    arr: &mut [T; N],
34    buf: &mut &mut [u8],
35) {
36    arr.fast_zeroize();
37    redoubt_util::fast_zeroize_slice(buf);
38}
39
40impl<T, const N: usize> BytesRequired for [T; N]
41where
42    T: BytesRequired,
43{
44    fn encode_bytes_required(&self) -> Result<usize, OverflowError> {
45        let mut bytes_required = header_size();
46
47        for elem in self.iter() {
48            let new_bytes_required = bytes_required.wrapping_add(elem.encode_bytes_required()?);
49
50            if new_bytes_required < bytes_required {
51                return Err(OverflowError {
52                    reason: "Array bytes_required overflow".into(),
53                });
54            }
55
56            bytes_required = new_bytes_required;
57        }
58
59        Ok(bytes_required)
60    }
61}
62
63impl<T, const N: usize> TryEncode for [T; N]
64where
65    T: EncodeSlice + BytesRequired + FastZeroizable + ZeroizeMetadata,
66{
67    fn try_encode_into(&mut self, buf: &mut RedoubtCodecBuffer) -> Result<(), EncodeError> {
68        let mut size = Zeroizing::new(N);
69        let mut bytes_required = Zeroizing::from(&mut self.encode_bytes_required()?);
70
71        write_header(buf, &mut size, &mut bytes_required)?;
72
73        T::encode_slice_into(self.as_mut_slice(), buf)
74    }
75}
76
77impl<T, const N: usize> Encode for [T; N]
78where
79    T: EncodeSlice + BytesRequired + FastZeroizable + ZeroizeMetadata,
80{
81    #[inline(always)]
82    fn encode_into(&mut self, buf: &mut RedoubtCodecBuffer) -> Result<(), EncodeError> {
83        let result = self.try_encode_into(buf);
84
85        #[cfg(feature = "zeroize")]
86        if result.is_err() {
87            cleanup_encode_error(self, buf);
88        } else {
89            self.fast_zeroize();
90        }
91
92        result
93    }
94}
95
96impl<T, const N: usize> EncodeSlice for [T; N]
97where
98    T: EncodeSlice + BytesRequired + FastZeroizable + ZeroizeMetadata,
99{
100    fn encode_slice_into(
101        slice: &mut [Self],
102        buf: &mut RedoubtCodecBuffer,
103    ) -> Result<(), EncodeError> {
104        for elem in slice.iter_mut() {
105            elem.encode_into(buf)?;
106        }
107
108        Ok(())
109    }
110}
111
112impl<T, const N: usize> TryDecode for [T; N]
113where
114    T: DecodeSlice + FastZeroizable + ZeroizeMetadata,
115{
116    #[inline(always)]
117    fn try_decode_from(&mut self, buf: &mut &mut [u8]) -> Result<(), DecodeError> {
118        let mut size = Zeroizing::from(&mut 0usize);
119
120        process_header(buf, &mut size)?;
121
122        // Validate that encoded size matches array size
123        if *size != N {
124            return Err(DecodeError::PreconditionViolated);
125        }
126
127        drop(size);
128
129        T::decode_slice_from(self.as_mut_slice(), buf)
130    }
131}
132
133impl<T, const N: usize> Decode for [T; N]
134where
135    T: DecodeSlice + FastZeroizable + ZeroizeMetadata,
136{
137    fn decode_from(&mut self, buf: &mut &mut [u8]) -> Result<(), DecodeError> {
138        let result = self.try_decode_from(buf);
139
140        #[cfg(feature = "zeroize")]
141        if result.is_err() {
142            cleanup_decode_error(self, buf);
143        }
144
145        result
146    }
147}
148
149impl<T, const N: usize> DecodeSlice for [T; N]
150where
151    T: DecodeSlice + FastZeroizable + ZeroizeMetadata,
152{
153    fn decode_slice_from(slice: &mut [Self], buf: &mut &mut [u8]) -> Result<(), DecodeError> {
154        for elem in slice.iter_mut() {
155            elem.decode_from(buf)?;
156        }
157
158        Ok(())
159    }
160}
161
162// PreAlloc for arrays - allows arrays to be used as Vec elements
163// Note: [T; N]: Default only works for N <= 32 in stable Rust
164
165impl<T: Default, const N: usize> PreAlloc for [T; N]
166where
167    Self: Default,
168{
169    /// Arrays cannot be zero-initialized (must use Default::default() for proper initialization)
170    const ZERO_INIT: bool = false;
171
172    fn prealloc(&mut self, _size: usize) {
173        // Arrays are fixed-size, nothing to preallocate
174    }
175}