Skip to main content

holos_tda/distributed/
manifest.rs

1use super::compose::{job_id, require_canonical_vertices};
2use super::model::{ArtifactId, DistributedInterfaceError, DistributedInterfaceManifest};
3use super::wire::{
4    MANIFEST_MAGIC, Reader, VERSION, decode_ids, decode_usizes, encode_ids, encode_usizes,
5    put_usize,
6};
7
8impl DistributedInterfaceManifest {
9    /// Deterministic job identifier.
10    pub fn job(&self) -> ArtifactId {
11        self.job
12    }
13
14    /// Highest composed homology dimension.
15    pub fn max_dim(&self) -> usize {
16        self.max_dim
17    }
18
19    /// Prime coefficient modulus.
20    pub fn modulus(&self) -> u32 {
21        self.modulus
22    }
23
24    /// Separator protected during every intermediate fold.
25    pub fn separator_vertices(&self) -> &[usize] {
26        &self.separator_vertices
27    }
28
29    /// Protected vertices retained in the final result.
30    pub fn output_protected_vertices(&self) -> &[usize] {
31        &self.output_protected_vertices
32    }
33
34    /// Ordered shard artifact identifiers.
35    pub fn shards(&self) -> &[ArtifactId] {
36        &self.shards
37    }
38
39    /// Accumulator identifier after each shard.
40    pub fn folds(&self) -> &[ArtifactId] {
41        &self.folds
42    }
43
44    /// Final relative-interface artifact identifier.
45    pub fn result(&self) -> ArtifactId {
46        self.result
47    }
48
49    /// Encode the canonical `HOLOSDM` version 1 manifest.
50    pub fn encode(&self) -> Result<Vec<u8>, DistributedInterfaceError> {
51        let mut output = Vec::new();
52        output.extend_from_slice(MANIFEST_MAGIC);
53        output.extend_from_slice(&VERSION.to_be_bytes());
54        output.extend_from_slice(self.job.as_bytes());
55        put_usize(&mut output, self.max_dim)?;
56        output.extend_from_slice(&self.modulus.to_be_bytes());
57        encode_usizes(&mut output, &self.separator_vertices)?;
58        encode_usizes(&mut output, &self.output_protected_vertices)?;
59        encode_ids(&mut output, &self.shards)?;
60        encode_ids(&mut output, &self.folds)?;
61        output.extend_from_slice(self.result.as_bytes());
62        Ok(output)
63    }
64
65    /// Decode one canonical bounded `HOLOSDM` version 1 manifest.
66    pub fn decode(bytes: &[u8], maximum_bytes: usize) -> Result<Self, DistributedInterfaceError> {
67        check_manifest_size(bytes.len(), maximum_bytes)?;
68        let mut reader = Reader::new(bytes);
69        check_manifest_identity(&mut reader)?;
70        let manifest = decode_manifest_body(&mut reader)?;
71        check_manifest_shape(&manifest, reader.remaining())?;
72        check_manifest_binding(&manifest)?;
73        Ok(manifest)
74    }
75}
76
77fn check_manifest_size(actual: usize, limit: usize) -> Result<(), DistributedInterfaceError> {
78    if actual > limit {
79        return Err(DistributedInterfaceError::new(
80            "manifest exceeds the byte limit",
81        ));
82    }
83    Ok(())
84}
85
86fn check_manifest_identity(reader: &mut Reader<'_>) -> Result<(), DistributedInterfaceError> {
87    if reader.take(8)? != MANIFEST_MAGIC || reader.u16()? != VERSION {
88        return Err(DistributedInterfaceError::new(
89            "unsupported distributed manifest",
90        ));
91    }
92    Ok(())
93}
94
95fn decode_manifest_body(
96    reader: &mut Reader<'_>,
97) -> Result<DistributedInterfaceManifest, DistributedInterfaceError> {
98    Ok(DistributedInterfaceManifest {
99        job: ArtifactId(reader.array32()?),
100        max_dim: reader.usize()?,
101        modulus: reader.u32()?,
102        separator_vertices: decode_usizes(reader)?,
103        output_protected_vertices: decode_usizes(reader)?,
104        shards: decode_ids(reader)?,
105        folds: decode_ids(reader)?,
106        result: ArtifactId(reader.array32()?),
107    })
108}
109
110fn check_manifest_shape(
111    manifest: &DistributedInterfaceManifest,
112    remaining: usize,
113) -> Result<(), DistributedInterfaceError> {
114    if remaining != 0 || manifest.shards.is_empty() || manifest.folds.len() != manifest.shards.len()
115    {
116        return Err(DistributedInterfaceError::new(
117            "distributed manifest shape is invalid",
118        ));
119    }
120    require_canonical_vertices(&manifest.separator_vertices)?;
121    require_canonical_vertices(&manifest.output_protected_vertices)?;
122    Ok(())
123}
124
125fn check_manifest_binding(
126    manifest: &DistributedInterfaceManifest,
127) -> Result<(), DistributedInterfaceError> {
128    let expected = job_id(
129        manifest.max_dim,
130        manifest.modulus,
131        &manifest.separator_vertices,
132        &manifest.output_protected_vertices,
133        &manifest.shards,
134    );
135    if expected != manifest.job {
136        return Err(DistributedInterfaceError::new(
137            "distributed manifest binding is invalid",
138        ));
139    }
140    Ok(())
141}