Skip to main content

acyclic_fs/kernel/
metadata.rs

1//! Canonical cross-profile metadata with explicit unavailable fields.
2
3use super::codec::{CanonicalDecodeError, DecodeLimits, Decoder, Encoder};
4use super::types::digest_object;
5use crate::storage::{ObjectId, ObjectKind, object_digest};
6
7const DOMAIN: &[u8; 8] = b"ACYFSMET";
8const VERSION: u16 = 1;
9
10/// A metadata fact that is either unavailable in the source profile or exact.
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12pub enum MetadataField<T> {
13    /// Source filesystem/profile cannot represent or did not observe the fact.
14    Unavailable,
15    /// Exact observed value, including zero.
16    Value(T),
17}
18
19/// Complete profile-independent metadata record.
20///
21/// Platform-specific facts are never guessed. `Unavailable` is distinct from a
22/// supported field whose exact value is zero.
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub struct FileMetadata {
25    /// POSIX permission/type bits when represented by the source.
26    pub posix_mode: MetadataField<u32>,
27    /// POSIX numeric owner identity.
28    pub posix_uid: MetadataField<u32>,
29    /// POSIX numeric group identity.
30    pub posix_gid: MetadataField<u32>,
31    /// POSIX inode flags where available.
32    pub posix_flags: MetadataField<u64>,
33    /// Windows file-attribute bitset.
34    pub windows_attributes: MetadataField<u32>,
35    /// Creation/birth time in signed Unix-epoch nanoseconds.
36    pub created_ns: MetadataField<i64>,
37    /// Last content-modification time in signed Unix-epoch nanoseconds.
38    pub modified_ns: MetadataField<i64>,
39    /// Last access time in signed Unix-epoch nanoseconds.
40    pub accessed_ns: MetadataField<i64>,
41    /// Last metadata-change time in signed Unix-epoch nanoseconds.
42    pub changed_ns: MetadataField<i64>,
43    /// Named xattrs, alternate streams, and resource forks.
44    pub named_attributes: MetadataField<ObjectId>,
45    /// Canonical ACL bytes when represented independently of a security descriptor.
46    pub acl: MetadataField<ObjectId>,
47    /// Canonical Windows security descriptor bytes.
48    pub security_descriptor: MetadataField<ObjectId>,
49}
50
51impl Default for FileMetadata {
52    fn default() -> Self {
53        Self {
54            posix_mode: MetadataField::Unavailable,
55            posix_uid: MetadataField::Unavailable,
56            posix_gid: MetadataField::Unavailable,
57            posix_flags: MetadataField::Unavailable,
58            windows_attributes: MetadataField::Unavailable,
59            created_ns: MetadataField::Unavailable,
60            modified_ns: MetadataField::Unavailable,
61            accessed_ns: MetadataField::Unavailable,
62            changed_ns: MetadataField::Unavailable,
63            named_attributes: MetadataField::Unavailable,
64            acl: MetadataField::Unavailable,
65            security_descriptor: MetadataField::Unavailable,
66        }
67    }
68}
69
70impl FileMetadata {
71    fn validate(self) -> Result<(), CanonicalDecodeError> {
72        validate_object(
73            self.named_attributes,
74            ObjectKind::AttributePage,
75            "named attributes",
76        )?;
77        validate_object(self.acl, ObjectKind::Blob, "acl")?;
78        validate_object(
79            self.security_descriptor,
80            ObjectKind::Blob,
81            "security descriptor",
82        )
83    }
84}
85
86/// Encodes one canonical metadata record.
87///
88/// # Errors
89///
90/// Rejects references whose typed object class does not match the field.
91pub fn encode_file_metadata(metadata: FileMetadata) -> Result<Vec<u8>, CanonicalDecodeError> {
92    metadata.validate()?;
93    let mut encoder = Encoder::new(DOMAIN, VERSION);
94    encode_u32(&mut encoder, metadata.posix_mode);
95    encode_u32(&mut encoder, metadata.posix_uid);
96    encode_u32(&mut encoder, metadata.posix_gid);
97    encode_u64(&mut encoder, metadata.posix_flags);
98    encode_u32(&mut encoder, metadata.windows_attributes);
99    encode_i64(&mut encoder, metadata.created_ns);
100    encode_i64(&mut encoder, metadata.modified_ns);
101    encode_i64(&mut encoder, metadata.accessed_ns);
102    encode_i64(&mut encoder, metadata.changed_ns);
103    encode_object(&mut encoder, metadata.named_attributes);
104    encode_object(&mut encoder, metadata.acl);
105    encode_object(&mut encoder, metadata.security_descriptor);
106    Ok(encoder.finish())
107}
108
109/// Decodes one bounded canonical metadata record.
110///
111/// # Errors
112///
113/// Fails closed on malformed bytes, non-canonical presence tags, wrong object
114/// classes, unsupported versions, or trailing data.
115pub fn decode_file_metadata(
116    bytes: &[u8],
117    limits: DecodeLimits,
118) -> Result<FileMetadata, CanonicalDecodeError> {
119    let mut decoder = Decoder::new(bytes, DOMAIN, VERSION, limits.maximum_object_bytes)?;
120    let metadata = FileMetadata {
121        posix_mode: decode_u32(&mut decoder, "posix_mode")?,
122        posix_uid: decode_u32(&mut decoder, "posix_uid")?,
123        posix_gid: decode_u32(&mut decoder, "posix_gid")?,
124        posix_flags: decode_u64(&mut decoder, "posix_flags")?,
125        windows_attributes: decode_u32(&mut decoder, "windows_attributes")?,
126        created_ns: decode_i64(&mut decoder, "created_ns")?,
127        modified_ns: decode_i64(&mut decoder, "modified_ns")?,
128        accessed_ns: decode_i64(&mut decoder, "accessed_ns")?,
129        changed_ns: decode_i64(&mut decoder, "changed_ns")?,
130        named_attributes: decode_object(
131            &mut decoder,
132            ObjectKind::AttributePage,
133            "named_attributes",
134        )?,
135        acl: decode_object(&mut decoder, ObjectKind::Blob, "acl")?,
136        security_descriptor: decode_object(&mut decoder, ObjectKind::Blob, "security_descriptor")?,
137    };
138    decoder.finish()?;
139    metadata.validate()?;
140    Ok(metadata)
141}
142
143/// Computes the typed authenticated identity of canonical metadata.
144///
145/// # Errors
146///
147/// Returns the same validation errors as [`encode_file_metadata`].
148pub fn file_metadata_id(metadata: FileMetadata) -> Result<ObjectId, CanonicalDecodeError> {
149    let bytes = encode_file_metadata(metadata)?;
150    Ok(ObjectId {
151        kind: ObjectKind::Metadata,
152        digest: object_digest(ObjectKind::Metadata, &bytes),
153    })
154}
155
156fn encode_u32(encoder: &mut Encoder, field: MetadataField<u32>) {
157    match field {
158        MetadataField::Unavailable => encoder.u8(0),
159        MetadataField::Value(value) => {
160            encoder.u8(1);
161            encoder.u32(value);
162        }
163    }
164}
165
166fn encode_u64(encoder: &mut Encoder, field: MetadataField<u64>) {
167    match field {
168        MetadataField::Unavailable => encoder.u8(0),
169        MetadataField::Value(value) => {
170            encoder.u8(1);
171            encoder.u64(value);
172        }
173    }
174}
175
176fn encode_i64(encoder: &mut Encoder, field: MetadataField<i64>) {
177    match field {
178        MetadataField::Unavailable => encoder.u8(0),
179        MetadataField::Value(value) => {
180            encoder.u8(1);
181            encoder.i64(value);
182        }
183    }
184}
185
186fn encode_object(encoder: &mut Encoder, field: MetadataField<ObjectId>) {
187    match field {
188        MetadataField::Unavailable => encoder.u8(0),
189        MetadataField::Value(object) => {
190            encoder.u8(1);
191            encoder.fixed(object.digest.as_bytes());
192        }
193    }
194}
195
196fn decode_u32(
197    decoder: &mut Decoder<'_>,
198    field: &'static str,
199) -> Result<MetadataField<u32>, CanonicalDecodeError> {
200    match decoder.u8()? {
201        0 => Ok(MetadataField::Unavailable),
202        1 => Ok(MetadataField::Value(decoder.u32()?)),
203        tag => Err(CanonicalDecodeError::UnknownTag { field, tag }),
204    }
205}
206
207fn decode_u64(
208    decoder: &mut Decoder<'_>,
209    field: &'static str,
210) -> Result<MetadataField<u64>, CanonicalDecodeError> {
211    match decoder.u8()? {
212        0 => Ok(MetadataField::Unavailable),
213        1 => Ok(MetadataField::Value(decoder.u64()?)),
214        tag => Err(CanonicalDecodeError::UnknownTag { field, tag }),
215    }
216}
217
218fn decode_i64(
219    decoder: &mut Decoder<'_>,
220    field: &'static str,
221) -> Result<MetadataField<i64>, CanonicalDecodeError> {
222    match decoder.u8()? {
223        0 => Ok(MetadataField::Unavailable),
224        1 => Ok(MetadataField::Value(decoder.i64()?)),
225        tag => Err(CanonicalDecodeError::UnknownTag { field, tag }),
226    }
227}
228
229fn decode_object(
230    decoder: &mut Decoder<'_>,
231    kind: ObjectKind,
232    field: &'static str,
233) -> Result<MetadataField<ObjectId>, CanonicalDecodeError> {
234    match decoder.u8()? {
235        0 => Ok(MetadataField::Unavailable),
236        1 => Ok(MetadataField::Value(digest_object(kind, decoder.fixed()?))),
237        tag => Err(CanonicalDecodeError::UnknownTag { field, tag }),
238    }
239}
240
241fn validate_object(
242    field: MetadataField<ObjectId>,
243    kind: ObjectKind,
244    name: &'static str,
245) -> Result<(), CanonicalDecodeError> {
246    if let MetadataField::Value(object) = field
247        && object.kind != kind
248    {
249        return Err(CanonicalDecodeError::Invariant(format!(
250            "{name} has the wrong object kind"
251        )));
252    }
253    Ok(())
254}
255
256#[cfg(test)]
257#[path = "tests/metadata.rs"]
258mod tests;