redoubt_codec_core/collections/
option.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::{BytesRequired, Decode, Encode, TryDecode, TryEncode};
10use crate::zeroizing::Zeroizing;
11
12use super::helpers::{header_size, process_header, write_header};
13
14/// Cleanup function for encode errors. Marked #[cold] to keep it out of the hot path.
15#[cfg(feature = "zeroize")]
16#[cold]
17#[inline(never)]
18fn cleanup_encode_error<T: FastZeroizable + ZeroizeMetadata>(
19    opt: &mut Option<T>,
20    buf: &mut RedoubtCodecBuffer,
21) {
22    opt.fast_zeroize();
23    buf.fast_zeroize();
24}
25
26/// Cleanup function for decode errors. Marked #[cold] to keep it out of the hot path.
27#[cfg(feature = "zeroize")]
28#[cold]
29#[inline(never)]
30fn cleanup_decode_error<T: FastZeroizable + ZeroizeMetadata>(
31    opt: &mut Option<T>,
32    buf: &mut &mut [u8],
33) {
34    opt.fast_zeroize();
35    buf.fast_zeroize();
36}
37
38impl<T> BytesRequired for Option<T>
39where
40    T: BytesRequired,
41{
42    fn encode_bytes_required(&self) -> Result<usize, OverflowError> {
43        let header = header_size();
44
45        match self {
46            None => Ok(header),
47            Some(inner) => {
48                let inner_bytes = inner.encode_bytes_required()?;
49                let total = header.wrapping_add(inner_bytes);
50
51                if total < header {
52                    return Err(OverflowError {
53                        reason: "Option::encode_bytes_required overflow".into(),
54                    });
55                }
56
57                Ok(total)
58            }
59        }
60    }
61}
62
63impl<T> TryEncode for Option<T>
64where
65    T: Encode + BytesRequired + FastZeroizable + ZeroizeMetadata,
66{
67    fn try_encode_into(&mut self, buf: &mut RedoubtCodecBuffer) -> Result<(), EncodeError> {
68        let mut bytes_required = Zeroizing::from(&mut self.encode_bytes_required()?);
69
70        match self {
71            None => {
72                // size = 0 indicates None
73                let mut size = Zeroizing::from(&mut 0usize);
74                write_header(buf, &mut size, &mut bytes_required)?;
75            }
76            Some(inner) => {
77                // size = 1 indicates Some
78                let mut size = Zeroizing::from(&mut 1usize);
79                write_header(buf, &mut size, &mut bytes_required)?;
80
81                inner.encode_into(buf)?;
82            }
83        }
84
85        Ok(())
86    }
87}
88
89impl<T> Encode for Option<T>
90where
91    T: Encode + BytesRequired + FastZeroizable + ZeroizeMetadata,
92{
93    #[inline(always)]
94    fn encode_into(&mut self, buf: &mut RedoubtCodecBuffer) -> Result<(), EncodeError> {
95        let result = self.try_encode_into(buf);
96
97        #[cfg(feature = "zeroize")]
98        if result.is_err() {
99            cleanup_encode_error(self, buf);
100        } else {
101            self.fast_zeroize();
102        }
103
104        result
105    }
106}
107
108impl<T> TryDecode for Option<T>
109where
110    T: Decode + Default + FastZeroizable + ZeroizeMetadata,
111{
112    #[inline(always)]
113    fn try_decode_from(&mut self, buf: &mut &mut [u8]) -> Result<(), DecodeError> {
114        let mut size = Zeroizing::from(&mut 0);
115
116        process_header(buf, &mut size)?;
117
118        match *size {
119            0 => {
120                // None
121                *self = None;
122            }
123            1 => {
124                // Some
125                let mut inner = T::default();
126                inner.decode_from(buf)?;
127                *self = Some(inner);
128            }
129            _ => {
130                return Err(DecodeError::PreconditionViolated);
131            }
132        }
133
134        Ok(())
135    }
136}
137
138impl<T> Decode for Option<T>
139where
140    T: Decode + Default + FastZeroizable + ZeroizeMetadata,
141{
142    #[inline(always)]
143    fn decode_from(&mut self, buf: &mut &mut [u8]) -> Result<(), DecodeError> {
144        let result = self.try_decode_from(buf);
145
146        #[cfg(feature = "zeroize")]
147        if result.is_err() {
148            cleanup_decode_error(self, buf);
149        }
150
151        result
152    }
153}