Skip to main content

rns_core/resource/
assembly.rs

1//! Owned, authenticated Resource data that can be verified away from its receiver.
2use super::{
3    parts::extract_metadata_owned,
4    proof::{build_proof_data, compute_expected_proof, compute_resource_hash},
5    ResourceAction, ResourceError,
6};
7use crate::{
8    buffer::types::{Compressor, DecompressError},
9    constants::RESOURCE_RANDOM_HASH_SIZE,
10};
11use alloc::{sync::Arc, vec, vec::Vec};
12
13/// Identity of one assembly attempt. Equality compares attempts, not Resource hashes.
14#[derive(Clone, Debug)]
15pub struct AssemblyId(Arc<()>);
16impl AssemblyId {
17    pub(super) fn new() -> Self {
18        Self(Arc::new(()))
19    }
20}
21impl PartialEq for AssemblyId {
22    fn eq(&self, other: &Self) -> bool {
23        Arc::ptr_eq(&self.0, &other.0)
24    }
25}
26impl Eq for AssemblyId {}
27
28/// A consuming handoff. Authentication has already succeeded when encryption is used.
29/// No protocol actions are published until the receiver accepts the returned result.
30pub struct ResourceAssembly {
31    pub(super) id: AssemblyId,
32    pub(super) data: AssemblyData,
33}
34/// A completed attempt, bound to its original receiver. Contents are deliberately private.
35pub struct AssemblyResult {
36    pub(super) id: AssemblyId,
37    pub(super) result: Result<Vec<ResourceAction>, ResourceError>,
38}
39impl ResourceAssembly {
40    pub fn id(&self) -> &AssemblyId {
41        &self.id
42    }
43    /// Owned plaintext capacity retained while the attempt is queued.
44    pub fn input_capacity(&self) -> usize {
45        self.data.plaintext.capacity()
46    }
47    /// Decoder output bound, independent of the advertised data size.
48    pub fn max_output_size(&self) -> usize {
49        self.data.limit
50    }
51    pub fn run(self, compressor: &dyn Compressor) -> AssemblyResult {
52        AssemblyResult {
53            id: self.id,
54            result: self.data.run(compressor),
55        }
56    }
57}
58
59pub(super) struct AssemblyData {
60    pub plaintext: Vec<u8>,
61    pub random_hash: [u8; RESOURCE_RANDOM_HASH_SIZE],
62    pub resource_hash: [u8; 32],
63    pub compressed: bool,
64    pub metadata: bool,
65    pub limit: usize,
66}
67impl AssemblyData {
68    pub fn run(self, compressor: &dyn Compressor) -> Result<Vec<ResourceAction>, ResourceError> {
69        let mut plaintext = self.plaintext;
70        let input = plaintext
71            .get(RESOURCE_RANDOM_HASH_SIZE..)
72            .ok_or(ResourceError::InvalidPart)?;
73        let decompressed = if self.compressed {
74            compressor
75                .decompress_bounded(input, self.limit)
76                .map_err(|error| match error {
77                    DecompressError::TooLarge => ResourceError::TooLarge,
78                    DecompressError::InvalidData => ResourceError::DecompressionFailed,
79                })?
80        } else {
81            let len = input.len();
82            plaintext.copy_within(RESOURCE_RANDOM_HASH_SIZE.., 0);
83            plaintext.truncate(len);
84            plaintext
85        };
86        let hash = compute_resource_hash(&decompressed, &self.random_hash);
87        if hash != self.resource_hash {
88            return Err(ResourceError::HashMismatch);
89        }
90        // Proof covers the complete data, including its metadata prefix.
91        let proof = build_proof_data(&hash, &compute_expected_proof(&decompressed, &hash));
92        let (data, metadata) = if self.metadata {
93            let (metadata, data) =
94                extract_metadata_owned(decompressed).ok_or(ResourceError::InvalidPart)?;
95            (data, Some(metadata))
96        } else {
97            (decompressed, None)
98        };
99        Ok(vec![
100            ResourceAction::SendProof(proof),
101            ResourceAction::DataReceived { data, metadata },
102            ResourceAction::Completed,
103        ])
104    }
105}