Skip to main content

fastboop_schema/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3extern crate alloc;
4
5use alloc::collections::BTreeMap;
6use alloc::string::String;
7use alloc::vec::Vec;
8pub use gibblox_pipeline::{
9    PipelineSource as BootProfileArtifactSource,
10    PipelineSourceAndroidSparseImgSource as BootProfileArtifactSourceAndroidSparseImgSource,
11    PipelineSourceCasync as BootProfileArtifactSourceCasync,
12    PipelineSourceCasyncSource as BootProfileArtifactSourceCasyncSource,
13    PipelineSourceFileSource as BootProfileArtifactSourceFileSource,
14    PipelineSourceGpt as BootProfileArtifactSourceGpt,
15    PipelineSourceGptSource as BootProfileArtifactSourceGptSource,
16    PipelineSourceHttpSource as BootProfileArtifactSourceHttpSource,
17    PipelineSourceMbr as BootProfileArtifactSourceMbr,
18    PipelineSourceMbrSource as BootProfileArtifactSourceMbrSource,
19    PipelineSourceTar as BootProfileArtifactSourceTar,
20    PipelineSourceTarSource as BootProfileArtifactSourceTarSource,
21    PipelineSourceXzSource as BootProfileArtifactSourceXzSource,
22};
23use serde::{Deserialize, Serialize};
24
25#[cfg(feature = "schema")]
26use schemars::JsonSchema;
27
28/// DevPro v0 schema root.
29#[derive(Clone, Debug, Deserialize, Serialize)]
30#[cfg_attr(feature = "schema", derive(JsonSchema))]
31#[serde(deny_unknown_fields)]
32pub struct DeviceProfile {
33    pub id: String,
34    pub display_name: Option<String>,
35    pub devicetree_name: String,
36    pub r#match: Vec<MatchRule>,
37    pub probe: Vec<ProbeStep>,
38    pub boot: Boot,
39}
40
41#[derive(Clone, Debug, Deserialize, Serialize)]
42#[cfg_attr(feature = "schema", derive(JsonSchema))]
43pub struct MatchRule {
44    pub fastboot: FastbootMatch,
45}
46
47#[derive(Clone, Debug, Deserialize, Serialize)]
48#[cfg_attr(feature = "schema", derive(JsonSchema))]
49pub struct FastbootMatch {
50    pub vid: u16,
51    pub pid: u16,
52}
53
54#[derive(Clone, Debug, Deserialize, Serialize)]
55#[cfg_attr(feature = "schema", derive(JsonSchema))]
56pub enum ProbeStep {
57    #[serde(untagged)]
58    FastbootGetvarEq(FastbootGetvarEq),
59    #[serde(untagged)]
60    FastbootGetvarStartsWith(FastbootGetvarStartsWith),
61    #[serde(untagged)]
62    FastbootGetvarNotEq(FastbootGetvarNotEq),
63    #[serde(untagged)]
64    FastbootGetvarExists(FastbootGetvarExists),
65    #[serde(untagged)]
66    FastbootGetvarNotExists(FastbootGetvarNotExists),
67}
68
69#[derive(Clone, Debug, Deserialize, Serialize)]
70#[cfg_attr(feature = "schema", derive(JsonSchema))]
71pub struct FastbootGetvarEq {
72    #[serde(rename = "fastboot.getvar")]
73    pub name: String,
74    pub equals: String,
75}
76
77#[derive(Clone, Debug, Deserialize, Serialize)]
78#[cfg_attr(feature = "schema", derive(JsonSchema))]
79pub struct FastbootGetvarStartsWith {
80    #[serde(rename = "fastboot.getvar")]
81    pub name: String,
82    pub starts_with: String,
83}
84
85#[derive(Clone, Debug, Deserialize, Serialize)]
86#[cfg_attr(feature = "schema", derive(JsonSchema))]
87pub struct FastbootGetvarNotEq {
88    #[serde(rename = "fastboot.getvar")]
89    pub name: String,
90    pub not_equals: String,
91}
92
93#[derive(Clone, Debug, Deserialize, Serialize)]
94#[cfg_attr(feature = "schema", derive(JsonSchema))]
95pub struct FastbootGetvarExists {
96    #[serde(rename = "fastboot.getvar")]
97    pub name: String,
98    /// presence of the key means "exists" check
99    #[serde(default, skip_serializing_if = "Option::is_none")]
100    pub exists: Option<ExistsFlag>,
101}
102
103#[derive(Clone, Debug, Deserialize, Serialize)]
104#[cfg_attr(feature = "schema", derive(JsonSchema))]
105pub struct FastbootGetvarNotExists {
106    #[serde(rename = "fastboot.getvar")]
107    pub name: String,
108    /// presence of the key means "not_exists" check
109    #[serde(default, skip_serializing_if = "Option::is_none")]
110    pub not_exists: Option<NotExistsFlag>,
111}
112
113#[derive(Clone, Debug, Deserialize, Serialize)]
114#[cfg_attr(feature = "schema", derive(JsonSchema))]
115pub struct ExistsFlag;
116
117#[derive(Clone, Debug, Deserialize, Serialize)]
118#[cfg_attr(feature = "schema", derive(JsonSchema))]
119pub struct NotExistsFlag;
120
121#[derive(Clone, Debug, Deserialize, Serialize)]
122#[cfg_attr(feature = "schema", derive(JsonSchema))]
123pub struct Boot {
124    pub fastboot_boot: BootPayload,
125}
126
127#[derive(Clone, Debug, Deserialize, Serialize)]
128#[cfg_attr(feature = "schema", derive(JsonSchema))]
129pub struct BootPayload {
130    #[serde(alias = "bootimg")]
131    pub android_bootimg: AndroidBootImage,
132}
133
134#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
135#[cfg_attr(feature = "schema", derive(JsonSchema))]
136pub struct InjectMac {
137    #[serde(default, skip_serializing_if = "Option::is_none")]
138    pub wifi: Option<String>,
139    #[serde(default, skip_serializing_if = "Option::is_none")]
140    pub bluetooth: Option<String>,
141}
142
143#[derive(Clone, Debug, Deserialize, Serialize)]
144#[cfg_attr(feature = "schema", derive(JsonSchema))]
145pub struct AndroidBootImage {
146    pub header_version: u32,
147    pub page_size: u32,
148    #[serde(default)]
149    pub base: Option<u64>,
150    #[serde(default)]
151    pub kernel_offset: Option<u64>,
152    #[serde(default)]
153    pub dtb_offset: Option<u64>,
154    #[serde(default)]
155    pub limits: Option<BootLimits>,
156    pub kernel: AndroidKernel,
157    #[serde(default)]
158    pub initrd: Option<AndroidInitrd>,
159    #[serde(default)]
160    pub cmdline_append: Option<String>,
161}
162
163#[derive(Clone, Debug, Deserialize, Serialize)]
164#[cfg_attr(feature = "schema", derive(JsonSchema))]
165pub struct BootLimits {
166    pub max_kernel_bytes: Option<u64>,
167    pub max_initrd_bytes: Option<u64>,
168    pub max_total_bytes: Option<u64>,
169}
170
171#[derive(Clone, Debug, Deserialize, Serialize)]
172#[cfg_attr(feature = "schema", derive(JsonSchema))]
173pub struct AndroidKernel {
174    pub encoding: KernelEncoding,
175}
176
177#[derive(Clone, Debug, Deserialize, Serialize)]
178#[cfg_attr(feature = "schema", derive(JsonSchema))]
179#[serde(rename_all = "kebab-case")]
180pub enum KernelEncoding {
181    #[serde(rename = "image")]
182    Image,
183    #[serde(rename = "image+dtb")]
184    ImageDtb,
185    #[serde(rename = "image.gz")]
186    ImageGzip,
187    #[serde(rename = "image.gz+dtb")]
188    ImageGzipDtb,
189    #[serde(rename = "image.lz4")]
190    ImageLz4,
191    #[serde(rename = "image.lz4+dtb")]
192    ImageLz4Dtb,
193    #[serde(rename = "image.zst")]
194    ImageZstd,
195    #[serde(rename = "image.zst+dtb")]
196    ImageZstdDtb,
197}
198
199impl KernelEncoding {
200    pub fn compression(&self) -> Compression {
201        match self {
202            KernelEncoding::Image | KernelEncoding::ImageDtb => Compression::None,
203            KernelEncoding::ImageGzip | KernelEncoding::ImageGzipDtb => Compression::Gzip,
204            KernelEncoding::ImageLz4 | KernelEncoding::ImageLz4Dtb => Compression::Lz4,
205            KernelEncoding::ImageZstd | KernelEncoding::ImageZstdDtb => Compression::Zstd,
206        }
207    }
208
209    pub fn append_dtb(&self) -> bool {
210        matches!(
211            self,
212            KernelEncoding::ImageDtb
213                | KernelEncoding::ImageGzipDtb
214                | KernelEncoding::ImageLz4Dtb
215                | KernelEncoding::ImageZstdDtb
216        )
217    }
218}
219
220#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
221#[cfg_attr(feature = "schema", derive(JsonSchema))]
222#[serde(rename_all = "kebab-case")]
223pub enum Compression {
224    None,
225    Gzip,
226    Lz4,
227    Zstd,
228}
229
230#[derive(Clone, Debug, Deserialize, Serialize)]
231#[cfg_attr(feature = "schema", derive(JsonSchema))]
232pub struct AndroidInitrd {
233    #[serde(default)]
234    pub compress: Option<Compression>,
235}
236
237/// Authoring schema for boot profiles (YAML/JSON).
238///
239/// This shape keeps DT overlays as inline DTS/DTSO text. Runtime code should consume
240/// [`BootProfile`], where overlays are compiled DTB/DTBO blobs.
241#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
242#[cfg_attr(feature = "schema", derive(JsonSchema))]
243#[serde(deny_unknown_fields)]
244pub struct BootProfileManifest {
245    pub id: String,
246    #[serde(default, skip_serializing_if = "Option::is_none")]
247    pub display_name: Option<String>,
248    pub rootfs: BootProfileRootfs,
249    #[serde(default, skip_serializing_if = "Option::is_none")]
250    pub kernel: Option<BootProfileArtifactPathSource>,
251    #[serde(default, skip_serializing_if = "Option::is_none")]
252    pub dtbs: Option<BootProfileArtifactPathSource>,
253    #[serde(default, skip_serializing_if = "Vec::is_empty")]
254    pub dt_overlays: Vec<String>,
255    #[serde(default, skip_serializing_if = "Option::is_none")]
256    pub extra_cmdline: Option<String>,
257    #[serde(default, skip_serializing_if = "BootProfileManifestStage0::is_empty")]
258    pub stage0: BootProfileManifestStage0,
259}
260
261impl BootProfileManifest {
262    pub fn compile_dt_overlays<E, F>(&self, mut compile: F) -> Result<BootProfile, E>
263    where
264        F: FnMut(&str) -> Result<Vec<u8>, E>,
265    {
266        let mut dt_overlays = Vec::with_capacity(self.dt_overlays.len());
267        for overlay in &self.dt_overlays {
268            dt_overlays.push(compile(overlay)?);
269        }
270
271        let mut devices = BTreeMap::new();
272        for (device_id, device) in &self.stage0.devices {
273            let mut device_overlays = Vec::with_capacity(device.dt_overlays.len());
274            for overlay in &device.dt_overlays {
275                device_overlays.push(compile(overlay)?);
276            }
277            devices.insert(
278                device_id.clone(),
279                BootProfileDevice {
280                    dt_overlays: device_overlays,
281                    extra_cmdline: device.extra_cmdline.clone(),
282                    stage0: BootProfileDeviceStage0 {
283                        kernel_modules: device.stage0.kernel_modules.clone(),
284                        inject_mac: device.stage0.inject_mac.clone(),
285                    },
286                },
287            );
288        }
289
290        Ok(BootProfile {
291            id: self.id.clone(),
292            display_name: self.display_name.clone(),
293            rootfs: self.rootfs.clone(),
294            kernel: self.kernel.clone(),
295            dtbs: self.dtbs.clone(),
296            dt_overlays,
297            extra_cmdline: self.extra_cmdline.clone(),
298            stage0: BootProfileStage0 {
299                kernel_modules: self.stage0.kernel_modules.clone(),
300                devices,
301            },
302        })
303    }
304}
305
306#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
307#[cfg_attr(feature = "schema", derive(JsonSchema))]
308#[serde(deny_unknown_fields)]
309pub struct BootProfileManifestStage0 {
310    #[serde(default, skip_serializing_if = "Vec::is_empty")]
311    pub kernel_modules: Vec<String>,
312    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
313    pub devices: BTreeMap<String, BootProfileManifestDevice>,
314}
315
316impl BootProfileManifestStage0 {
317    pub fn is_empty(&self) -> bool {
318        self.kernel_modules.is_empty() && self.devices.is_empty()
319    }
320}
321
322#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
323#[cfg_attr(feature = "schema", derive(JsonSchema))]
324#[serde(deny_unknown_fields)]
325pub struct BootProfileManifestDevice {
326    #[serde(default, skip_serializing_if = "Vec::is_empty")]
327    pub dt_overlays: Vec<String>,
328    #[serde(default, skip_serializing_if = "Option::is_none")]
329    pub extra_cmdline: Option<String>,
330    #[serde(
331        default,
332        skip_serializing_if = "BootProfileManifestDeviceStage0::is_empty"
333    )]
334    pub stage0: BootProfileManifestDeviceStage0,
335}
336
337#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
338#[cfg_attr(feature = "schema", derive(JsonSchema))]
339#[serde(deny_unknown_fields)]
340pub struct BootProfileManifestDeviceStage0 {
341    #[serde(default, skip_serializing_if = "Vec::is_empty")]
342    pub kernel_modules: Vec<String>,
343    #[serde(default, skip_serializing_if = "Option::is_none")]
344    pub inject_mac: Option<InjectMac>,
345}
346
347impl BootProfileManifestDeviceStage0 {
348    pub fn is_empty(&self) -> bool {
349        self.kernel_modules.is_empty() && self.inject_mac.is_none()
350    }
351}
352
353/// Runtime boot profile with precompiled DT overlays.
354#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
355#[cfg_attr(feature = "schema", derive(JsonSchema))]
356#[serde(deny_unknown_fields)]
357pub struct BootProfile {
358    pub id: String,
359    #[serde(default, skip_serializing_if = "Option::is_none")]
360    pub display_name: Option<String>,
361    pub rootfs: BootProfileRootfs,
362    #[serde(default, skip_serializing_if = "Option::is_none")]
363    pub kernel: Option<BootProfileArtifactPathSource>,
364    #[serde(default, skip_serializing_if = "Option::is_none")]
365    pub dtbs: Option<BootProfileArtifactPathSource>,
366    #[serde(default, skip_serializing_if = "Vec::is_empty")]
367    pub dt_overlays: Vec<Vec<u8>>,
368    #[serde(default, skip_serializing_if = "Option::is_none")]
369    pub extra_cmdline: Option<String>,
370    #[serde(default, skip_serializing_if = "BootProfileStage0::is_empty")]
371    pub stage0: BootProfileStage0,
372}
373
374impl BootProfile {
375    pub fn decompile_dt_overlays<E, F>(&self, mut decompile: F) -> Result<BootProfileManifest, E>
376    where
377        F: FnMut(&[u8]) -> Result<String, E>,
378    {
379        let mut dt_overlays = Vec::with_capacity(self.dt_overlays.len());
380        for overlay in &self.dt_overlays {
381            dt_overlays.push(decompile(overlay)?);
382        }
383
384        let mut devices = BTreeMap::new();
385        for (device_id, device) in &self.stage0.devices {
386            let mut device_overlays = Vec::with_capacity(device.dt_overlays.len());
387            for overlay in &device.dt_overlays {
388                device_overlays.push(decompile(overlay)?);
389            }
390            devices.insert(
391                device_id.clone(),
392                BootProfileManifestDevice {
393                    dt_overlays: device_overlays,
394                    extra_cmdline: device.extra_cmdline.clone(),
395                    stage0: BootProfileManifestDeviceStage0 {
396                        kernel_modules: device.stage0.kernel_modules.clone(),
397                        inject_mac: device.stage0.inject_mac.clone(),
398                    },
399                },
400            );
401        }
402
403        Ok(BootProfileManifest {
404            id: self.id.clone(),
405            display_name: self.display_name.clone(),
406            rootfs: self.rootfs.clone(),
407            kernel: self.kernel.clone(),
408            dtbs: self.dtbs.clone(),
409            dt_overlays,
410            extra_cmdline: self.extra_cmdline.clone(),
411            stage0: BootProfileManifestStage0 {
412                kernel_modules: self.stage0.kernel_modules.clone(),
413                devices,
414            },
415        })
416    }
417}
418
419#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
420#[cfg_attr(feature = "schema", derive(JsonSchema))]
421#[serde(deny_unknown_fields)]
422pub struct BootProfileStage0 {
423    #[serde(default, skip_serializing_if = "Vec::is_empty")]
424    pub kernel_modules: Vec<String>,
425    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
426    pub devices: BTreeMap<String, BootProfileDevice>,
427}
428
429impl BootProfileStage0 {
430    pub fn is_empty(&self) -> bool {
431        self.kernel_modules.is_empty() && self.devices.is_empty()
432    }
433}
434
435#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
436#[cfg_attr(feature = "schema", derive(JsonSchema))]
437#[serde(deny_unknown_fields)]
438pub struct BootProfileDevice {
439    #[serde(default, skip_serializing_if = "Vec::is_empty")]
440    pub dt_overlays: Vec<Vec<u8>>,
441    #[serde(default, skip_serializing_if = "Option::is_none")]
442    pub extra_cmdline: Option<String>,
443    #[serde(default, skip_serializing_if = "BootProfileDeviceStage0::is_empty")]
444    pub stage0: BootProfileDeviceStage0,
445}
446
447#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
448#[cfg_attr(feature = "schema", derive(JsonSchema))]
449#[serde(deny_unknown_fields)]
450pub struct BootProfileDeviceStage0 {
451    #[serde(default, skip_serializing_if = "Vec::is_empty")]
452    pub kernel_modules: Vec<String>,
453    #[serde(default, skip_serializing_if = "Option::is_none")]
454    pub inject_mac: Option<InjectMac>,
455}
456
457impl BootProfileDeviceStage0 {
458    pub fn is_empty(&self) -> bool {
459        self.kernel_modules.is_empty() && self.inject_mac.is_none()
460    }
461}
462
463#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
464#[cfg_attr(feature = "schema", derive(JsonSchema))]
465#[serde(untagged)]
466pub enum BootProfileRootfs {
467    Ostree(BootProfileRootfsOstreeSource),
468    Erofs(BootProfileRootfsErofsSource),
469    Ext4(BootProfileRootfsExt4Source),
470    Fat(BootProfileRootfsFatSource),
471}
472
473impl BootProfileRootfs {
474    pub fn source(&self) -> &BootProfileArtifactSource {
475        match self {
476            Self::Ostree(source) => source.source(),
477            Self::Erofs(source) => &source.erofs,
478            Self::Ext4(source) => &source.ext4,
479            Self::Fat(source) => &source.fat,
480        }
481    }
482
483    pub fn source_mut(&mut self) -> &mut BootProfileArtifactSource {
484        match self {
485            Self::Ostree(source) => source.source_mut(),
486            Self::Erofs(source) => &mut source.erofs,
487            Self::Ext4(source) => &mut source.ext4,
488            Self::Fat(source) => &mut source.fat,
489        }
490    }
491
492    pub fn is_ostree(&self) -> bool {
493        matches!(self, Self::Ostree(_))
494    }
495}
496
497#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
498#[cfg_attr(feature = "schema", derive(JsonSchema))]
499#[serde(deny_unknown_fields)]
500pub struct BootProfileRootfsOstreeSource {
501    pub ostree: BootProfileRootfsFilesystemSource,
502}
503
504impl BootProfileRootfsOstreeSource {
505    pub fn source(&self) -> &BootProfileArtifactSource {
506        self.ostree.source()
507    }
508
509    pub fn source_mut(&mut self) -> &mut BootProfileArtifactSource {
510        self.ostree.source_mut()
511    }
512}
513
514#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
515#[cfg_attr(feature = "schema", derive(JsonSchema))]
516#[serde(untagged)]
517pub enum BootProfileRootfsFilesystemSource {
518    Erofs(BootProfileRootfsErofsSource),
519    Ext4(BootProfileRootfsExt4Source),
520    Fat(BootProfileRootfsFatSource),
521}
522
523impl BootProfileRootfsFilesystemSource {
524    pub fn source(&self) -> &BootProfileArtifactSource {
525        match self {
526            Self::Erofs(source) => &source.erofs,
527            Self::Ext4(source) => &source.ext4,
528            Self::Fat(source) => &source.fat,
529        }
530    }
531
532    pub fn source_mut(&mut self) -> &mut BootProfileArtifactSource {
533        match self {
534            Self::Erofs(source) => &mut source.erofs,
535            Self::Ext4(source) => &mut source.ext4,
536            Self::Fat(source) => &mut source.fat,
537        }
538    }
539}
540
541#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
542#[cfg_attr(feature = "schema", derive(JsonSchema))]
543pub struct BootProfileArtifactPathSource {
544    pub path: String,
545    #[serde(flatten)]
546    pub source: BootProfileRootfs,
547}
548
549impl BootProfileArtifactPathSource {
550    pub fn artifact_source(&self) -> &BootProfileArtifactSource {
551        self.source.source()
552    }
553
554    pub fn artifact_source_mut(&mut self) -> &mut BootProfileArtifactSource {
555        self.source.source_mut()
556    }
557}
558
559#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
560#[cfg_attr(feature = "schema", derive(JsonSchema))]
561#[serde(deny_unknown_fields)]
562pub struct BootProfileRootfsErofsSource {
563    pub erofs: BootProfileArtifactSource,
564}
565
566#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
567#[cfg_attr(feature = "schema", derive(JsonSchema))]
568#[serde(deny_unknown_fields)]
569pub struct BootProfileRootfsExt4Source {
570    pub ext4: BootProfileArtifactSource,
571}
572
573#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
574#[cfg_attr(feature = "schema", derive(JsonSchema))]
575#[serde(deny_unknown_fields)]
576pub struct BootProfileRootfsFatSource {
577    pub fat: BootProfileArtifactSource,
578}
579
580pub mod bin;