Skip to main content

wasefire_protocol/
bundle.rs

1// Copyright 2026 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use alloc::borrow::Cow;
16use alloc::vec::Vec;
17
18use wasefire_error::{Code, Error};
19use wasefire_wire::Wire;
20
21use crate::common::AppletKind;
22use crate::{Service as _, applet, platform};
23
24/// Bundle magic header.
25///
26/// This is meant to read as Wasefire in hexadecimal.
27pub const MAGIC: [u8; 4] = [0x3a, 0x5e, 0xf1, 0x2e];
28
29/// Bundle file extension.
30///
31/// This is meant to read as Wasefire File Bundle.
32pub const EXTENSION: &str = "wfb";
33
34/// Bundle content.
35///
36/// The version and type is encoded by the enum tag.
37#[derive(Debug, Wire)]
38#[wire(range = 2)]
39pub enum Bundle<'a> {
40    #[wire(tag = 0)]
41    Platform0(Platform0<'a>),
42    #[wire(tag = 1)]
43    Applet0(Applet0<'a>),
44}
45
46impl<'a> Bundle<'a> {
47    /// Encodes the bundle to its associated file content.
48    pub fn encode(&self) -> Result<Vec<u8>, Error> {
49        let mut content = MAGIC.to_vec();
50        wasefire_wire::encode_suffix(&mut content, self)?;
51        Ok(content)
52    }
53
54    /// Decodes a bundle from its associated file content.
55    pub fn decode(content: &'a [u8]) -> Result<Self, Error> {
56        let Some((&magic, content)) = content.split_first_chunk::<4>() else {
57            return Err(Error::user(Code::InvalidLength));
58        };
59        if magic != MAGIC {
60            return Err(Error::user(Code::InvalidState));
61        }
62        wasefire_wire::decode(content)
63    }
64
65    /// Assumes the bundle is for a platform.
66    pub fn platform(self) -> Result<Platform<'a>, Error> {
67        match self {
68            Bundle::Platform0(x) => Ok(Platform::V0(x)),
69            _ => Err(Error::user(Code::InvalidArgument)),
70        }
71    }
72
73    /// Assumes the bundle is for an applet.
74    pub fn applet(self) -> Result<Applet<'a>, Error> {
75        match self {
76            Bundle::Applet0(x) => Ok(Applet::V0(x)),
77            _ => Err(Error::user(Code::InvalidArgument)),
78        }
79    }
80}
81
82/// Platform update.
83#[derive(Debug, Wire)]
84pub struct Platform0<'a> {
85    /// Metadata about the platform.
86    ///
87    /// This metadata is redundant with the platform data for both sides, but contrary to the
88    /// platform data, this metadata can be accessed by generic tools (without knowning the
89    /// platform-specific data format).
90    pub metadata: platform::SideInfo0<'a>,
91
92    /// The platform data when stored on the A side.
93    pub side_a: Cow<'a, [u8]>,
94
95    /// The platform data when stored on the B side.
96    ///
97    /// This can be empty if the platform has only one side.
98    pub side_b: Cow<'a, [u8]>,
99}
100
101pub enum Platform<'a> {
102    V0(Platform0<'a>),
103}
104
105impl<'a> Platform<'a> {
106    /// Returns the device-specific payloads to update the platform.
107    pub fn payloads(&self) -> (Vec<u8>, Vec<u8>) {
108        (self.side_a().to_vec(), self.side_b().to_vec())
109    }
110
111    /// Returns the side A of the platform.
112    pub fn side_a(&self) -> &[u8] {
113        match self {
114            Platform::V0(x) => &x.side_a,
115        }
116    }
117
118    /// Returns the side B of the platform.
119    pub fn side_b(&self) -> &[u8] {
120        match self {
121            Platform::V0(x) => &x.side_b,
122        }
123    }
124}
125
126/// Applet install (and update).
127#[derive(Debug, Wire)]
128pub struct Applet0<'a> {
129    /// The applet kind.
130    ///
131    /// This is redundant with the applet data at this time (pulley and wasm), but provided for
132    /// convenience.
133    pub kind: AppletKind,
134
135    /// The applet metadata.
136    ///
137    /// The CLI or Web UI will suffix this metadata to the applet data in the format supported
138    /// by the device.
139    pub metadata: applet::Metadata0<'a>,
140
141    /// The applet data.
142    pub data: Cow<'a, [u8]>,
143}
144
145pub enum Applet<'a> {
146    V0(Applet0<'a>),
147}
148
149impl<'a> Applet<'a> {
150    /// Returns the device-specific payload to install the applet.
151    ///
152    /// The version is the protocol version of the device.
153    pub fn payload(&self, version: u32) -> Result<Vec<u8>, Error> {
154        let mut result = self.data().to_vec();
155        let len = result.len() as u32;
156        match self {
157            Applet::V0(x) => {
158                // TODO: We should convert the metadata.
159                if !crate::AppletMetadata0::VERSIONS.contains(version)? {
160                    return Err(Error::internal(Code::NotImplemented));
161                }
162                wasefire_wire::encode_suffix(&mut result, &x.metadata)?;
163            }
164        }
165        result.extend_from_slice(&len.to_be_bytes());
166        Ok(result)
167    }
168
169    /// Returns the kind of the applet.
170    pub fn kind(&self) -> AppletKind {
171        match self {
172            Applet::V0(x) => x.kind,
173        }
174    }
175
176    /// Returns the data of the applet.
177    pub fn data(&self) -> &[u8] {
178        match self {
179            Applet::V0(x) => &x.data,
180        }
181    }
182}