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}