Skip to main content

crev_wot/
lib.rs

1//! Crev - Web of Trust implementation
2//!
3//! # Introduction
4//!
5//! It's important to mention that Crev does not mandate
6//! any particular implementation of the Web of Trust. It only
7//! loosely defines data-format to describe trust relationships
8//! between users.
9//!
10//! How exactly is the trustworthiness in the wider network
11//! calculated remains an open question, and subject for experimentation.
12//!
13//! `crev-wot` is just an initial, reference implementation, and might
14//! evolve, be replaced or become just one of many available implementations.
15#![allow(clippy::default_trait_access)]
16#![allow(clippy::doc_markdown)]
17#![allow(clippy::if_not_else)]
18#![allow(clippy::missing_panics_doc)]
19#![allow(clippy::module_name_repetitions)]
20#![allow(clippy::redundant_closure_for_method_calls)]
21
22use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
23use std::sync;
24
25use chrono::offset::Utc;
26use chrono::{self, DateTime};
27use crev_data::proof::trust::TrustLevel;
28use crev_data::proof::{self, CommonOps, Content, review};
29use crev_data::{self, Digest, Id, Level, RegistrySource, Url, Version};
30use default::default;
31use log::debug;
32
33pub mod trust_set;
34pub use trust_set::TrustSet;
35
36#[derive(thiserror::Error, Debug)]
37pub enum Error {
38    #[error("Unknown proof type '{}'", _0)]
39    UnknownProofType(Box<str>),
40
41    #[error("{}", _0)]
42    Data(#[from] crev_data::Error),
43}
44
45type Result<T, E = Error> = std::result::Result<T, E>;
46
47/// Where a proof has been fetched from
48#[derive(Debug, Clone)]
49pub enum FetchSource {
50    /// Remote repository (other people's proof repos)
51    Url(sync::Arc<Url>),
52    /// One of user's own proof repos, which are assumed to contain only
53    /// verified information
54    LocalUser,
55}
56
57/// A `T` with a timestamp
58///
59/// This allows easily keeping track of a most recent version
60/// of `T`. Typically `T` is some information from a timestamped
61/// *proof* of some kind.
62#[derive(Clone, Debug)]
63pub struct Timestamped<T> {
64    pub date: chrono::DateTime<Utc>,
65    value: T,
66}
67
68impl<T> Timestamped<T> {
69    // Return `true` if value was updated
70    fn update_to_more_recent(&mut self, other: &Self)
71    where
72        T: Clone,
73    {
74        // in practice it doesn't matter, but in tests
75        // it's convenient to overwrite even if the time
76        // is exactly the same
77        if self.date <= other.date {
78            self.date = other.date;
79            self.value = other.value.clone();
80        }
81    }
82}
83
84impl<T, Tz> From<(&DateTime<Tz>, T)> for Timestamped<T>
85where
86    Tz: chrono::TimeZone,
87{
88    fn from(from: (&DateTime<Tz>, T)) -> Self {
89        Timestamped {
90            date: from.0.with_timezone(&Utc),
91            value: from.1,
92        }
93    }
94}
95
96pub type Signature = String;
97type TimestampedUrl = Timestamped<Url>;
98type TimestampedTrustLevel = Timestamped<TrustLevel>;
99type TimestampedReview = Timestamped<review::Review>;
100type TimestampedSignature = Timestamped<Signature>;
101type TimestampedDigest = Timestamped<proof::Digest>;
102type TimestampedFlags = Timestamped<proof::Flags>;
103
104impl From<proof::Trust> for TimestampedTrustLevel {
105    fn from(trust: proof::Trust) -> Self {
106        TimestampedTrustLevel {
107            date: trust.date_utc(),
108            value: trust.trust,
109        }
110    }
111}
112
113impl<T: proof::WithReview + Content + CommonOps> From<&T> for TimestampedReview {
114    fn from(review: &T) -> Self {
115        TimestampedReview {
116            value: review.review().clone(),
117            date: review.date_utc(),
118        }
119    }
120}
121
122/// Unique package review id
123///
124/// Since package review can be overwritten, it's useful
125/// to refer to a review by an unique combination of:
126///
127/// * author's ID
128/// * pkg source
129/// * pkg name
130/// * pkg version
131#[derive(Hash, Debug, Clone, PartialEq, Eq)]
132pub struct PkgVersionReviewId {
133    from: Id,
134    package_version_id: proof::PackageVersionId,
135}
136
137impl From<review::Package> for PkgVersionReviewId {
138    fn from(review: review::Package) -> Self {
139        PkgVersionReviewId {
140            from: review.from().id.clone(),
141            package_version_id: review.package.id,
142        }
143    }
144}
145
146impl From<&review::Package> for PkgVersionReviewId {
147    fn from(review: &review::Package) -> Self {
148        PkgVersionReviewId {
149            from: review.from().id.clone(),
150            package_version_id: review.package.id.clone(),
151        }
152    }
153}
154
155/// An unique id for a review by a given author of a given package.
156///
157/// Similar to `PackageVersionReviewId`, but where
158/// exact version is not important.
159#[derive(Hash, Debug, Clone, PartialEq, Eq)]
160pub struct PkgReviewId {
161    from: Id,
162    package_id: proof::PackageId,
163}
164
165impl From<review::Package> for PkgReviewId {
166    fn from(review: review::Package) -> Self {
167        PkgReviewId {
168            from: review.from().id.clone(),
169            package_id: review.package.id.id,
170        }
171    }
172}
173
174impl From<&review::Package> for PkgReviewId {
175    fn from(review: &review::Package) -> Self {
176        PkgReviewId {
177            from: review.from().id.clone(),
178            package_id: review.package.id.id.clone(),
179        }
180    }
181}
182
183/// Just a `String` version of `RegistrySource`, almost always `"https://crates.io"`.
184pub type RegistrySourceOwned = String;
185/// Crate name
186pub type Name = String;
187
188/// Alternatives relationship
189///
190/// Derived from the data in the proofs
191#[derive(Default)]
192struct AlternativesData {
193    derived_recalculation_counter: usize,
194    for_pkg: HashMap<proof::PackageId, HashMap<Id, HashSet<proof::PackageId>>>,
195    reported_by: HashMap<(proof::PackageId, proof::PackageId), HashMap<Id, Signature>>,
196}
197
198impl AlternativesData {
199    fn new() -> Self {
200        Default::default()
201    }
202
203    fn wipe(&mut self) {
204        *self = Self::new();
205    }
206
207    fn record_from_proof(&mut self, review: &review::Package, signature: &Signature) {
208        for alternative in &review.alternatives {
209            let a = &review.package.id.id;
210            let b = alternative;
211            let id = &review.from().id;
212            self.for_pkg
213                .entry(a.clone())
214                .or_default()
215                .entry(id.clone())
216                .or_default()
217                .insert(b.clone());
218
219            self.for_pkg
220                .entry(b.clone())
221                .or_default()
222                .entry(id.clone())
223                .or_default()
224                .insert(a.clone());
225
226            self.reported_by
227                .entry((a.clone(), b.clone()))
228                .or_default()
229                .insert(id.clone(), signature.clone());
230
231            self.reported_by
232                .entry((b.clone(), a.clone()))
233                .or_default()
234                .insert(id.clone(), signature.clone());
235        }
236    }
237}
238
239pub type TimestampedTrustDetails = Timestamped<TrustDetails>;
240
241#[derive(Debug, Clone)]
242pub struct TrustDetails {
243    level: TrustLevel,
244    override_: HashSet<Id>,
245}
246
247/// In memory database tracking information from proofs
248///
249/// After population, used for calculating the effective trust set, etc.
250///
251/// Right now, for every invocation of crev, we just load it up with
252/// all known proofs, and then query. If it ever becomes too slow,
253/// all the logic here will have to be moved to a real embedded db
254/// of some kind.
255pub struct ProofDB {
256    /// who -(trusts)-> whom
257    trust_id_to_id: HashMap<Id, HashMap<Id, TimestampedTrustDetails>>,
258    /// who -(is being trusted by -> whom
259    reverse_trust_id_to_id: HashMap<Id, HashMap<Id, TimestampedTrustLevel>>,
260
261    /// (source, target) -> -(trust via)-> trust proof certificate
262    ids_to_trust_proof_signatures: HashMap<(Id, Id), TimestampedSignature>,
263
264    /// Id->URL mapping verified by Id's signature
265    /// boolean is whether it's been fetched from the same URL, or local trusted
266    /// repo, so that URL->Id is also true.
267    url_by_id_self_reported: HashMap<Id, (TimestampedUrl, bool)>,
268
269    /// Id->URL relationship reported by someone else that this Id
270    url_by_id_reported_by_others: HashMap<Id, TimestampedUrl>,
271
272    // all reviews are here
273    package_review_by_signature: HashMap<Signature, review::Package>,
274
275    // all trust proofs here
276    trust_proofs_by_signature: HashMap<Signature, proof::Trust>,
277
278    // we can get the to the review through the signature from these two
279    package_review_signatures_by_package_digest:
280        HashMap<Vec<u8>, HashMap<PkgVersionReviewId, TimestampedSignature>>,
281    package_review_signatures_by_pkg_review_id: HashMap<PkgVersionReviewId, TimestampedSignature>,
282
283    // given a pkg_review_id, get proof digest
284    proof_digest_by_pkg_review_id: HashMap<PkgVersionReviewId, TimestampedDigest>,
285
286    // pkg_review_id by package information, nicely grouped
287    package_reviews: BTreeMap<
288        RegistrySourceOwned,
289        BTreeMap<Name, BTreeMap<Version, HashSet<PkgVersionReviewId>>>,
290    >,
291
292    package_flags: HashMap<proof::PackageId, HashMap<Id, TimestampedFlags>>,
293
294    // given an Id of an author, get the list of all package version id that were produced by it
295    from_id_to_package_reviews: HashMap<Id, HashSet<proof::PackageVersionId>>,
296
297    // original data about pkg alternatives
298    // for every package_id, we store a map of ids that had alternatives for it,
299    // and a timestamped signature of the proof, so we keep track of only
300    // the newest alternatives list for a `(PackageId, reporting Id)` pair
301    package_alternatives: HashMap<proof::PackageId, HashMap<Id, TimestampedSignature>>,
302
303    // derived data about pkg alternatives
304    // it is hard to keep track of some data when proofs are being added
305    // which can override previously stored information; because of that
306    // we don't keep track of it, until needed, and only then we just lazily
307    // recalculate it
308    insertion_counter: usize,
309    derived_alternatives: sync::RwLock<AlternativesData>,
310}
311
312impl Default for ProofDB {
313    fn default() -> Self {
314        ProofDB {
315            trust_id_to_id: default(),
316            reverse_trust_id_to_id: default(),
317            ids_to_trust_proof_signatures: default(),
318            trust_proofs_by_signature: default(),
319            url_by_id_self_reported: default(),
320            url_by_id_reported_by_others: default(),
321            package_review_signatures_by_package_digest: default(),
322            package_review_signatures_by_pkg_review_id: default(),
323            proof_digest_by_pkg_review_id: default(),
324            package_review_by_signature: default(),
325            package_reviews: default(),
326            package_alternatives: default(),
327            package_flags: default(),
328            from_id_to_package_reviews: default(),
329
330            insertion_counter: 0,
331            derived_alternatives: sync::RwLock::new(AlternativesData::new()),
332        }
333    }
334}
335
336/// Result of `get_open_issues_for_version`
337#[derive(Default, Debug)]
338pub struct IssueDetails {
339    pub severity: Level,
340    /// Reviews that reported a given issue by `issues` field
341    pub issues: HashSet<PkgVersionReviewId>,
342    /// Reviews that reported a given issue by `advisories` field
343    pub advisories: HashSet<PkgVersionReviewId>,
344}
345
346impl ProofDB {
347    /// Use `Local::load_db()` to populate it
348    #[must_use]
349    pub fn new() -> Self {
350        default()
351    }
352
353    fn get_derived_alternatives(&self) -> sync::RwLockReadGuard<'_, AlternativesData> {
354        {
355            let read = self.derived_alternatives.read().expect("lock to work");
356
357            if read.derived_recalculation_counter == self.insertion_counter {
358                return read;
359            }
360        }
361
362        {
363            let mut write = self.derived_alternatives.write().expect("lock to work");
364
365            write.wipe();
366
367            for alt in self.package_alternatives.values() {
368                for signature in alt.values() {
369                    write.record_from_proof(
370                        &self.package_review_by_signature[&signature.value],
371                        &signature.value,
372                    );
373                }
374            }
375
376            write.derived_recalculation_counter = self.insertion_counter;
377        }
378
379        self.derived_alternatives.read().expect("lock to work")
380    }
381
382    pub fn get_pkg_alternatives_by_author(
383        &self,
384        from: &Id,
385        pkg_id: &proof::PackageId,
386    ) -> HashSet<proof::PackageId> {
387        let from = from.clone();
388
389        let alternatives = self.get_derived_alternatives();
390        alternatives
391            .for_pkg
392            .get(pkg_id)
393            .into_iter()
394            .filter_map(move |i| i.get(&from))
395            .flatten()
396            .cloned()
397            .collect()
398    }
399
400    pub fn get_pkg_alternatives(
401        &self,
402        pkg_id: &proof::PackageId,
403    ) -> HashSet<(Id, proof::PackageId)> {
404        let alternatives = self.get_derived_alternatives();
405
406        alternatives
407            .for_pkg
408            .get(pkg_id)
409            .into_iter()
410            .flat_map(move |i| i.iter())
411            .flat_map(move |(id, pkg_ids)| pkg_ids.iter().map(move |v| (id.clone(), v.clone())))
412            .collect()
413    }
414
415    pub fn get_pkg_flags_by_author<'s>(
416        &'s self,
417        from: &Id,
418        pkg_id: &proof::PackageId,
419    ) -> Option<&'s proof::Flags> {
420        let from = from.clone();
421        self.package_flags
422            .get(pkg_id)
423            .and_then(move |i| i.get(&from))
424            .map(move |timestampted| &timestampted.value)
425    }
426
427    pub fn get_pkg_flags(
428        &self,
429        pkg_id: &proof::PackageId,
430    ) -> impl Iterator<Item = (&Id, &proof::Flags)> {
431        self.package_flags
432            .get(pkg_id)
433            .into_iter()
434            .flat_map(move |i| i.iter())
435            .map(|(id, flags)| (id, &flags.value))
436    }
437
438    /// Use `"https://crates.io"` to get all crates-io reviews
439    pub fn get_pkg_reviews_for_source<'a>(
440        &'a self,
441        source: RegistrySource<'_>,
442    ) -> impl Iterator<Item = &'a proof::review::Package> {
443        self.package_reviews
444            .get(source)
445            .into_iter()
446            .flat_map(move |map| map.iter())
447            .flat_map(move |(_, map)| map.iter())
448            .flat_map(|(_, v)| v)
449            .map(move |pkg_review_id| {
450                self.get_pkg_review_by_pkg_review_id(pkg_review_id)
451                    .expect("exists")
452            })
453    }
454
455    pub fn get_pkg_reviews_for_name<'a, 'b, 'c: 'a>(
456        &'a self,
457        source: RegistrySource<'b>,
458        name: &'c str,
459    ) -> impl Iterator<Item = &'a proof::review::Package> {
460        self.package_reviews
461            .get(source)
462            .into_iter()
463            .filter_map(move |map| map.get(name))
464            .flat_map(move |map| map.iter())
465            .flat_map(|(_, v)| v)
466            .map(move |pkg_review_id| {
467                self.get_pkg_review_by_pkg_review_id(pkg_review_id)
468                    .expect("exists")
469            })
470    }
471
472    pub fn get_pkg_reviews_for_version<'a, 'b, 'c: 'a, 'd: 'a>(
473        &'a self,
474        source: RegistrySource<'b>,
475        name: &'c str,
476        version: &'d Version,
477    ) -> impl Iterator<Item = &'a proof::review::Package> {
478        self.package_reviews
479            .get(source)
480            .into_iter()
481            .filter_map(move |map| map.get(name))
482            .filter_map(move |map| map.get(version))
483            .flatten()
484            .map(move |pkg_review_id| {
485                self.get_pkg_review_by_pkg_review_id(pkg_review_id)
486                    .expect("exists")
487            })
488    }
489
490    pub fn get_pkg_reviews_gte_version<'a, 'b, 'c: 'a, 'd: 'a>(
491        &'a self,
492        source: RegistrySource<'b>,
493        name: &'c str,
494        version: &'d Version,
495    ) -> impl Iterator<Item = &'a proof::review::Package> {
496        self.package_reviews
497            .get(source)
498            .into_iter()
499            .filter_map(move |map| map.get(name))
500            .flat_map(move |map| map.range(version..))
501            .flat_map(move |(_, v)| v)
502            .map(move |pkg_review_id| {
503                self.get_pkg_review_by_pkg_review_id(pkg_review_id)
504                    .expect("exists")
505            })
506    }
507
508    pub fn get_pkg_reviews_lte_version<'a, 'b, 'c: 'a, 'd: 'a>(
509        &'a self,
510        source: RegistrySource<'b>,
511        name: &'c str,
512        version: &'d Version,
513    ) -> impl Iterator<Item = &'a proof::review::Package> {
514        self.package_reviews
515            .get(source)
516            .into_iter()
517            .filter_map(move |map| map.get(name))
518            .flat_map(move |map| map.range(..=version))
519            .flat_map(|(_, v)| v)
520            .map(move |pkg_review_id| {
521                self.get_pkg_review_by_pkg_review_id(pkg_review_id)
522                    .expect("exists")
523            })
524    }
525
526    pub fn get_pkg_review_by_pkg_review_id(
527        &self,
528        uniq: &PkgVersionReviewId,
529    ) -> Option<&proof::review::Package> {
530        let signature = &self
531            .package_review_signatures_by_pkg_review_id
532            .get(uniq)?
533            .value;
534        self.package_review_by_signature.get(signature)
535    }
536
537    pub fn get_proof_digest_by_pkg_review_id(
538        &self,
539        uniq: &PkgVersionReviewId,
540    ) -> Option<&proof::Digest> {
541        Some(&self.proof_digest_by_pkg_review_id.get(uniq)?.value)
542    }
543
544    pub fn get_pkg_review<'a, 'b, 'c: 'a, 'd: 'a>(
545        &'a self,
546        source: RegistrySource<'b>,
547        name: &'c str,
548        version: &'d Version,
549        id: &Id,
550    ) -> Option<&'a proof::review::Package> {
551        self.get_pkg_reviews_for_version(source, name, version)
552            .find(|pkg_review| pkg_review.from().id == *id)
553    }
554
555    pub fn get_advisories<'a, 'b: 'a, 'c: 'a, 'd: 'a>(
556        &'a self,
557        source: RegistrySource<'b>,
558        name: Option<&'c str>,
559        version: Option<&'d Version>,
560    ) -> impl Iterator<Item = &'a proof::review::Package> + 'a {
561        match (name, version) {
562            (Some(name), Some(version)) => {
563                Box::new(self.get_advisories_for_version(source, name, version))
564                    as Box<dyn Iterator<Item = _>>
565            }
566
567            (Some(name), None) => Box::new(self.get_advisories_for_package(source, name)),
568            (None, None) => Box::new(self.get_advisories_for_source(source)),
569            (None, Some(_)) => panic!("Wrong usage"),
570        }
571    }
572
573    pub fn get_pkg_reviews_with_issues_for<'a, 'b: 'a, 'c: 'a, 'd: 'a>(
574        &'a self,
575        source: RegistrySource<'b>,
576        name: Option<&'c str>,
577        version: Option<&'c Version>,
578        trust_set: &'d TrustSet,
579        trust_level_required: TrustLevel,
580    ) -> impl Iterator<Item = &'a proof::review::Package> + 'a {
581        match (name, version) {
582            (Some(name), Some(version)) => Box::new(self.get_pkg_reviews_with_issues_for_version(
583                source,
584                name,
585                version,
586                trust_set,
587                trust_level_required,
588            )) as Box<dyn Iterator<Item = _>>,
589            (Some(name), None) => Box::new(self.get_pkg_reviews_with_issues_for_name(
590                source,
591                name,
592                trust_set,
593                trust_level_required,
594            )),
595            (None, None) => Box::new(self.get_pkg_reviews_with_issues_for_source(
596                source,
597                trust_set,
598                trust_level_required,
599            )),
600            (None, Some(_)) => panic!("Wrong usage"),
601        }
602    }
603
604    pub fn get_advisories_for_version<'a, 'b, 'c: 'a, 'd: 'a>(
605        &'a self,
606        source: RegistrySource<'b>,
607        name: &'c str,
608        version: &'d Version,
609    ) -> impl Iterator<Item = &'a proof::review::Package> {
610        self.get_pkg_reviews_gte_version(source, name, version)
611            .filter(move |review| review.is_advisory_for(version))
612    }
613
614    pub fn get_advisories_for_package<'a, 'b, 'c: 'a>(
615        &'a self,
616        source: RegistrySource<'b>,
617        name: &'c str,
618    ) -> impl Iterator<Item = &'a proof::review::Package> {
619        self.package_reviews
620            .get(source)
621            .into_iter()
622            .filter_map(move |map| map.get(name))
623            .flat_map(move |map| map.iter())
624            .flat_map(|(_, v)| v)
625            .filter_map(move |pkg_review_id| {
626                let review = &self.package_review_by_signature
627                    [&self.package_review_signatures_by_pkg_review_id[pkg_review_id].value];
628
629                if !review.advisories.is_empty() {
630                    Some(review)
631                } else {
632                    None
633                }
634            })
635    }
636
637    pub fn get_advisories_for_source(
638        &self,
639        source: RegistrySource<'_>,
640    ) -> impl Iterator<Item = &proof::review::Package> {
641        self.get_pkg_reviews_for_source(source)
642            .filter(|review| !review.advisories.is_empty())
643    }
644
645    /// Get all issues affecting a given package version
646    ///
647    /// Collect a map of Issue ID -> `IssueReports`, listing
648    /// all issues known to affect a given package version.
649    ///
650    /// These are calculated from `advisories` and `issues` fields
651    /// of the package reviews of reviewers intside a given `trust_set`
652    /// of at least given `trust_level_required`.
653    pub fn get_open_issues_for_version(
654        &self,
655        source: RegistrySource<'_>,
656        name: &str,
657        queried_version: &Version,
658        trust_set: &TrustSet,
659        trust_level_required: TrustLevel,
660    ) -> HashMap<String, IssueDetails> {
661        // This is one of the most complicated calculations in whole crev. I hate this
662        // code already, and I have barely put it together.
663
664        // Here we track all the reported issue by issue id
665        let mut issue_reports_by_id: HashMap<String, IssueDetails> = HashMap::new();
666
667        // First we go through all the reports in previous versions with `issues` fields
668        // and collect these. Easy.
669        for (review, issue) in self
670            .get_pkg_reviews_lte_version(source, name, queried_version)
671            .filter(|review| {
672                let effective = trust_set.get_effective_trust_level(&review.from().id);
673                effective >= trust_level_required
674            })
675            .flat_map(move |review| review.issues.iter().map(move |issue| (review, issue)))
676            .filter(|(review, issue)| {
677                issue.is_for_version_when_reported_in_version(
678                    queried_version,
679                    &review.package.id.version,
680                )
681            })
682        {
683            issue_reports_by_id
684                .entry(issue.id.clone())
685                .or_default()
686                .issues
687                .insert(PkgVersionReviewId::from(review));
688        }
689
690        // Now the complicated part. We go through all the advisories for all the
691        // versions of given package.
692        //
693        // Advisories itself have two functions: first, they might have report an issue
694        // by advertising that a given version should be upgraded to a newer version.
695        //
696        // Second - they might cancel `issues` inside `issue_reports_by_id` because they
697        // advertise a fix that happened somewhere between the `issue` report and
698        // the current `queried_version`.
699        for (review, advisory) in self
700            .get_pkg_reviews_for_name(source, name)
701            .filter(|review| {
702                let effective = trust_set.get_effective_trust_level(&review.from().id);
703                effective >= trust_level_required
704            })
705            .flat_map(move |review| {
706                review
707                    .advisories
708                    .iter()
709                    .map(move |advisory| (review, advisory))
710            })
711        {
712            // Add new issue reports created by the advisory
713            if advisory.is_for_version_when_reported_in_version(
714                queried_version,
715                &review.package.id.version,
716            ) {
717                for id in &advisory.ids {
718                    issue_reports_by_id
719                        .entry(id.clone())
720                        .or_default()
721                        .issues
722                        .insert(PkgVersionReviewId::from(review));
723                }
724            }
725
726            // Remove the reports that are already fixed
727            for id in &advisory.ids {
728                if let Some(issue_marker) = issue_reports_by_id.get_mut(id) {
729                    let issues = std::mem::take(&mut issue_marker.issues);
730                    issue_marker.issues = issues
731                        .into_iter()
732                        .filter(|pkg_review_id| {
733                            let signature = &self
734                                .package_review_signatures_by_pkg_review_id
735                                .get(pkg_review_id)
736                                .expect("review for this signature")
737                                .value;
738                            let issue_review = self
739                                .package_review_by_signature
740                                .get(signature)
741                                .expect("review for this pkg_review_id");
742                            !advisory.is_for_version_when_reported_in_version(
743                                &issue_review.package.id.version,
744                                &review.package.id.version,
745                            )
746                        })
747                        .collect();
748                }
749            }
750        }
751
752        issue_reports_by_id
753            .into_iter()
754            .filter(|(_id, markers)| !markers.issues.is_empty() || !markers.advisories.is_empty())
755            .collect()
756    }
757
758    pub fn get_pkg_reviews_with_issues_for_version<'a, 'b, 'c: 'a>(
759        &'a self,
760        source: RegistrySource<'b>,
761        name: &'c str,
762        queried_version: &'c Version,
763        trust_set: &'c TrustSet,
764        trust_level_required: TrustLevel,
765    ) -> impl Iterator<Item = &'a proof::review::Package> {
766        self.get_pkg_reviews_with_issues_for_name(source, name, trust_set, trust_level_required)
767            .filter(move |review| {
768                !review.issues.is_empty()
769                    || review.advisories.iter().any(|advi| {
770                        advi.is_for_version_when_reported_in_version(
771                            queried_version,
772                            &review.package.id.version,
773                        )
774                    })
775            })
776    }
777
778    pub fn get_pkg_reviews_with_issues_for_name<'a, 'b, 'c: 'a>(
779        &'a self,
780        source: RegistrySource<'b>,
781        name: &'c str,
782        trust_set: &'c TrustSet,
783        trust_level_required: TrustLevel,
784    ) -> impl Iterator<Item = &'a proof::review::Package> {
785        self.get_pkg_reviews_for_name(source, name)
786            .filter(move |review| {
787                let effective = trust_set.get_effective_trust_level(&review.from().id);
788                effective >= trust_level_required
789            })
790            .filter(|review| !review.issues.is_empty() || !review.advisories.is_empty())
791    }
792
793    pub fn get_pkg_reviews_with_issues_for_source<'a, 'b, 'c: 'a>(
794        &'a self,
795        source: RegistrySource<'b>,
796        trust_set: &'c TrustSet,
797        trust_level_required: TrustLevel,
798    ) -> impl Iterator<Item = &'a proof::review::Package> {
799        self.get_pkg_reviews_for_source(source)
800            .filter(move |review| {
801                let effective = trust_set.get_effective_trust_level(&review.from().id);
802                effective >= trust_level_required
803            })
804            .filter(|review| !review.issues.is_empty() || !review.advisories.is_empty())
805    }
806
807    pub fn unique_package_review_proof_count(&self) -> usize {
808        self.package_review_signatures_by_pkg_review_id.len()
809    }
810
811    pub fn unique_trust_proof_count(&self) -> usize {
812        self.trust_id_to_id
813            .iter()
814            .fold(0, |count, (_id, set)| count + set.len())
815    }
816
817    fn add_code_review(&mut self, review: &review::Code, fetched_from: &FetchSource) {
818        let from = &review.from();
819        self.record_url_from_from_field(&review.date_utc(), from, fetched_from);
820        for _file in &review.files {
821            // not implemented right now; just ignore
822        }
823    }
824
825    fn add_package_review(
826        &mut self,
827        review: review::Package,
828        signature: &str,
829        fetched_from: &FetchSource,
830        proof_digest: proof::Digest,
831    ) {
832        self.insertion_counter += 1;
833
834        let from = review.from();
835        self.record_url_from_from_field(&review.date_utc(), from, fetched_from);
836
837        let pkg_review_id = PkgVersionReviewId::from(&review);
838        let timestamp_signature = TimestampedSignature::from((review.date(), signature.to_owned()));
839        let timestamp_proof_digest = TimestampedDigest::from((review.date(), proof_digest));
840        let timestamp_flags = TimestampedFlags::from((review.date(), review.flags.clone()));
841
842        self.package_review_signatures_by_package_digest
843            .entry(review.package.digest.clone())
844            .or_default()
845            .entry(pkg_review_id.clone())
846            .and_modify(|s| s.update_to_more_recent(&timestamp_signature))
847            .or_insert_with(|| timestamp_signature.clone());
848
849        self.package_review_signatures_by_pkg_review_id
850            .entry(pkg_review_id.clone())
851            .and_modify(|s| s.update_to_more_recent(&timestamp_signature))
852            .or_insert_with(|| timestamp_signature.clone());
853
854        self.proof_digest_by_pkg_review_id
855            .entry(pkg_review_id.clone())
856            .and_modify(|s| s.update_to_more_recent(&timestamp_proof_digest))
857            .or_insert_with(|| timestamp_proof_digest.clone());
858
859        self.from_id_to_package_reviews
860            .entry(review.common.from.id.clone())
861            .or_default()
862            .insert(pkg_review_id.package_version_id.clone());
863
864        self.package_reviews
865            .entry(review.package.id.id.source.clone())
866            .or_default()
867            .entry(review.package.id.id.name.clone())
868            .or_default()
869            .entry(review.package.id.version.clone())
870            .or_default()
871            .insert(pkg_review_id);
872
873        self.package_alternatives
874            .entry(review.package.id.id.clone())
875            .or_default()
876            .entry(review.from().id.clone())
877            .and_modify(|a| a.update_to_more_recent(&timestamp_signature))
878            .or_insert_with(|| timestamp_signature);
879
880        self.package_flags
881            .entry(review.package.id.id.clone())
882            .or_default()
883            .entry(review.from().id.clone())
884            .and_modify(|f| f.update_to_more_recent(&timestamp_flags))
885            .or_insert_with(|| timestamp_flags);
886
887        self.package_review_by_signature
888            .entry(signature.to_owned())
889            .or_insert(review);
890    }
891
892    pub fn get_package_review_count(
893        &self,
894        source: RegistrySource<'_>,
895        name: Option<&str>,
896        version: Option<&Version>,
897    ) -> usize {
898        self.get_package_reviews_for_package(source, name, version)
899            .count()
900    }
901
902    pub fn get_package_reviews_for_package<'a, 'b: 'a, 'c: 'a, 'd: 'a>(
903        &'a self,
904        source: RegistrySource<'b>,
905        name: Option<&'c str>,
906        version: Option<&'d Version>,
907    ) -> impl Iterator<Item = &'a proof::review::Package> + 'a {
908        match (name, version) {
909            (Some(name), Some(version)) => {
910                Box::new(self.get_pkg_reviews_for_version(source, name, version))
911                    as Box<dyn Iterator<Item = _>>
912            }
913            (Some(name), None) => Box::new(self.get_pkg_reviews_for_name(source, name)),
914            (None, None) => Box::new(self.get_pkg_reviews_for_source(source)),
915            (None, Some(_)) => panic!("Wrong usage"),
916        }
917    }
918
919    pub fn get_package_reviews_for_package_sorted<'a, 'b: 'a, 'c: 'a, 'd: 'a>(
920        &'a self,
921        source: RegistrySource<'b>,
922        name: Option<&'c str>,
923        version: Option<&'d Version>,
924    ) -> Vec<proof::review::Package> {
925        let mut proofs: Vec<_> = self
926            .get_package_reviews_for_package(source, name, version)
927            .cloned()
928            .collect();
929
930        proofs.sort_by_key(|a| a.date_utc());
931
932        proofs
933    }
934
935    fn add_trust_raw(
936        &mut self,
937        from: &Id,
938        to: &Id,
939        date: DateTime<Utc>,
940        trust_proof: &proof::Trust,
941        signature: &str,
942    ) {
943        let trust = TrustDetails {
944            level: trust_proof.trust,
945            override_: trust_proof
946                .override_
947                .iter()
948                .map(|o| o.id.id.clone())
949                .collect(),
950        };
951
952        let tl = TimestampedTrustLevel {
953            value: trust.level,
954            date,
955        };
956        let td = TimestampedTrustDetails { value: trust, date };
957
958        self.trust_proofs_by_signature
959            .insert(signature.to_owned(), trust_proof.clone());
960
961        let signature = TimestampedSignature {
962            value: signature.to_owned(),
963            date,
964        };
965
966        self.ids_to_trust_proof_signatures
967            .entry((from.clone(), to.clone()))
968            .and_modify(|e| e.update_to_more_recent(&signature))
969            .or_insert_with(|| signature);
970
971        self.trust_id_to_id
972            .entry(from.clone())
973            .or_default()
974            .entry(to.clone())
975            .and_modify(|e| e.update_to_more_recent(&td))
976            .or_insert_with(|| td);
977
978        self.reverse_trust_id_to_id
979            .entry(to.clone())
980            .or_default()
981            .entry(from.clone())
982            .and_modify(|e| e.update_to_more_recent(&tl))
983            .or_insert_with(|| tl);
984    }
985
986    fn add_trust(&mut self, trust: &proof::Trust, signature: &str, fetched_from: &FetchSource) {
987        let from = &trust.from();
988        self.record_url_from_from_field(&trust.date_utc(), from, fetched_from);
989        for to in &trust.ids {
990            self.add_trust_raw(&from.id, &to.id, trust.date_utc(), trust, signature);
991        }
992        for to in &trust.ids {
993            // Others should not be making verified claims about this URL,
994            // regardless of where these proofs were fetched from, because only
995            // owner of the Id is authoritative.
996            self.record_url_from_to_field(&trust.date_utc(), to);
997        }
998    }
999
1000    pub fn all_known_ids(&self) -> BTreeSet<Id> {
1001        self.url_by_id_self_reported
1002            .keys()
1003            .chain(self.url_by_id_reported_by_others.keys())
1004            .cloned()
1005            .collect()
1006    }
1007
1008    /// Only for direct relationship. See `calculate_trust_set`.
1009    pub fn get_reverse_trust_for<'id, 's: 'id>(
1010        &'s self,
1011        id: &'id Id,
1012    ) -> impl Iterator<Item = (&'id Id, TrustLevel)> + 's {
1013        self.reverse_trust_id_to_id
1014            .get(id)
1015            .into_iter()
1016            .flat_map(|map| map.iter().map(|(id, trust_level)| (id, trust_level.value)))
1017    }
1018
1019    /// Get all Ids that authored a proof (with total count)
1020    pub fn all_author_ids(&self) -> BTreeMap<Id, usize> {
1021        let mut res = BTreeMap::new();
1022        for (id, set) in &self.trust_id_to_id {
1023            *res.entry(id.clone()).or_default() += set.len();
1024        }
1025
1026        for uniq_rev in self.package_review_signatures_by_pkg_review_id.keys() {
1027            *res.entry(uniq_rev.from.clone()).or_default() += 1;
1028        }
1029
1030        res
1031    }
1032
1033    pub fn get_package_review_by_signature<'a>(
1034        &'a self,
1035        signature: &str,
1036    ) -> Option<&'a review::Package> {
1037        self.package_review_by_signature.get(signature)
1038    }
1039
1040    pub fn get_package_reviews_by_digest<'a>(
1041        &'a self,
1042        digest: &Digest,
1043    ) -> impl Iterator<Item = review::Package> + 'a {
1044        self.package_review_signatures_by_package_digest
1045            .get(digest.as_slice())
1046            .into_iter()
1047            .flat_map(move |unique_reviews| {
1048                unique_reviews
1049                    .values()
1050                    .map(|signature| self.package_review_by_signature[&signature.value].clone())
1051            })
1052    }
1053
1054    /// Record an untrusted mapping between a `PublicId` and a URL it declares
1055    fn record_url_from_to_field(&mut self, date: &DateTime<Utc>, to: &crev_data::PublicId) {
1056        if let Some(url) = &to.url {
1057            self.url_by_id_reported_by_others
1058                .entry(to.id.clone())
1059                .or_insert_with(|| TimestampedUrl {
1060                    value: url.clone(),
1061                    date: *date,
1062                });
1063        }
1064    }
1065
1066    pub fn record_trusted_url_from_own_id(&mut self, own_id: &crev_data::PublicId) {
1067        self.record_url_from_from_field(&Utc::now(), own_id, &FetchSource::LocalUser);
1068    }
1069
1070    /// Record mapping between a `PublicId` and a URL it declares, and trust
1071    /// it's correct only if it's been fetched from the same URL
1072    fn record_url_from_from_field(
1073        &mut self,
1074        date: &DateTime<Utc>,
1075        from: &crev_data::PublicId,
1076        fetched_from: &FetchSource,
1077    ) {
1078        if let Some(url) = &from.url {
1079            let tu = TimestampedUrl {
1080                value: url.clone(),
1081                date: *date,
1082            };
1083            let fetch_matches = match fetched_from {
1084                FetchSource::LocalUser => true,
1085                FetchSource::Url(fetched_url) if **fetched_url == *url => true,
1086                FetchSource::Url(_other) => false,
1087            };
1088            self.url_by_id_self_reported
1089                .entry(from.id.clone())
1090                .and_modify(|e| {
1091                    e.0.update_to_more_recent(&tu);
1092                    if fetch_matches {
1093                        e.1 = true;
1094                    }
1095                })
1096                .or_insert_with(|| (tu, fetch_matches));
1097        }
1098    }
1099
1100    fn add_proof(&mut self, proof: &proof::Proof, fetched_from: FetchSource) -> Result<()> {
1101        proof
1102            .verify()
1103            .expect("All proofs were supposed to be valid here");
1104        match proof.kind() {
1105            proof::CodeReview::KIND => self.add_code_review(&proof.parse_content()?, &fetched_from),
1106            proof::PackageReview::KIND => self.add_package_review(
1107                proof.parse_content()?,
1108                proof.signature(),
1109                &fetched_from,
1110                proof::Digest(*proof.digest()),
1111            ),
1112            proof::Trust::KIND => {
1113                self.add_trust(&proof.parse_content()?, proof.signature(), &fetched_from);
1114            }
1115            other => return Err(Error::UnknownProofType(other.into())),
1116        }
1117
1118        Ok(())
1119    }
1120
1121    pub fn import_from_iter(&mut self, i: impl Iterator<Item = (proof::Proof, FetchSource)>) {
1122        for (proof, fetch_source) in i {
1123            // ignore errors
1124            if let Err(e) = self.add_proof(&proof, fetch_source) {
1125                debug!("Ignoring proof: {e}");
1126            }
1127        }
1128    }
1129
1130    fn get_trust_details_list_of_id(&self, id: &Id) -> impl Iterator<Item = (&TrustDetails, &Id)> {
1131        self.trust_id_to_id
1132            .get(id)
1133            .map(|map| map.iter().map(|(id, trust)| (&trust.value, id)))
1134            .into_iter()
1135            .flatten()
1136    }
1137
1138    /// Only for direct relationship. See `calculate_trust_set`.
1139    pub fn get_trust_proof_between(&self, from: &Id, to: &Id) -> Option<&proof::Trust> {
1140        self.ids_to_trust_proof_signatures
1141            .get(&(from.clone(), to.clone()))
1142            .and_then(|sig| self.trust_proofs_by_signature.get(&sig.value))
1143    }
1144
1145    fn get_package_reviews_by_author<'iter, 's: 'iter, 'id: 'iter>(
1146        &'s self,
1147        id: &'id Id,
1148    ) -> impl Iterator<Item = &'s review::Package> + 'iter {
1149        self.from_id_to_package_reviews
1150            .get(id)
1151            .into_iter()
1152            .flat_map(move |set| {
1153                set.iter()
1154                    .map(move |package_version_id| PkgVersionReviewId {
1155                        from: id.clone(),
1156                        package_version_id: package_version_id.clone(),
1157                    })
1158            })
1159            .map(move |pkg_version_review_id| {
1160                &self.package_review_by_signature
1161                    [&self.package_review_signatures_by_pkg_review_id[&pkg_version_review_id].value]
1162            })
1163    }
1164
1165    /// How much you trust others
1166    pub fn calculate_trust_set(&self, for_id: &Id, params: &TrustDistanceParams) -> TrustSet {
1167        TrustSet::from(self, for_id, params)
1168    }
1169
1170    /// Finds which URL is the latest and claimed to belong to the given Id.
1171    /// The result indicates how reliable information this is.
1172    pub fn lookup_url(&self, id: &Id) -> UrlOfId<'_> {
1173        self.url_by_id_self_reported
1174            .get(id)
1175            .map(|(url, fetch_matches)| {
1176                if *fetch_matches {
1177                    UrlOfId::FromSelfVerified(&url.value)
1178                } else {
1179                    UrlOfId::FromSelf(&url.value)
1180                }
1181            })
1182            .or_else(|| {
1183                self.url_by_id_reported_by_others
1184                    .get(id)
1185                    .map(|url| UrlOfId::FromOthers(&url.value))
1186            })
1187            .unwrap_or(UrlOfId::None)
1188    }
1189}
1190
1191/// Result of URL lookup
1192#[derive(Debug, Copy, Clone)]
1193pub enum UrlOfId<'a> {
1194    /// Verified both ways: Id->URL via signature,
1195    /// and URL->Id by fetching, or trusting local user
1196    FromSelfVerified(&'a Url),
1197    /// Self-reported (signed by this Id)
1198    FromSelf(&'a Url),
1199    /// Reported by someone else (unverified)
1200    FromOthers(&'a Url),
1201    /// Unknown
1202    None,
1203}
1204
1205impl<'a> UrlOfId<'a> {
1206    /// Only if this URL has been signed by its Id and verified by fetching
1207    #[must_use]
1208    pub fn verified(self) -> Option<&'a Url> {
1209        match self {
1210            Self::FromSelfVerified(url) => Some(url),
1211            _ => None,
1212        }
1213    }
1214
1215    /// Only if this URL has been signed by its Id
1216    #[must_use]
1217    pub fn from_self(self) -> Option<&'a Url> {
1218        match self {
1219            Self::FromSelfVerified(url) | Self::FromSelf(url) => Some(url),
1220            _ => None,
1221        }
1222    }
1223
1224    /// Any URL available, even if reported by someone else
1225    #[must_use]
1226    pub fn any_unverified(self) -> Option<&'a Url> {
1227        match self {
1228            Self::FromSelfVerified(url) | Self::FromSelf(url) | Self::FromOthers(url) => Some(url),
1229            Self::None => None,
1230        }
1231    }
1232}
1233
1234pub struct TrustDistanceParams {
1235    pub max_distance: u64,
1236    pub high_trust_distance: u64,
1237    pub medium_trust_distance: u64,
1238    pub low_trust_distance: u64,
1239    pub none_trust_distance: u64,
1240    pub distrust_distance: u64,
1241}
1242
1243impl TrustDistanceParams {
1244    #[must_use]
1245    pub fn new_no_wot() -> Self {
1246        Self {
1247            max_distance: 0,
1248            high_trust_distance: 1,
1249            medium_trust_distance: 1,
1250            low_trust_distance: 1,
1251            none_trust_distance: 1,
1252            distrust_distance: 1,
1253        }
1254    }
1255
1256    fn distance_by_level(&self, level: TrustLevel) -> u64 {
1257        use crev_data::proof::trust::TrustLevel::*;
1258        match level {
1259            Distrust => self.distrust_distance,
1260            None => self.none_trust_distance,
1261            Low => self.low_trust_distance,
1262            Medium => self.medium_trust_distance,
1263            High => self.high_trust_distance,
1264        }
1265    }
1266}
1267
1268impl Default for TrustDistanceParams {
1269    fn default() -> Self {
1270        Self {
1271            max_distance: 10,
1272            high_trust_distance: 0,
1273            medium_trust_distance: 1,
1274            low_trust_distance: 5,
1275            none_trust_distance: 11,
1276            distrust_distance: 11,
1277        }
1278    }
1279}
1280
1281/// List of authors recommending override (ignore) trust / package review with
1282/// their effective trust level.
1283#[derive(Debug, Clone, Default)]
1284pub struct OverrideSourcesDetails(HashMap<Id, TrustLevel>);
1285
1286impl OverrideSourcesDetails {
1287    pub fn insert(&mut self, id: Id, level: TrustLevel) {
1288        self.0
1289            .entry(id)
1290            .and_modify(|prev_level| *prev_level = level.max(*prev_level))
1291            .or_insert(level);
1292    }
1293
1294    #[must_use]
1295    pub fn max_level(&self) -> Option<TrustLevel> {
1296        self.0.iter().map(|e| e.1).max().copied()
1297    }
1298}
1299
1300#[test]
1301fn db_is_send_sync() {
1302    fn is<T: Send + Sync>() {}
1303    is::<ProofDB>();
1304}
1305
1306#[cfg(test)]
1307mod tests;