Skip to main content

structured_xades/hashes/
participants.rs

1//! Complete original root/counter hash-use qualification.
2
3use super::{HashBinding, HashError, HashProblem, HashXml, Hashes, check, check_selected};
4use crate::pki::CounterPaths;
5use crate::prepare::Prepared;
6use crate::xml::{Access, Snapshot, Xml};
7use structured_asn1::memory::{OwnedVec, Work};
8
9/// Original actor whose completed hash guard remained negative/incomplete.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct ParticipantProblem<N> {
12    /// Exact original signature, including a plain `XMLDSig` counter.
13    pub signature: N,
14    /// Actual selected method/use or incomplete discovery boundary.
15    pub cause: HashProblem<N>,
16}
17
18/// Complete root plus every original incorporated counter. Construction is
19/// private; a root-only receipt cannot stand for this whole participant set.
20pub struct ParticipantHashes<N, D, G> {
21    root: Hashes<N, D, G>,
22    counters: OwnedVec<Hashes<N, D, G>>,
23}
24
25impl<N: Copy, D: Copy, G: Copy> ParticipantHashes<N, D, G> {
26    /// Original immutable source generation.
27    #[must_use]
28    pub fn snapshot(&self) -> Snapshot<D, G> {
29        self.root.snapshot()
30    }
31    /// Original selected root, not an arbitrary discovered signature.
32    #[must_use]
33    pub fn signature(&self) -> N {
34        self.root.signature()
35    }
36    /// Root's complete actual-use guard.
37    #[must_use]
38    pub fn root(&self) -> &Hashes<N, D, G> {
39        &self.root
40    }
41    /// Every counter once in its original complete counter-census order.
42    #[must_use]
43    pub fn counters(&self) -> &[Hashes<N, D, G>] {
44        &self.counters
45    }
46}
47
48/// Whole-set completion or one original negative/incomplete actor. No partial
49/// successful participant set is returned after a late child refusal.
50pub enum ParticipantBinding<N, D, G> {
51    /// Root and all original counters qualified through the same hash kernel.
52    Bound(ParticipantHashes<N, D, G>),
53    /// Original prohibition/unsupported/unprocessed fact, not a resource error.
54    Unproven(ParticipantProblem<N>),
55}
56
57/// Whole-set hash query result preserving original source/processing causes.
58pub type Result<X> = core::result::Result<
59    ParticipantBinding<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>,
60    HashError<<X as Xml>::Error, <X as Xml>::Id>,
61>;
62
63/// Qualify actual message/MGF/reference/Manifest uses for the selected root and
64/// its COMPLETE checked counter census, including plain `XMLDSig` children. The
65/// EN319132-1 ยง6.2.1 MD5 guard is shared with the root and timestamp checks;
66/// this never invents child B-level properties or applies new suite policy.
67/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
68///
69/// Foreign selections fail before owner callbacks. Current Work admits storage
70/// and every dispatch before querying the original owner; no signature/hash/
71/// payload operation is repeated. All partial retained metadata drops on failure.
72pub fn check_participants<X: HashXml, W: Work>(
73    prepared: &Prepared<X>,
74    counters: &CounterPaths<'_, '_, X::Id, X::Document, X::Generation>,
75    access: &Access<'_, X, W>,
76) -> Result<X> {
77    let layout = prepared.bound().containers();
78    let signature = layout.signature();
79    if layout.snapshot() != access.snapshot() || counters.snapshot() != access.snapshot() {
80        return Err(HashError::Foreign { node: signature });
81    }
82    if !access
83        .equal_id(signature, counters.signature())
84        .map_err(|cause| HashError::Resource {
85            node: signature,
86            cause,
87        })?
88    {
89        return Err(HashError::Foreign { node: signature });
90    }
91    let mut children = access.execution().vector();
92    children
93        .try_reserve_exact(counters.actors().len())
94        .map_err(|cause| HashError::Resource {
95            node: signature,
96            cause,
97        })?;
98    let root = match check(prepared.bound(), access)? {
99        HashBinding::Bound(root) => root,
100        HashBinding::Unproven(cause) => {
101            return Ok(ParticipantBinding::Unproven(ParticipantProblem {
102                signature,
103                cause,
104            }));
105        }
106    };
107    for actor in counters.actors() {
108        let signature = actor.signer().counter().original().child();
109        access
110            .execution()
111            .charge_work(1)
112            .map_err(|cause| HashError::Resource {
113                node: signature,
114                cause,
115            })?;
116        match check_selected(signature, access)? {
117            HashBinding::Bound(receipt) => {
118                children
119                    .try_push(receipt)
120                    .map_err(|cause| HashError::Resource {
121                        node: signature,
122                        cause,
123                    })?;
124            }
125            HashBinding::Unproven(cause) => {
126                return Ok(ParticipantBinding::Unproven(ParticipantProblem {
127                    signature,
128                    cause,
129                }));
130            }
131        }
132    }
133    access
134        .query(signature, |_, _, _| Ok(()))
135        .map_err(HashError::Access)?;
136    Ok(ParticipantBinding::Bound(ParticipantHashes {
137        root,
138        counters: children.into_owned(),
139    }))
140}