1use crate::ascii_header::{encode_ascii_header_kv, parse_ascii_header_kv, HeaderField};
5use crate::crc::compute_crc32_iso_hdlc;
6use crate::encoding::Encoding;
7use crate::error::{CapsuleError, CapsuleResult};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
10pub struct Version(pub u16);
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct Prelude {
14 pub version: Version,
15 pub encoding: Encoding,
16 pub header_len: u16,
17 pub body_crc: u32,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct Capsule {
22 pub prelude: Prelude,
23 pub header_encoded: Vec<u8>,
24 pub payload_encoded: Vec<u8>,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct CapsuleDecoded {
29 pub prelude: Prelude,
30 pub header_encoded: Vec<u8>,
31 pub payload_encoded: Vec<u8>,
32 pub header_decoded: Vec<u8>,
33 pub payload_decoded: Vec<u8>,
34 pub header_fields: Option<Vec<HeaderField>>,
35}
36
37#[derive(Debug, Clone, Copy)]
38pub struct ParseOptions {
39 pub verify_crc: bool,
40 pub validate_encoding: bool,
41}
42
43impl Default for ParseOptions {
44 fn default() -> Self {
45 Self { verify_crc: true, validate_encoding: true }
46 }
47}
48
49fn parse_upper_hex_u16(bytes: &[u8]) -> Option<u16> {
50 if bytes.len() != 4 { return None; }
51 let mut v: u16 = 0;
52 for &b in bytes {
53 let d = match b {
54 b'0'..=b'9' => b - b'0',
55 b'A'..=b'F' => b - b'A' + 10,
56 _ => return None,
57 };
58 v = (v << 4) | d as u16;
59 }
60 Some(v)
61}
62
63fn parse_upper_hex_u32(bytes: &[u8]) -> Option<u32> {
64 if bytes.len() != 8 { return None; }
65 let mut v: u32 = 0;
66 for &b in bytes {
67 let d = match b {
68 b'0'..=b'9' => b - b'0',
69 b'A'..=b'F' => b - b'A' + 10,
70 _ => return None,
71 };
72 v = (v << 4) | d as u32;
73 }
74 Some(v)
75}
76
77fn ensure_ascii(which: &'static str, bytes: &[u8]) -> CapsuleResult<()> {
78 for (i, &b) in bytes.iter().enumerate() {
79 if b > 0x7F {
80 return Err(CapsuleError::NonAsciiByte { which, offset: i });
81 }
82 }
83 Ok(())
84}
85
86fn decode_base64(which: &'static str, bytes: &[u8]) -> CapsuleResult<Vec<u8>> {
87 data_encoding::BASE64
89 .decode(bytes)
90 .map_err(|e| CapsuleError::InvalidBase64 { which, message: e.to_string() })
91}
92
93fn validate_cbor(which: &'static str, bytes: &[u8]) -> CapsuleResult<()> {
94 use serde::de::IgnoredAny;
95
96 if bytes.is_empty() {
97 return Ok(());
98 }
99
100 let mut it = serde_cbor::Deserializer::from_slice(bytes).into_iter::<IgnoredAny>();
103 while let Some(item) = it.next() {
104 item.map_err(|e| CapsuleError::InvalidCbor {
105 which,
106 message: e.to_string(),
107 })?;
108 }
109
110 if it.byte_offset() != bytes.len() {
111 return Err(CapsuleError::InvalidCbor {
112 which,
113 message: "trailing bytes after CBOR sequence".to_string(),
114 });
115 }
116
117 Ok(())
118}
119
120impl Capsule {
121 pub fn parse(bytes: &[u8]) -> CapsuleResult<CapsuleDecoded> {
122 Self::parse_with_options(bytes, ParseOptions::default())
123 }
124
125 pub fn parse_with_options(bytes: &[u8], options: ParseOptions) -> CapsuleResult<CapsuleDecoded> {
126 if bytes.len() < 24 {
127 return Err(CapsuleError::FileTooShort(bytes.len()));
128 }
129
130 if &bytes[..7] != b"CAPSULE" {
131 return Err(CapsuleError::InvalidMagic);
132 }
133
134 let version_u16 = parse_upper_hex_u16(&bytes[7..11]).ok_or(CapsuleError::InvalidVersionField)?;
135 if version_u16 == 0 {
136 return Err(CapsuleError::ReservedVersion);
137 }
138
139 let encoding = Encoding::from_byte(bytes[11])?;
140 let header_len = parse_upper_hex_u16(&bytes[12..16]).ok_or(CapsuleError::InvalidHeaderLengthField)?;
141 let body_crc = parse_upper_hex_u32(&bytes[16..24]).ok_or(CapsuleError::InvalidBodyCrcField)?;
142
143 let header_len_usize = header_len as usize;
144 let available = bytes.len() - 24;
145 if header_len_usize > available {
146 return Err(CapsuleError::HeaderLengthExceedsAvailable { declared: header_len_usize, available });
147 }
148
149 let header_start = 24;
150 let header_end = 24 + header_len_usize;
151 let header_encoded = bytes[header_start..header_end].to_vec();
152 let payload_encoded = bytes[header_end..].to_vec();
153
154 if options.verify_crc {
155 let computed = compute_crc32_iso_hdlc(&bytes[24..]);
156 if computed != body_crc {
157 return Err(CapsuleError::CrcMismatch { declared: body_crc, computed });
158 }
159 }
160
161 let (header_decoded, payload_decoded, header_fields) = match encoding {
162 Encoding::Ascii => {
163 if options.validate_encoding {
164 ensure_ascii("header", &header_encoded)?;
165 ensure_ascii("payload", &payload_encoded)?;
166 }
167 let fields = parse_ascii_header_kv(&header_encoded)?;
168 (header_encoded.clone(), payload_encoded.clone(), Some(fields))
169 }
170 Encoding::Base64 => {
171 let header = decode_base64("header", &header_encoded)?;
172 let payload = decode_base64("payload", &payload_encoded)?;
173 (header, payload, None)
174 }
175 Encoding::Cbor => {
176 if options.validate_encoding {
177 validate_cbor("header", &header_encoded)?;
178 validate_cbor("payload", &payload_encoded)?;
179 }
180 (header_encoded.clone(), payload_encoded.clone(), None)
181 }
182 };
183
184 Ok(CapsuleDecoded {
185 prelude: Prelude {
186 version: Version(version_u16),
187 encoding,
188 header_len,
189 body_crc,
190 },
191 header_encoded,
192 payload_encoded,
193 header_decoded,
194 payload_decoded,
195 header_fields,
196 })
197 }
198
199 pub fn to_bytes(&self) -> CapsuleResult<Vec<u8>> {
200 let header_len = self.header_encoded.len();
201 if header_len > u16::MAX as usize {
202 return Err(CapsuleError::InvalidHeaderLengthField);
203 }
204
205 let mut out = Vec::with_capacity(24 + header_len + self.payload_encoded.len());
206 out.extend_from_slice(b"CAPSULE");
207
208 let version = self.prelude.version.0;
209 if version == 0 {
210 return Err(CapsuleError::ReservedVersion);
211 }
212
213 out.extend_from_slice(format!("{:04X}", version).as_bytes());
214 out.push(self.prelude.encoding.to_byte());
215 out.extend_from_slice(format!("{:04X}", header_len as u16).as_bytes());
216
217 let mut body = Vec::with_capacity(header_len + self.payload_encoded.len());
218 body.extend_from_slice(&self.header_encoded);
219 body.extend_from_slice(&self.payload_encoded);
220
221 let crc = compute_crc32_iso_hdlc(&body);
222 out.extend_from_slice(format!("{:08X}", crc).as_bytes());
223 out.extend_from_slice(&body);
224
225 Ok(out)
226 }
227
228 pub fn from_decoded(
229 version: Version,
230 encoding: Encoding,
231 header_fields: Option<&[HeaderField]>,
232 header_decoded: &[u8],
233 payload_decoded: &[u8],
234 ) -> CapsuleResult<Self> {
235 let header_encoded = match encoding {
236 Encoding::Ascii => {
237 let fields = header_fields.ok_or_else(|| {
238 CapsuleError::InvalidAsciiHeader("missing header fields for ASCII encoding".to_string())
239 })?;
240 encode_ascii_header_kv(fields)?
241 }
242 Encoding::Base64 => data_encoding::BASE64.encode(header_decoded).into_bytes(),
243 Encoding::Cbor => {
244 validate_cbor("header", header_decoded)?;
245 header_decoded.to_vec()
246 }
247 };
248
249 let payload_encoded = match encoding {
250 Encoding::Ascii => {
251 ensure_ascii("payload", payload_decoded)?;
252 payload_decoded.to_vec()
253 }
254 Encoding::Base64 => data_encoding::BASE64.encode(payload_decoded).into_bytes(),
255 Encoding::Cbor => {
256 validate_cbor("payload", payload_decoded)?;
257 payload_decoded.to_vec()
258 }
259 };
260
261 Ok(Self {
262 prelude: Prelude {
263 version,
264 encoding,
265 header_len: header_encoded.len() as u16,
266 body_crc: 0,
267 },
268 header_encoded,
269 payload_encoded,
270 })
271 }
272}