Skip to main content

blindplane_access/
codec.rs

1//! Bounded canonical encoding helpers.
2
3use crate::{ACCESS_FORMAT_VERSION, AccessError};
4
5pub(crate) const MAGIC: &[u8; 4] = b"BPAC";
6
7/// Bounds applied while decoding untrusted access objects.
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub struct AccessValidationPolicy {
10    /// Maximum UTF-8 byte length of an identifier.
11    pub max_identifier_bytes: usize,
12    /// Maximum number of capability rules in one policy.
13    pub max_rules: usize,
14    /// Maximum HPKE ciphertext size in one grant.
15    pub max_wrapped_grant_bytes: usize,
16    /// Maximum plaintext body size in one audit event.
17    pub max_event_body_bytes: usize,
18}
19
20impl Default for AccessValidationPolicy {
21    fn default() -> Self {
22        Self {
23            max_identifier_bytes: 255,
24            max_rules: 512,
25            max_wrapped_grant_bytes: 4 * 1024,
26            max_event_body_bytes: 8 * 1024 * 1024,
27        }
28    }
29}
30
31pub(crate) fn push_header(out: &mut Vec<u8>, tag: u8) {
32    out.extend_from_slice(MAGIC);
33    out.extend_from_slice(&ACCESS_FORMAT_VERSION.to_be_bytes());
34    out.push(tag);
35}
36
37pub(crate) fn push_bytes(out: &mut Vec<u8>, bytes: &[u8]) {
38    let length = u32::try_from(bytes.len()).expect("access values are bounded below u32::MAX");
39    out.extend_from_slice(&length.to_be_bytes());
40    out.extend_from_slice(bytes);
41}
42
43pub(crate) fn push_string(out: &mut Vec<u8>, value: &str) {
44    push_bytes(out, value.as_bytes());
45}
46
47pub(crate) struct Cursor<'a> {
48    bytes: &'a [u8],
49    offset: usize,
50}
51
52impl<'a> Cursor<'a> {
53    pub(crate) const fn new(bytes: &'a [u8]) -> Self {
54        Self { bytes, offset: 0 }
55    }
56
57    pub(crate) fn take_header(&mut self, expected_tag: u8) -> Result<(), AccessError> {
58        if self.take_exact(MAGIC.len())? != MAGIC {
59            return Err(AccessError::WrongObjectType);
60        }
61        let version = self.take_u16()?;
62        if version != ACCESS_FORMAT_VERSION {
63            return Err(AccessError::UnsupportedVersion(version));
64        }
65        if self.take_u8()? != expected_tag {
66            return Err(AccessError::WrongObjectType);
67        }
68        Ok(())
69    }
70
71    pub(crate) const fn is_empty(&self) -> bool {
72        self.offset == self.bytes.len()
73    }
74
75    pub(crate) fn take_exact(&mut self, length: usize) -> Result<&'a [u8], AccessError> {
76        let end = self
77            .offset
78            .checked_add(length)
79            .ok_or(AccessError::Truncated)?;
80        if end > self.bytes.len() {
81            return Err(AccessError::Truncated);
82        }
83        let value = &self.bytes[self.offset..end];
84        self.offset = end;
85        Ok(value)
86    }
87
88    pub(crate) fn take_u8(&mut self) -> Result<u8, AccessError> {
89        Ok(self.take_exact(1)?[0])
90    }
91
92    pub(crate) fn take_u16(&mut self) -> Result<u16, AccessError> {
93        Ok(u16::from_be_bytes(
94            self.take_exact(2)?.try_into().expect("two bytes"),
95        ))
96    }
97
98    pub(crate) fn take_u32(&mut self) -> Result<u32, AccessError> {
99        Ok(u32::from_be_bytes(
100            self.take_exact(4)?.try_into().expect("four bytes"),
101        ))
102    }
103
104    pub(crate) fn take_u64(&mut self) -> Result<u64, AccessError> {
105        Ok(u64::from_be_bytes(
106            self.take_exact(8)?.try_into().expect("eight bytes"),
107        ))
108    }
109
110    pub(crate) fn take_array32(&mut self) -> Result<[u8; 32], AccessError> {
111        Ok(self.take_exact(32)?.try_into().expect("32 bytes"))
112    }
113
114    pub(crate) fn take_array64(&mut self) -> Result<[u8; 64], AccessError> {
115        Ok(self.take_exact(64)?.try_into().expect("64 bytes"))
116    }
117
118    pub(crate) fn take_len(&mut self, limit: usize) -> Result<usize, AccessError> {
119        let length = self.take_u32()?;
120        let length = usize::try_from(length).expect("u32 fits into usize on supported targets");
121        if length > limit {
122            return Err(AccessError::LengthLimit(length));
123        }
124        Ok(length)
125    }
126
127    pub(crate) fn take_bytes(&mut self, limit: usize) -> Result<&'a [u8], AccessError> {
128        let length = self.take_len(limit)?;
129        self.take_exact(length)
130    }
131
132    pub(crate) fn take_string(&mut self, limit: usize) -> Result<String, AccessError> {
133        let bytes = self.take_bytes(limit)?;
134        core::str::from_utf8(bytes)
135            .map(str::to_owned)
136            .map_err(|_| AccessError::InvalidUtf8)
137    }
138}
139
140pub(crate) fn validate_identifier(value: &str, limit: usize) -> Result<(), AccessError> {
141    if value.is_empty() || value.len() > limit {
142        Err(AccessError::InvalidIdentifier)
143    } else {
144        Ok(())
145    }
146}