1use super::{CheckedPath, CrlAuthorities, CrlAuthority, MemberStatus, PathStatuses, ResponderPath};
4use core::hash::{Hash, Hasher};
5use structured_asn1::memory::{HashWork, MemoryError, OwnedMap, OwnedVec, Work};
6use structured_pkix::{ocsp::AuthoritySource, temporal::Reading};
7
8pub struct AuthorityNode<'view, 'certificate, 'document> {
12 pub statuses: &'view PathStatuses<'view, 'certificate, 'document>,
14 pub responders: &'view [&'view ResponderPath<'view, 'view, 'certificate, 'document>],
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum AuthorityOrigin {
21 Root {
23 index: usize,
25 },
26 Crl {
28 node: usize,
30 member: usize,
32 source: usize,
34 delta: bool,
36 },
37 Responder {
39 node: usize,
41 member: usize,
43 },
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum AuthorityClosureError {
49 Roots,
51 DuplicateNode {
53 index: usize,
55 },
56 DuplicateResponder {
58 node: usize,
60 },
61 MissingPath {
63 origin: AuthorityOrigin,
65 },
66 ForeignRoot {
68 index: usize,
70 },
71 MissingResponder {
73 origin: AuthorityOrigin,
75 },
76 Resource(MemoryError),
78}
79
80impl From<MemoryError> for AuthorityClosureError {
81 fn from(value: MemoryError) -> Self {
82 Self::Resource(value)
83 }
84}
85
86pub struct AuthorityClosure<'view, 'certificate, 'document> {
91 roots: &'view [&'view PathStatuses<'view, 'certificate, 'document>],
92 nodes: OwnedVec<&'view AuthorityNode<'view, 'certificate, 'document>>,
93}
94
95impl<'view, 'certificate, 'document> AuthorityClosure<'view, 'certificate, 'document> {
96 #[must_use]
98 pub fn roots(&self) -> &'view [&'view PathStatuses<'view, 'certificate, 'document>] {
99 self.roots
100 }
101 #[must_use]
103 pub fn nodes(&self) -> &[&'view AuthorityNode<'view, 'certificate, 'document>] {
104 &self.nodes
105 }
106}
107
108#[derive(Clone, Copy)]
109pub(super) struct Key<'view, 'document> {
110 pub(super) path: &'view CheckedPath<'document>,
111 pub(super) at: Reading,
112}
113
114impl PartialEq for Key<'_, '_> {
115 fn eq(&self, other: &Self) -> bool {
116 core::ptr::eq(self.path, other.path) && self.at == other.at
117 }
118}
119impl Eq for Key<'_, '_> {}
120impl Hash for Key<'_, '_> {
121 fn hash<H: Hasher>(&self, state: &mut H) {
122 core::ptr::from_ref(self.path).hash(state);
125 self.at.start().seconds().hash(state);
126 self.at.start().nanos().hash(state);
127 self.at.start().is_leap_second().hash(state);
128 self.at.has_subnanosecond_remainder().hash(state);
129 }
130}
131impl HashWork for Key<'_, '_> {
132 fn hash_work(&self) -> Result<u64, MemoryError> {
133 Ok(6)
134 }
135}
136struct State {
137 index: usize,
138 queued: bool,
139}
140
141struct Pending<'view, 'certificate, 'document, W: Work> {
142 supplied: &'view [AuthorityNode<'view, 'certificate, 'document>],
143 index: OwnedMap<Key<'view, 'document>, State, W>,
144 queue: OwnedVec<&'view AuthorityNode<'view, 'certificate, 'document>, W>,
145}
146
147impl<'view, 'document, W: Work> Pending<'view, '_, 'document, W> {
148 fn need(
149 &mut self,
150 path: &'view CheckedPath<'document>,
151 at: Reading,
152 origin: AuthorityOrigin,
153 work: &W,
154 ) -> Result<usize, AuthorityClosureError> {
155 let Some(state) = self.index.get_mut(&Key { path, at }, work)? else {
156 return Err(AuthorityClosureError::MissingPath { origin });
157 };
158 work.charge_work(1)?;
159 if !state.queued {
160 self.queue.try_push(&self.supplied[state.index])?;
161 state.queued = true;
162 }
163 Ok(state.index)
164 }
165}
166
167pub fn close_authorities<'view, 'certificate, 'document, W: Work>(
186 roots: &'view [&'view PathStatuses<'view, 'certificate, 'document>],
187 supplied: &'view [AuthorityNode<'view, 'certificate, 'document>],
188 work: &W,
189) -> Result<AuthorityClosure<'view, 'certificate, 'document>, AuthorityClosureError> {
190 work.charge_work(1)?;
191 if roots.is_empty() {
192 return Err(AuthorityClosureError::Roots);
193 }
194 let mut pending = Pending {
195 supplied,
196 index: work.map(),
197 queue: work.vector(),
198 };
199 let mut responders = work.map();
200 for (index, node) in supplied.iter().enumerate() {
201 work.charge_work(1)?;
202 let key = Key {
203 path: node.statuses.path(),
204 at: node.statuses.evaluated_at(),
205 };
206 if pending
207 .index
208 .try_insert(
209 key,
210 State {
211 index,
212 queued: false,
213 },
214 )?
215 .is_some()
216 {
217 return Err(AuthorityClosureError::DuplicateNode { index });
218 }
219 for responder in node.responders {
220 work.charge_work(1)?;
221 let key = core::ptr::from_ref(responder.target()).addr();
222 if responders.try_insert(key, *responder)?.is_some() {
223 return Err(AuthorityClosureError::DuplicateResponder { node: index });
224 }
225 }
226 }
227 for (index, root) in roots.iter().enumerate() {
228 work.charge_work(1)?;
229 let node = pending.need(
230 root.path(),
231 root.evaluated_at(),
232 AuthorityOrigin::Root { index },
233 work,
234 )?;
235 if !core::ptr::eq(supplied[node].statuses, *root) {
236 return Err(AuthorityClosureError::ForeignRoot { index });
237 }
238 }
239 let mut cursor = 0;
240 while cursor < pending.queue.len() {
241 work.charge_work(1)?;
242 let inputs = pending.queue[cursor].statuses;
243 let key = Key {
244 path: inputs.path(),
245 at: inputs.evaluated_at(),
246 };
247 let index = pending
248 .index
249 .get(&key, work)?
250 .expect("queued node has its index")
251 .index;
252 follow(
253 inputs,
254 index,
255 &responders,
256 &mut |path, at, origin, work| pending.need(path, at, origin, work).map(|_| ()),
257 work,
258 )?;
259 cursor += 1;
260 }
261 Ok(AuthorityClosure {
262 roots,
263 nodes: pending.queue.into_owned(),
264 })
265}
266
267pub(super) fn follow<'view, 'certificate, 'document, W: Work>(
268 inputs: &'view PathStatuses<'view, 'certificate, 'document>,
269 index: usize,
270 responders: &OwnedMap<usize, &'view ResponderPath<'view, 'view, 'certificate, 'document>, W>,
271 need: &mut impl FnMut(
272 &'view CheckedPath<'document>,
273 Reading,
274 AuthorityOrigin,
275 &W,
276 ) -> Result<(), AuthorityClosureError>,
277 work: &W,
278) -> Result<(), AuthorityClosureError> {
279 for (member, input) in inputs.entries().iter().enumerate() {
280 work.charge_work(1)?;
281 match input {
282 MemberStatus::Crl(bound) => {
283 for (source, authority) in bound.authorities().iter().enumerate() {
284 work.charge_work(1)?;
285 follow_crl(
286 (index, member, source),
287 *authority,
288 inputs.evaluated_at(),
289 need,
290 work,
291 )?;
292 }
293 }
294 MemberStatus::CrlReading(bound) => follow_crl(
295 (index, member, 0),
296 bound.authorities(),
297 inputs.evaluated_at(),
298 need,
299 work,
300 )?,
301 MemberStatus::Ocsp(target) => {
302 if matches!(target.authority(), AuthoritySource::Delegate { .. }) {
303 let origin = AuthorityOrigin::Responder {
304 node: index,
305 member,
306 };
307 let key = core::ptr::from_ref(*target).addr();
308 let responder = responders
309 .get(&key, work)?
310 .ok_or(AuthorityClosureError::MissingResponder { origin })?;
311 need(responder.path(), inputs.evaluated_at(), origin, work)?;
312 }
313 }
314 MemberStatus::ResponderNoCheck(_) => (),
315 }
316 }
317 Ok(())
318}
319
320fn follow_crl<'view, 'document, W: Work>(
321 position: (usize, usize, usize),
322 authorities: CrlAuthorities<'view, 'document>,
323 at: Reading,
324 visit: &mut impl FnMut(
325 &'view CheckedPath<'document>,
326 Reading,
327 AuthorityOrigin,
328 &W,
329 ) -> Result<(), AuthorityClosureError>,
330 work: &W,
331) -> Result<(), AuthorityClosureError> {
332 let (node, member, source) = position;
333 let mut need = |authority, delta| {
334 if let CrlAuthority::Path(path) = authority {
335 visit(
336 path,
337 at,
338 AuthorityOrigin::Crl {
339 node,
340 member,
341 source,
342 delta,
343 },
344 work,
345 )?;
346 }
347 Ok::<_, AuthorityClosureError>(())
348 };
349 match authorities {
350 CrlAuthorities::Complete(authority) => need(authority, false),
351 CrlAuthorities::Delta { base, delta } => {
352 need(base, false)?;
353 need(delta, true)
354 }
355 }
356}