1use alloc::vec::Vec;
2
3use fastboop_schema::bin::{
4 DEV_PROFILE_BIN_FORMAT_V0, DEV_PROFILE_BIN_V0_HEADER_LEN, DEV_PROFILE_BIN_V0_MAGIC,
5 DeviceProfileBin,
6};
7
8pub use fastboop_schema::*;
9
10#[derive(Debug)]
11pub enum DevProfileCodecError {
12 Decode(postcard::Error),
13 InvalidMagic,
14 UnsupportedFormatVersion(u16),
15}
16
17impl core::fmt::Display for DevProfileCodecError {
18 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
19 match self {
20 Self::Decode(err) => write!(f, "decode dev profile: {err}"),
21 Self::InvalidMagic => {
22 write!(
23 f,
24 "invalid dev profile magic (expected {DEV_PROFILE_BIN_V0_MAGIC:?})"
25 )
26 }
27 Self::UnsupportedFormatVersion(version) => {
28 write!(
29 f,
30 "unsupported dev profile format version {version} (expected {DEV_PROFILE_BIN_FORMAT_V0})"
31 )
32 }
33 }
34 }
35}
36
37impl From<postcard::Error> for DevProfileCodecError {
38 fn from(err: postcard::Error) -> Self {
39 Self::Decode(err)
40 }
41}
42
43pub fn decode_dev_profile(bytes: &[u8]) -> Result<DeviceProfile, DevProfileCodecError> {
44 let Some(format_version) = dev_profile_bin_header_version(bytes) else {
45 return Err(DevProfileCodecError::InvalidMagic);
46 };
47 if format_version != DEV_PROFILE_BIN_FORMAT_V0 {
48 return Err(DevProfileCodecError::UnsupportedFormatVersion(
49 format_version,
50 ));
51 }
52
53 let payload = &bytes[DEV_PROFILE_BIN_V0_HEADER_LEN..];
54 let profile: DeviceProfileBin = postcard::from_bytes(payload)?;
55 Ok(DeviceProfile::from(profile))
56}
57
58pub fn decode_dev_profile_prefix(
59 bytes: &[u8],
60) -> Result<(DeviceProfile, usize), DevProfileCodecError> {
61 let Some(format_version) = dev_profile_bin_header_version(bytes) else {
62 return Err(DevProfileCodecError::InvalidMagic);
63 };
64 if format_version != DEV_PROFILE_BIN_FORMAT_V0 {
65 return Err(DevProfileCodecError::UnsupportedFormatVersion(
66 format_version,
67 ));
68 }
69
70 let payload = &bytes[DEV_PROFILE_BIN_V0_HEADER_LEN..];
71 let (profile, remaining): (DeviceProfileBin, &[u8]) = postcard::take_from_bytes(payload)?;
72 let consumed = DEV_PROFILE_BIN_V0_HEADER_LEN
73 .checked_add(payload.len() - remaining.len())
74 .expect("dev profile consumed length overflow");
75 Ok((DeviceProfile::from(profile), consumed))
76}
77
78pub fn encode_dev_profile(profile: &DeviceProfile) -> Result<Vec<u8>, postcard::Error> {
79 let payload = postcard::to_allocvec(&DeviceProfileBin::from(profile.clone()))?;
80 let mut out = Vec::with_capacity(DEV_PROFILE_BIN_V0_HEADER_LEN + payload.len());
81 out.extend_from_slice(&DEV_PROFILE_BIN_V0_MAGIC);
82 out.extend_from_slice(&DEV_PROFILE_BIN_FORMAT_V0.to_le_bytes());
83 out.extend_from_slice(&payload);
84 Ok(out)
85}
86
87pub fn dev_profile_bin_header_version(bytes: &[u8]) -> Option<u16> {
88 if bytes.len() < DEV_PROFILE_BIN_V0_HEADER_LEN {
89 return None;
90 }
91 if bytes[..DEV_PROFILE_BIN_V0_MAGIC.len()] != DEV_PROFILE_BIN_V0_MAGIC {
92 return None;
93 }
94 Some(u16::from_le_bytes([
95 bytes[DEV_PROFILE_BIN_V0_MAGIC.len()],
96 bytes[DEV_PROFILE_BIN_V0_MAGIC.len() + 1],
97 ]))
98}