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