1use super::codec::{CanonicalDecodeError, Decoder, Encoder};
4use crate::foundation::{AuthorityId, Digest, VolumeId};
5use crate::model::{
6 CaseSensitivity, ConcurrencyMode, FilesystemProfile, Lifecycle, UnicodePolicy, VolumeConfig,
7 VolumeConfigError, VolumeLimits,
8};
9use crate::storage::{ObjectId, ObjectKind};
10use thiserror::Error;
11
12const DOMAIN: &[u8] = b"acyclic-fs-volume-created-v1\0";
13const VERSION: u16 = 1;
14
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub struct VolumeCreated {
18 pub volume_id: VolumeId,
20 pub config: VolumeConfig,
22 pub initial_generation_root: ObjectId,
24}
25
26#[derive(Debug, Error)]
28pub enum VolumeCreatedError {
29 #[error(transparent)]
31 Config(#[from] VolumeConfigError),
32 #[error(transparent)]
34 Decode(#[from] CanonicalDecodeError),
35}
36
37#[must_use]
42pub const fn volume_authority_id(volume_id: VolumeId) -> AuthorityId {
43 AuthorityId::from_bytes(volume_id.into_bytes())
44}
45
46pub fn encode_volume_created(value: VolumeCreated) -> Result<Vec<u8>, VolumeCreatedError> {
52 value.config.validate()?;
53 if value.initial_generation_root.kind != ObjectKind::GenerationRoot {
54 return Err(invariant("initial volume object is not a generation root").into());
55 }
56 let mut encoder = Encoder::new(DOMAIN, VERSION);
57 encoder.fixed(&value.volume_id.into_bytes());
58 encode_config(&mut encoder, value.config);
59 encoder.u8(value.initial_generation_root.kind.canonical_tag());
60 encoder.fixed(value.initial_generation_root.digest.as_bytes());
61 Ok(encoder.finish())
62}
63
64pub fn decode_volume_created(
71 bytes: &[u8],
72 maximum_payload_bytes: u64,
73) -> Result<VolumeCreated, VolumeCreatedError> {
74 let mut decoder = Decoder::new(bytes, DOMAIN, VERSION, maximum_payload_bytes)?;
75 let volume_id = VolumeId::from_bytes(decoder.fixed()?);
76 let config = decode_config(&mut decoder)?.validate()?;
77 let kind = ObjectKind::from_canonical_tag(decoder.u8()?)
78 .map_err(|_| invariant("unknown initial-generation object kind"))?;
79 if kind != ObjectKind::GenerationRoot {
80 return Err(invariant("initial volume object is not a generation root").into());
81 }
82 let initial_generation_root = ObjectId {
83 kind,
84 digest: Digest::from_bytes(decoder.fixed()?),
85 };
86 decoder.finish()?;
87 Ok(VolumeCreated {
88 volume_id,
89 config,
90 initial_generation_root,
91 })
92}
93
94pub(crate) fn encode_config(encoder: &mut Encoder, config: VolumeConfig) {
95 encoder.u8(match config.profile {
96 FilesystemProfile::Portable => 1,
97 FilesystemProfile::Posix => 2,
98 FilesystemProfile::Windows => 3,
99 FilesystemProfile::Browser => 4,
100 });
101 encoder.u8(match config.concurrency {
102 ConcurrencyMode::ExclusiveWriter => 1,
103 ConcurrencyMode::Optimistic => 2,
104 ConcurrencyMode::SerializedAuthority => 3,
105 });
106 encoder.u8(match config.lifecycle {
107 Lifecycle::Ephemeral => 1,
108 Lifecycle::Durable => 2,
109 });
110 encoder.u8(match config.case_sensitivity {
111 CaseSensitivity::Sensitive => 1,
112 CaseSensitivity::ProfileFolded => 2,
113 });
114 encoder.u8(match config.unicode {
115 UnicodePolicy::Preserve => 1,
116 UnicodePolicy::RequireNfc => 2,
117 });
118 encoder.u8(u8::from(config.symbolic_links));
119 encoder.u8(u8::from(config.hard_links));
120 encoder.u8(u8::from(config.sparse_files));
121 encoder.u32(config.limits.maximum_path_bytes);
122 encoder.u32(config.limits.maximum_component_bytes);
123 encoder.u32(u32::from(config.limits.maximum_path_depth));
124 encoder.u64(config.limits.maximum_object_bytes);
125 encoder.u32(config.limits.maximum_mutations_per_batch);
126 encoder.u32(config.limits.maximum_paths_per_batch);
127 encoder.u32(config.limits.maximum_checkout_dependencies);
128 encoder.u32(config.limits.maximum_directory_page_entries);
129 encoder.u32(u32::from(config.limits.maximum_page_height));
130 encoder.u64(config.limits.maximum_read_bytes);
131 encoder.u64(config.limits.maximum_files_per_generation);
132 encoder.u64(config.limits.maximum_objects_per_generation);
133 encoder.u64(config.limits.maximum_generation_bytes);
134}
135
136pub(crate) fn decode_config(
137 decoder: &mut Decoder<'_>,
138) -> Result<VolumeConfig, CanonicalDecodeError> {
139 let profile = match decoder.u8()? {
140 1 => FilesystemProfile::Portable,
141 2 => FilesystemProfile::Posix,
142 3 => FilesystemProfile::Windows,
143 4 => FilesystemProfile::Browser,
144 _ => return Err(invariant("unknown filesystem profile")),
145 };
146 let concurrency = match decoder.u8()? {
147 1 => ConcurrencyMode::ExclusiveWriter,
148 2 => ConcurrencyMode::Optimistic,
149 3 => ConcurrencyMode::SerializedAuthority,
150 _ => return Err(invariant("unknown concurrency mode")),
151 };
152 let lifecycle = match decoder.u8()? {
153 1 => Lifecycle::Ephemeral,
154 2 => Lifecycle::Durable,
155 _ => return Err(invariant("unknown lifecycle")),
156 };
157 let case_sensitivity = match decoder.u8()? {
158 1 => CaseSensitivity::Sensitive,
159 2 => CaseSensitivity::ProfileFolded,
160 _ => return Err(invariant("unknown case-sensitivity mode")),
161 };
162 let unicode = match decoder.u8()? {
163 1 => UnicodePolicy::Preserve,
164 2 => UnicodePolicy::RequireNfc,
165 _ => return Err(invariant("unknown Unicode policy")),
166 };
167 let symbolic_links = decode_bool(decoder.u8()?)?;
168 let hard_links = decode_bool(decoder.u8()?)?;
169 let sparse_files = decode_bool(decoder.u8()?)?;
170 let maximum_path_bytes = decoder.u32()?;
171 let maximum_component_bytes = decoder.u32()?;
172 let maximum_path_depth =
173 u16::try_from(decoder.u32()?).map_err(|_| invariant("maximum path depth exceeds u16"))?;
174 Ok(VolumeConfig {
175 profile,
176 concurrency,
177 lifecycle,
178 case_sensitivity,
179 unicode,
180 symbolic_links,
181 hard_links,
182 sparse_files,
183 limits: VolumeLimits {
184 maximum_path_bytes,
185 maximum_component_bytes,
186 maximum_path_depth,
187 maximum_object_bytes: decoder.u64()?,
188 maximum_mutations_per_batch: decoder.u32()?,
189 maximum_paths_per_batch: decoder.u32()?,
190 maximum_checkout_dependencies: decoder.u32()?,
191 maximum_directory_page_entries: decoder.u32()?,
192 maximum_page_height: u16::try_from(decoder.u32()?)
193 .map_err(|_| invariant("maximum page height exceeds u16"))?,
194 maximum_read_bytes: decoder.u64()?,
195 maximum_files_per_generation: decoder.u64()?,
196 maximum_objects_per_generation: decoder.u64()?,
197 maximum_generation_bytes: decoder.u64()?,
198 },
199 })
200}
201
202fn decode_bool(value: u8) -> Result<bool, CanonicalDecodeError> {
203 match value {
204 0 => Ok(false),
205 1 => Ok(true),
206 _ => Err(invariant("non-canonical Boolean value")),
207 }
208}
209
210fn invariant(message: &str) -> CanonicalDecodeError {
211 CanonicalDecodeError::Invariant(message.to_owned())
212}
213
214#[cfg(test)]
215#[path = "tests/volume.rs"]
216mod tests;