1use crate::{
6 discover::{AsDiscovered, DiscoveredAdvisory},
7 retrieve::{AsRetrieved, RetrievalContext, RetrievedAdvisory, RetrievedVisitor},
8 source::Source,
9 validation::{ValidatedAdvisory, ValidatedVisitor, ValidationContext, ValidationError},
10 verification::check::{Check, CheckError},
11};
12use csaf::Csaf;
13use serde::de::Error as _;
14use std::{
15 collections::{HashMap, HashSet},
16 fmt::{Debug, Display},
17 future::Future,
18 hash::Hash,
19 marker::PhantomData,
20 ops::{Deref, DerefMut},
21};
22use url::Url;
23use walker_common::retrieve::RetrievalError;
24use walker_common::utils::url::Urlify;
25
26pub mod check;
27
28#[derive(Debug)]
29pub struct VerifiedAdvisory<A, I>
30where
31 A: AsRetrieved,
32 I: Clone + PartialEq + Eq + Hash,
33{
34 pub advisory: A,
35 pub csaf: Csaf,
36 pub failures: HashMap<I, Vec<CheckError>>,
37 pub successes: HashSet<I>,
38}
39
40impl<A, I> Deref for VerifiedAdvisory<A, I>
41where
42 A: AsRetrieved,
43 I: Clone + PartialEq + Eq + Hash,
44{
45 type Target = A;
46
47 fn deref(&self) -> &Self::Target {
48 &self.advisory
49 }
50}
51
52impl<A, I> DerefMut for VerifiedAdvisory<A, I>
53where
54 A: AsRetrieved,
55 I: Clone + PartialEq + Eq + Hash,
56{
57 fn deref_mut(&mut self) -> &mut Self::Target {
58 &mut self.advisory
59 }
60}
61
62#[derive(Debug, thiserror::Error)]
63pub enum VerificationError<UE, A>
64where
65 A: Debug,
66 UE: Display + Debug,
67{
68 #[error(transparent)]
69 Upstream(UE),
70 #[error("document parsing error: {error}")]
71 Parsing {
72 advisory: A,
73 error: serde_json::Error,
74 },
75 #[error("check runtime error: {error}")]
76 Check { advisory: A, error: anyhow::Error },
77}
78
79impl<A, UE> AsDiscovered for VerificationError<UE, A>
80where
81 A: AsDiscovered + Debug,
82 UE: AsDiscovered + Display + Debug,
83{
84 fn as_discovered(&self) -> &DiscoveredAdvisory {
85 match self {
86 Self::Upstream(err) => err.as_discovered(),
87 Self::Parsing { advisory, .. } => advisory.as_discovered(),
88 Self::Check { advisory, .. } => advisory.as_discovered(),
89 }
90 }
91}
92
93impl<UE, A> Urlify for VerificationError<UE, A>
94where
95 A: AsRetrieved + Debug,
96 UE: Urlify + Display + Debug,
97{
98 fn url(&self) -> &Url {
99 match self {
100 Self::Upstream(err) => err.url(),
101 Self::Parsing { advisory, .. } => advisory.as_retrieved().url(),
102 Self::Check { advisory, .. } => advisory.as_retrieved().url(),
103 }
104 }
105}
106
107pub struct VerificationContext {}
108
109pub trait VerifiedVisitor<A, E, I>
111where
112 A: AsRetrieved,
113 E: Display + Debug,
114 I: Clone + PartialEq + Eq + Hash,
115{
116 type Error: Display + Debug;
117 type Context;
118
119 fn visit_context(
120 &self,
121 context: &VerificationContext,
122 ) -> impl Future<Output = Result<Self::Context, Self::Error>>;
123
124 fn visit_advisory(
125 &self,
126 context: &Self::Context,
127 result: Result<VerifiedAdvisory<A, I>, VerificationError<E, A>>,
128 ) -> impl Future<Output = Result<(), Self::Error>>;
129}
130
131#[derive(Debug, thiserror::Error)]
132pub enum Error<VE>
133where
134 VE: Display + Debug,
135{
136 #[error(transparent)]
137 Visitor(VE),
138}
139
140pub struct VerifyingVisitor<A, E, V, I>
142where
143 A: AsRetrieved,
144 V: VerifiedVisitor<A, E, I>,
145 E: Display + Debug,
146 I: Clone + PartialEq + Eq + Hash,
147{
148 visitor: V,
149 checks: Vec<(I, Box<dyn Check>)>,
150 _marker: PhantomData<(A, E)>,
151}
152
153impl<A, E, V, I> VerifyingVisitor<A, E, V, I>
154where
155 A: AsRetrieved,
156 V: VerifiedVisitor<A, E, I>,
157 E: Display + Debug,
158 I: Clone + PartialEq + Eq + Hash,
159{
160 pub fn new(visitor: V) -> Self {
161 Self {
162 visitor,
163 checks: vec![],
164 _marker: Default::default(),
165 }
166 }
167
168 pub fn with_checks(visitor: V, checks: Vec<(I, Box<dyn Check>)>) -> Self {
169 Self {
170 visitor,
171 checks,
172 _marker: Default::default(),
173 }
174 }
175
176 pub fn add<F: Check + 'static>(mut self, index: I, check: F) -> Self {
177 self.checks.push((index, Box::new(check)));
178 self
179 }
180
181 async fn verify(&self, advisory: A) -> Result<VerifiedAdvisory<A, I>, VerificationError<E, A>> {
182 let data = advisory.as_retrieved().data.clone();
183
184 let csaf = match tokio::task::spawn_blocking(move || serde_json::from_slice::<Csaf>(&data))
185 .await
186 {
187 Ok(Ok(csaf)) => csaf,
188 Ok(Err(error)) => return Err(VerificationError::Parsing { error, advisory }),
189 Err(_) => {
190 return Err(VerificationError::Parsing {
191 error: serde_json::error::Error::custom("failed to wait for deserialization"),
192 advisory,
193 })
194 }
195 };
196
197 let mut failures = HashMap::new();
198 let mut successes = HashSet::new();
199
200 for (index, check) in &self.checks {
201 let result = match check.as_ref().check(&csaf).await {
202 Ok(result) => result,
203 Err(error) => return Err(VerificationError::Check { error, advisory }),
204 };
205 if !result.is_empty() {
206 failures.insert(index.clone(), result);
207 } else {
208 successes.insert(index.clone());
209 }
210 }
211
212 Ok(VerifiedAdvisory {
213 advisory,
214 csaf,
215 failures,
216 successes,
217 })
218 }
219}
220
221impl<V, I, S> RetrievedVisitor<S>
222 for VerifyingVisitor<RetrievedAdvisory, RetrievalError<DiscoveredAdvisory, S>, V, I>
223where
224 V: VerifiedVisitor<RetrievedAdvisory, RetrievalError<DiscoveredAdvisory, S>, I>,
225 I: Clone + PartialEq + Eq + Hash,
226 S: Source,
227{
228 type Error = Error<V::Error>;
229 type Context = V::Context;
230
231 async fn visit_context(
232 &self,
233 _context: &RetrievalContext<'_>,
234 ) -> Result<Self::Context, Self::Error> {
235 self.visitor
236 .visit_context(&VerificationContext {})
237 .await
238 .map_err(Error::Visitor)
239 }
240
241 async fn visit_advisory(
242 &self,
243 context: &Self::Context,
244 result: Result<RetrievedAdvisory, RetrievalError<DiscoveredAdvisory, S>>,
245 ) -> Result<(), Self::Error> {
246 let result = match result {
247 Ok(doc) => self.verify(doc).await,
248 Err(err) => Err(VerificationError::Upstream(err)),
249 };
250
251 self.visitor
252 .visit_advisory(context, result)
253 .await
254 .map_err(Error::Visitor)?;
255
256 Ok(())
257 }
258}
259
260impl<V, I, S> ValidatedVisitor<S> for VerifyingVisitor<ValidatedAdvisory, ValidationError<S>, V, I>
261where
262 V: VerifiedVisitor<ValidatedAdvisory, ValidationError<S>, I>,
263 I: Clone + PartialEq + Eq + Hash,
264 S: Source,
265{
266 type Error = Error<V::Error>;
267 type Context = V::Context;
268
269 async fn visit_context(
270 &self,
271 _context: &ValidationContext<'_>,
272 ) -> Result<Self::Context, Self::Error> {
273 self.visitor
274 .visit_context(&VerificationContext {})
275 .await
276 .map_err(Error::Visitor)
277 }
278
279 async fn visit_advisory(
280 &self,
281 context: &Self::Context,
282 result: Result<ValidatedAdvisory, ValidationError<S>>,
283 ) -> Result<(), Self::Error> {
284 let result = match result {
285 Ok(doc) => self.verify(doc).await,
286 Err(err) => Err(VerificationError::Upstream(err)),
287 };
288
289 self.visitor
290 .visit_advisory(context, result)
291 .await
292 .map_err(Error::Visitor)?;
293
294 Ok(())
295 }
296}
297
298impl<F, E, Fut, A, I, UE> VerifiedVisitor<A, UE, I> for F
299where
300 UE: Debug + Display + 'static,
301 F: Fn(Result<VerifiedAdvisory<A, I>, VerificationError<UE, A>>) -> Fut,
302 Fut: Future<Output = Result<(), E>>,
303 E: Display + Debug + 'static,
304 A: AsRetrieved + 'static,
305 I: Clone + PartialEq + Eq + Hash + 'static,
306{
307 type Error = E;
308 type Context = ();
309
310 async fn visit_context(
311 &self,
312 _context: &VerificationContext,
313 ) -> Result<Self::Context, Self::Error> {
314 Ok(())
315 }
316
317 async fn visit_advisory(
318 &self,
319 _ctx: &Self::Context,
320 outcome: Result<VerifiedAdvisory<A, I>, VerificationError<UE, A>>,
321 ) -> Result<(), Self::Error> {
322 self(outcome).await
323 }
324}