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