web_static_pack/loader.rs
1//! Module containing [load] function used to convert (map) serialized `pack`
2//! into [PackArchived] object.
3
4use crate::common::{PACK_FILE_MAGIC, PACK_FILE_VERSION, pack::PackArchived};
5use anyhow::{Error, ensure};
6use rkyv::access_unchecked;
7
8/// Alignment value for `serialized` in [load].
9pub const ALIGN_BYTES: usize = 16;
10
11/// Loads [PackArchived] from serialized bytes created by
12/// [web-static-pack-packer](https://crates.io/crates/web-static-pack-packer).
13///
14/// This method is lightweight, as it does some pre-checks and then casts
15/// (zero-copy deserialization) [rkyv] archived [PackArchived] to the output.
16///
17/// In a typical scenario the [Pack] will be created with packer in build
18/// pipeline (or build.rs), then serialized to a file and stored in the working
19/// / build directory.
20///
21/// Target application will have serialized `pack` embedded with
22/// <https://docs.rs/include_bytes_aligned/latest/include_bytes_aligned/>
23/// (or alternatively read from fs) and then passed to this function, that will
24/// "map" it to [PackArchived].
25///
26/// Loaded [PackArchived] will be typically used to create
27/// [crate::responder::Responder].
28///
29/// Please note that `serialized` must be aligned to [ALIGN_BYTES], either by
30/// `include_bytes_aligned` crate (if embedding into executable) or
31/// [rkyv::util::AlignedVec] (if loading from fs in runtime).
32///
33/// # Examples
34///
35/// ```ignore
36/// // from workspace root:
37/// // $ cargo run -- directory-single ./tests/data/vcard-personal-portfolio/ vcard-personal-portfolio.pack
38///
39/// // then in your application
40/// static PACK_ARCHIVED_SERIALIZED: &[u8] = include_bytes_aligned!(
41/// 16, // == web_static_pack::loader::ALIGN_BYTES,
42/// "vcard-personal-portfolio.pack"
43/// );
44///
45/// fn main() {
46/// let pack = unsafe { web_static_pack::loader::load(PACK_ARCHIVED_SERIALIZED).unwrap() };
47/// // create responder from pack
48/// // pass http requests to responder
49/// // see crate documentation for full example
50/// }
51/// ```
52///
53/// # Safety
54/// `serialized` must point to valid `pack` created with matching version of
55/// packer. Underlying loader (rkyv) relies on correct file content. If invalid
56/// content is provided it is going to cause undefined behavior.
57pub unsafe fn load(serialized: &[u8]) -> Result<&PackArchived, Error> {
58 ensure!(
59 (serialized.as_ptr() as usize).is_multiple_of(ALIGN_BYTES),
60 "invalid alignment, serialized must be aligned to {ALIGN_BYTES} bytes"
61 );
62 ensure!(serialized.len() > 16, "premature file end");
63
64 // check file magic
65 let file_magic_bytes: [u8; 8] = serialized[0..8].try_into()?;
66 let file_magic = u64::from_ne_bytes(file_magic_bytes);
67 ensure!(
68 file_magic == PACK_FILE_MAGIC,
69 "file magic mismatch, probably not a pack file"
70 );
71
72 // check file version
73 let file_version_bytes: [u8; 8] = serialized[8..16].try_into()?;
74 let file_version = u64::from_ne_bytes(file_version_bytes);
75 ensure!(
76 file_version == PACK_FILE_VERSION,
77 "file version mismatch (got {file_version}, expected: {PACK_FILE_VERSION}) (probably pack created with different version)"
78 );
79
80 // deserialize content
81 // NOTE: value passed to [access_unchecked] must be 16-aligned
82 let pack = unsafe { access_unchecked::<PackArchived>(&serialized[16..]) };
83
84 Ok(pack)
85}