routinator/
store.rs

1//! A store for RPKI objects.
2//!
3//! To be more resistant against accidental or malicious errors in the data
4//! published by repositories, we retain a separate copy of all RPKI data that
5//! has been found to be covered by a valid manifest in what we call the
6//! _store._ The types in this module provide access to this store.
7//!
8//! The store is initialized and configured via [`Store`]. During validation,
9//! [`Run`] is used which can be acquired from the store via the
10//! [`start`][Store::start] method. It provides access to the trust anchor
11//! certificates via the [`load_ta`][Run::load_ta] and
12//! [`update_ta`][Run::update_ta] methods, and access to individual
13//! repositories and publication points via [`repository`][Run::repository]
14//! and [`pub_point`][Run::pub_point], respectively. These are represented
15//! by the [`Repository`] and [`StoredPoint`] types.
16//!
17//! # Error Handling
18//!
19//! Pretty much all methods and functions provided by this module can return
20//! an error. This is because the store uses files and that can go wrong in
21//! all kinds of ways at any time. The concrete error reason is logged and our
22//! generic [`Failed`][crate::error::Failed] is returned. When this happens,
23//! the store should be considered broken and not be used anymore.
24//!
25//! # Data Storage
26//!
27//! The store uses the file system to store its data. It has its dedicated
28//! directory within the RPKI repository directory, normally named `stored`
29//! (this is because an earlier version used `store` already). Within this
30//! directory are four sub-directories: `rrdp` and `rsync` contain the data
31//! for each stored publication point; `ta` contains the downloaded trust
32//! anchor certificates; and `tmp` is a directory for storing files as they
33//! are constructed.
34//!
35//! All publication points that do not support RRDP are stored under `rsync`.
36//! Each has a file stored at a path and file name derived from the
37//! signedObject URI of its manifest, starting with the authority part of the
38//! URI and then just following along. The file contains status information,
39//! the manifest, the CRL, and each object. It starts with a serialized
40//! [`StoredPointHeader`] which is primarily used to mark points requested
41//! but never successfully retrieved. If a point was successfully retrieved,
42//! the header is followed by a [`StoredManifest`] which in turn is followed
43//! by a sequence of serialized [`StoredObject`]s for all the objects as
44//! given on the manifest.
45//!
46//! All publication points that are hosted in an RRDP repository are stored
47//! under `rrdp`, independently of whether they have been retrieved via RRDP
48//! or rsync. Directly under `rrdp` is a set of directories for all the
49//! authorities (i.e., host names) of the RRDP servers seen. Within each of
50//! these is a set of directories named after the SHA-256 hash of the
51//! rpkiNotify URI of the RRDP repository. These directories in turn contain
52//! the same files for each publication point as in the rsync case above. They
53//! are similarly stored at a path and file name derived from the signedObject
54//! URI of the manifest with the `rsync` scheme used as the first component
55//! instead. (There is no good reason for that, it just happened.)
56//!
57//! Trust anchor certficates are stored under `ta` using a three level
58//! directory structure derived from the URI the certificate is retrieved
59//! from. The first level is the scheme, `https` or `rsync`, the second
60//! level is the authority (i.e., hostname), and the third is the SHA-256
61//! hash of the full URI with an extension of `.cer` added.
62//!
63//! Finally, the `tmp` directory is used to build the publication point files
64//! in so they can be constructed without yet knowing whether the update is
65//! actually complete and correct. File names here are named using eight
66//! random hex-digits.
67
68
69use std::{fs, io};
70use std::fs::File;
71use std::io::{BufReader, BufWriter, Seek, SeekFrom, Write};
72use std::path::{Path, PathBuf};
73use bytes::Bytes;
74use log::error;
75use rpki::uri;
76use rpki::crypto::DigestAlgorithm;
77use rpki::repository::cert::{Cert, ResourceCert};
78use rpki::repository::manifest::{ManifestContent, ManifestHash};
79use rpki::repository::tal::TalUri;
80use rpki::repository::x509::{Serial, Time};
81use tempfile::NamedTempFile;
82use crate::collector;
83use crate::config::Config;
84use crate::engine::CaCert;
85use crate::error::{Failed, Fatal, RunFailed};
86use crate::metrics::Metrics;
87use crate::utils::fatal;
88use crate::utils::binio::{Compose, Parse, ParseError};
89use crate::utils::dump::DumpRegistry;
90use crate::utils::json::JsonBuilder;
91use crate::utils::uri::UriExt;
92
93
94//------------ Store ---------------------------------------------------------
95
96/// A store for RPKI objects.
97///
98/// The store retains a copy of curated, published RPKI data. Its intended use
99/// is for keeping the most recent data of a given RPKI publication point that
100/// was found to be correctly published as well as keeping track of which
101/// publication points have been requested which is used during optimistic
102/// startup.
103///
104/// A store can be created via the [`new`][Store::new] function which will
105/// initialize a new store on disk if necessary and open it. If you only want
106/// to make sure that the store is initilized without actually using it,
107/// the [`init`][Store::init] function can be used.
108///
109/// To use the store during a validation run, the [`start`][Store::start]
110/// method is used. It returns a [`Run`] object providing actual access to
111/// the store.
112#[derive(Clone, Debug)]
113pub struct Store {
114    /// The base path for the store.
115    path: PathBuf,
116}
117
118impl Store {
119    /// The name of the status file.
120    const STATUS_NAME: &'static str = "status.bin";
121
122    /// The dirctory for TA certificates retrieved via rsync.
123    const RSYNC_TA_PATH: &'static str = "ta/rsync";
124
125    /// The dirctory for TA certificates retrieved via HTTPS.
126    const HTTPS_TA_PATH: &'static str = "ta/https";
127
128    /// The directory for the RRDP repositories.
129    const RRDP_BASE: &'static str = "rrdp";
130
131    /// The directory for the rsync repository.
132    const RSYNC_PATH: &'static str = "rsync";
133
134    /// The name of the directory where the temporary files go.
135    const TMP_BASE: &'static str = "tmp";
136}
137
138impl Store {
139    /// Returns the base path for the given config.
140    fn create_base_dir(config: &Config) -> Result<PathBuf, Failed> {
141        // We are using "stored" since store was foolishly used in 0.9.0 for
142        // the database.
143        let path = config.cache_dir.join("stored");
144        if let Err(err) = fs::create_dir_all(&path) {
145            error!(
146                "Failed to create store directory {}: {}",
147                path.display(), err
148            );
149            return Err(Failed)
150        }
151        Ok(path)
152    }
153
154    /// Initializes the store without creating a value.
155    ///
156    /// Ensures that the base directory exists and creates it of necessary.
157    ///
158    /// The function is called implicitly by [`new`][Self::new].
159    //  (Or, well, not really, but they both only call `create_base_dir`, so
160    //   from a user perspective it does.)
161    pub fn init(config: &Config) -> Result<(), Failed> {
162        Self::create_base_dir(config)?;
163        Ok(())
164    }
165
166    /// Creates a new store at the given path.
167    pub fn new(config: &Config) -> Result<Self, Failed> {
168        Ok(Store {
169            path: Self::create_base_dir(config)?,
170        })
171    }
172
173    /// Sanitizes the stored data.
174    ///
175    /// Currently doesn’t do anything.
176    pub fn sanitize(&self) -> Result<(), Fatal> {
177        Ok(())
178    }
179
180    /// Start a validation run with the store.
181    pub fn start(&self) -> Run<'_> {
182        Run::new(self)
183    }
184
185    /// Loads the status of the last run.
186    pub fn status(&self) -> Result<Option<StoredStatus>, Failed> {
187        let path = self.status_path();
188        let Some(mut file) = fatal::open_existing_file(&path)? else {
189            return Ok(None)
190        };
191        match StoredStatus::read(&mut file) {
192            Ok(status) => Ok(Some(status)),
193            Err(err) => {
194                error!("Failed to read store status file {}: {}",
195                    path.display(), err
196                );
197                Err(Failed)
198            }
199        }
200    }
201
202    /// Returns the path for the status file.
203    fn status_path(&self) -> PathBuf {
204        self.path.join(Self::STATUS_NAME)
205    }
206
207    /// Returns the path to use for the trust anchor at the given URI.
208    fn ta_path(&self, uri: &TalUri) -> PathBuf {
209        match *uri {
210            TalUri::Rsync(ref uri) => {
211                self.path.join(
212                    uri.unique_path(Self::RSYNC_TA_PATH, ".cer")
213                )
214            }
215            TalUri::Https(ref uri) => {
216                self.path.join(
217                    uri.unique_path(Self::HTTPS_TA_PATH, ".cer")
218                )
219            }
220        }
221    }
222
223    /// Returns the path where the RRDP repositories are stored.
224    fn rrdp_repository_base(&self) -> PathBuf {
225        self.path.join(Self::RRDP_BASE)
226    }
227
228    /// Returns the path for the RRDP repository with the given rpkiNotify URI.
229    fn rrdp_repository_path(&self, uri: &uri::Https) -> PathBuf {
230        self.path.join(uri.unique_path(Self::RRDP_BASE, ""))
231    }
232
233    /// Returns the path where the combined rsync repository is stored.
234    fn rsync_repository_path(&self) -> PathBuf {
235        self.path.join(Self::RSYNC_PATH)
236    }
237
238    /// Creates and returns a temporary file.
239    ///
240    /// The file is created in the store’s temporary path. If this succeeds,
241    /// the path and file object are returned.
242    fn tmp_file(&self) -> Result<NamedTempFile, Failed> {
243        let tmp_dir = self.path.join(Self::TMP_BASE);
244        fatal::create_dir_all(&tmp_dir)?;
245        NamedTempFile::new_in(&tmp_dir).map_err(|err| {
246            error!(
247                "Fatal: failed to create temporary file in {}: {}",
248                tmp_dir.display(), err
249            );
250            Failed
251        })
252    }
253}
254
255/// # Dumping of stored data
256impl Store {
257    /// Dumps the content of the store to `dir`.
258    pub fn dump(&self, dir: &Path) -> Result<(), Failed> {
259        // TA certificates.
260        self.dump_subdir(Self::RSYNC_TA_PATH, dir)?;
261        self.dump_subdir(Self::HTTPS_TA_PATH, dir)?;
262
263        // Dump store content.
264        let dir = dir.join("store");
265        fatal::remove_dir_all(&dir)?;
266        let mut repos = DumpRegistry::new(dir);
267        self.dump_point_tree(&self.rsync_repository_path(), &mut repos)?;
268        self.dump_point_tree(&self.rrdp_repository_base(), &mut repos)?;
269
270        self.dump_repository_json(repos)?;
271        Ok(())
272    }
273
274    /// Dumps all the complete sub-directory.
275    fn dump_subdir(
276        &self,
277        subdir: &str,
278        target_base: &Path,
279    ) -> Result<(), Failed> {
280        let source = self.path.join(subdir);
281        let target = target_base.join(subdir);
282        fatal::remove_dir_all(&target)?;
283        fatal::copy_existing_dir_all(&source, &target)?;
284        Ok(())
285    }
286
287    /// Dumps all the stored points found in the tree under `path`.
288    ///
289    /// The point’s repository and rsync URI is determined from the stored
290    /// points themselves. The target path is being determined from `repos`.
291    fn dump_point_tree(
292        &self,
293        path: &Path,
294        repos: &mut DumpRegistry,
295    ) -> Result<(), Failed> {
296        let dir = match fatal::read_existing_dir(path)? {
297            Some(dir) => dir,
298            None => return Ok(())
299        };
300        for entry in dir {
301            let entry = entry?;
302            if entry.is_dir() {
303                self.dump_point_tree(entry.path(), repos)?;
304            }
305            else if entry.is_file() {
306                self.dump_point(entry.path(), repos)?;
307            }
308        }
309        Ok(())
310    }
311
312    /// Dumps all data for a single stored publication point.
313    fn dump_point(
314        &self,
315        path: &Path,
316        repos: &mut DumpRegistry,
317    ) -> Result<(), Failed> {
318        let mut file = File::open(path).map_err(|err| {
319            error!(
320                "Fatal: failed to open file {}: {}",
321                path.display(), err
322            );
323            Failed
324        })?;
325        let header = match StoredPointHeader::read(&mut file) {
326            Ok(some) => some,
327            Err(err) => {
328                error!(
329                    "Skipping {}: failed to read file: {}",
330                    path.display(), err
331                );
332                return Ok(())
333            }
334        };
335        let manifest = StoredManifest::read(&mut file).map_err(|err| {
336            error!(
337                "Fatal: failed to read file {}: {}",
338                path.display(), err
339            );
340            Failed
341        })?;
342
343        let repo_dir = repos.get_repo_path(header.rpki_notify.as_ref());
344
345        // Manifest and CRL are in `manifest`.
346        self.dump_object(
347            &repo_dir, &header.manifest_uri, &manifest.manifest
348        )?;
349        self.dump_object(&repo_dir, &manifest.crl_uri, &manifest.crl)?;
350
351        // Loop all other objects.
352        while let Some(object) = StoredObject::read(&mut file).map_err(|err| {
353            error!(
354                "Fatal: failed to read file {}: {}",
355                path.display(), err
356            );
357            Failed
358        })? {
359            self.dump_object(&repo_dir, &object.uri, &object.content)?;
360        }
361
362        Ok(())
363    }
364
365    /// Writes the data of a single object.
366    fn dump_object(
367        &self,
368        dir: &Path,
369        uri: &uri::Rsync,
370        content: &[u8]
371    ) -> Result<(), Failed> {
372        let path = dir.join(
373            format!("{}/{}/{}",
374                uri.canonical_authority(),
375                uri.module_name(),
376                uri.path()
377            )
378        );
379        if let Some(dir) = path.parent() {
380            fatal::create_dir_all(dir)?;
381        }
382        let mut target = match File::create(&path) {
383            Ok(some) => some,
384            Err(err) => {
385                error!(
386                    "Fatal: cannot create target file {}: {}",
387                    path.display(), err
388                );
389                return Err(Failed)
390            }
391        };
392        if let Err(err) = target.write_all(content) {
393            error!(
394                "Fatal: failed to write to target file {}: {}",
395                path.display(), err
396            );
397            return Err(Failed)
398        }
399
400        Ok(())
401    }
402
403    /// Writes the repositories.json file.
404    fn dump_repository_json(
405        &self,
406        repos: DumpRegistry,
407    ) -> Result<(), Failed> {
408        fatal::create_dir_all(repos.base_dir())?;
409        let path = repos.base_dir().join("repositories.json");
410        fatal::write_file(
411            &path, 
412            JsonBuilder::build(|builder| {
413                builder.member_array("repositories", |builder| {
414                    for (key, value) in repos.rrdp_uris() {
415                        builder.array_object(|builder| {
416                            builder.member_str(
417                                "path", value
418                            );
419                            builder.member_str("type", "rrdp");
420                            builder.member_str(
421                                "rpkiNotify",
422                                key
423                            );
424                        })
425                    }
426                    builder.array_object(|builder| {
427                        builder.member_str("path", "rsync");
428                        builder.member_str("type", "rsync");
429                    });
430                })
431            }).as_bytes()
432        )
433    }
434}
435
436
437//------------ Run -----------------------------------------------------------
438
439/// A single validation run on using the store.
440///
441/// The type provides access to the stored versions of trust anchor
442/// certificates via the [`load_ta`][Self::load_ta] method and repositories
443/// through the [`repository`][Self::repository] method or publication points
444/// directly via [pub_point}[Self::pub_point].
445///
446/// Stored trust anchor certificates can be updated via
447/// [`update_ta`][Self::update_ta] on [`Run`] directly, while
448/// [`StoredPoint`] provides means to that for RPKI objects.
449#[derive(Debug)]
450pub struct Run<'a> {
451    /// A reference to the underlying store.
452    store: &'a Store,
453
454    /// The time this run was started.
455    started: Time,
456}
457
458impl<'a> Run<'a> {
459    /// Creates a new runner from a store.
460    fn new(
461        store: &'a Store,
462    ) -> Self {
463        Run { 
464            store,
465            started: Time::now(),
466        }
467    }
468
469    /// Finishes the validation run.
470    ///
471    /// Updates the `metrics` with the store run’s metrics.
472    pub fn done(self, metrics: &mut Metrics) {
473        let _ = metrics;
474        let path = self.store.status_path();
475        let Ok(mut file) = fatal::create_file(&path) else {
476            return
477        };
478        if let Err(err) = StoredStatus::new(Time::now()).write(&mut file) {
479            error!(
480                "Failed to write store status file {}: {}",
481                path.display(), err
482            );
483        }
484    }
485
486    /// Loads a stored trust anchor certificate.
487    pub fn load_ta(&self, uri: &TalUri) -> Result<Option<Bytes>, Failed> {
488        fatal::read_existing_file(&self.store.ta_path(uri)).map(|maybe| {
489            maybe.map(Into::into)
490        })
491    }
492
493    /// Updates or inserts a stored trust anchor certificate.
494    pub fn update_ta(
495        &self, uri: &TalUri, content: &[u8]
496    ) -> Result<(), Failed> {
497        let path = self.store.ta_path(uri);
498        if let Some(dir) = path.parent() {
499            fatal::create_dir_all(dir)?;
500        }
501        fatal::write_file(&path, content)
502    }
503
504    /// Accesses the repository for the provided RPKI CA.
505    ///
506    /// If the CA’s rpkiNotify URI is present, the RRDP repository identified
507    /// by that URI will be returned, otherwise the rsync repository will be
508    /// used.
509    ///
510    /// Note that we even use the RRDP repository if the collector had to fall
511    /// back to using rsync. Because rsync is ‘authoritative’ for the object
512    /// URIs, it is safe to use objects received via rsync in RRDP
513    /// repositories.
514    pub fn repository(&self, ca_cert: &CaCert) -> Repository {
515        Repository::new(self.store, ca_cert.rpki_notify().cloned())
516    }
517
518    /// Accesses the publication point for the provided RPKI CA.
519    ///
520    /// If the CA’s rpkiNotify URI is present, the RRDP repository identified
521    /// by that URI will be returned, otherwise the rsync repository will be
522    /// used.
523    ///
524    /// Note that we even use the RRDP repository if the collector had to fall
525    /// back to using rsync. Because rsync is ‘authoritative’ for the object
526    /// URIs, it is safe to use objects received via rsync in RRDP
527    /// repositories.
528    pub fn pub_point(
529        &self, ca_cert: &CaCert
530    ) -> Result<StoredPoint, Failed> {
531        self.repository(ca_cert).get_point(ca_cert.rpki_manifest())
532    }
533}
534
535impl Run<'_> {
536    /// Cleans up the store.
537    ///
538    /// All publication points that have an expired manifest will be removed.
539    /// RRDP repositories that have no more publication points are removed,
540    /// too.
541    ///
542    /// All RRDP repositories and rsync modules retained are registered with
543    /// `collector` for retaining in the collector as well.
544    pub fn cleanup(
545        &self,
546        collector: &mut collector::Cleanup,
547    ) -> Result<(), Failed> {
548        self.cleanup_ta()?;
549        self.cleanup_points(&self.store.rrdp_repository_base(), collector)?;
550        self.cleanup_points(&self.store.rsync_repository_path(), collector)?;
551        self.cleanup_tmp()?;
552        Ok(())
553    }
554
555    /// Cleans up a tree with publication points.
556    ///
557    /// Deletes all publication points with an expired manifest as well as
558    /// any obviously garbage files. The RRDP repository of any publication
559    /// point that is retained is registered to be retained by the collector.
560    fn cleanup_points(
561        &self,
562        base: &Path,
563        retain: &mut collector::Cleanup,
564    ) -> Result<(), Failed> {
565        Self::cleanup_dir_tree(base, |path| {
566            if let Some(stored) = StoredPoint::load_quietly(path.into()) {
567                if stored.retain(self.started) {
568                    if let Some(uri) = stored.header.rpki_notify.as_ref() {
569                        retain.add_rrdp_repository(uri)
570                    }
571                    else {
572                        retain.add_rsync_module(&stored.header.manifest_uri)
573                    }
574                    return Ok(true)
575                }
576            }
577            Ok(false)
578        })
579    }
580
581    /// Cleans up the trust anchors.
582    ///
583    /// Deletes all files that either don’t successfully parse as certificates
584    /// or that are expired certificates.
585    fn cleanup_ta(&self) -> Result<(), Failed> {
586        Self::cleanup_dir_tree(&self.store.path.join("ta"), |path| {
587            let content = fatal::read_file(path)?;
588            if let Ok(cert) = Cert::decode(Bytes::from(content)) {
589                if cert.validity().not_after() > Time::now() {
590                    return Ok(true)
591                }
592            }
593            Ok(false)
594        })
595    }
596
597    fn cleanup_tmp(&self) -> Result<(), Failed> {
598        Self::cleanup_dir_tree(&self.store.path.join("tmp"), |_path| {
599            Ok(false)
600        })
601    }
602
603    /// Cleans up a directory tree.
604    ///
605    /// If the closure returns `Ok(false)` for a file with the given path, the
606    /// file will be deleted. If all files in a directory are deleted, that
607    /// directory is deleted.
608    fn cleanup_dir_tree(
609        base: &Path,
610        mut keep: impl FnMut(&Path) -> Result<bool, Failed>
611    ) -> Result<(), Failed> {
612        /// Actual recursion.
613        ///
614        /// If `top` is `true`, we ignore if the directory `path` is missing.
615        ///
616        /// Returns whether the `base` needs to be kept. I.e., if `Ok(false)`
617        /// is returned, the calling recursing step will perform a
618        /// `delete_dir_all(base)`.
619        fn recurse(
620            base: &Path,
621            top: bool,
622            op: &mut impl FnMut(&Path) -> Result<bool, Failed>
623        ) -> Result<bool, Failed> {
624            let dir = if top {
625                match fatal::read_existing_dir(base)? {
626                    Some(dir) => dir,
627                    None => return Ok(false),
628                }
629            }
630            else {
631                fatal::read_dir(base)?
632            };
633
634            let mut keep = false;
635            for entry in dir {
636                let entry = entry?;
637                if entry.is_dir() {
638                    if !recurse(entry.path(), false, op)? {
639                        fatal::remove_dir_all(entry.path())?;
640                    }
641                    else {
642                        keep = true;
643                    }
644                }
645                else if entry.is_file() {
646                    if !op(entry.path())? {
647                        fatal::remove_file(entry.path())?;
648                    }
649                    else {
650                        keep = true;
651                    }
652                }
653                // Let’s not try deleting non-file-and-non-dir things here but
654                // leave it to remove_dir_all to give it a shot.
655            }
656            Ok(keep)
657        }
658        recurse(base, true, &mut keep).map(|_| ())
659    }
660}
661
662
663//------------ Repository ----------------------------------------------------
664
665/// Access to a single repository during a validation run.
666///
667/// A repository is a collection of publication points. Each of these points
668/// has a manifest and a set of objects. The manifest is identified by its
669/// signedObject URI while the objects are identified by their name on the
670/// manifest’s object list.
671///
672/// You can get access to a publication point via
673/// [`get_point`][Self::get_point].
674///
675pub struct Repository {
676    /// The path where the repository lives.
677    path: PathBuf,
678
679    /// The RRPD URI for the repository or `None` if this is the rsync repo.
680    rpki_notify: Option<uri::Https>,
681}
682
683impl Repository {
684    /// Creates a repository object for the given repository.
685    ///
686    /// The repository is identified by the RRDP URI. Each RRDP “server” gets
687    /// its own repository and all rsync “servers” share one.
688    fn new(store: &Store, rpki_notify: Option<uri::Https>) -> Self {
689        Self {
690            path: if let Some(rpki_notify) = rpki_notify.as_ref() {
691                store.rrdp_repository_path(rpki_notify)
692            }
693            else {
694                store.rsync_repository_path()
695            },
696            rpki_notify
697        }
698    }
699
700    /// Returns the RRDP URI if present.
701    pub fn rpki_notify(&self) -> Option<&uri::Https> {
702        self.rpki_notify.as_ref()
703    }
704
705    /// Returns whether this is an RRDP repository.
706    pub fn is_rrdp(&self) -> bool {
707        self.rpki_notify.is_some()
708    }
709
710    /// Opens the given stored publication point.
711    ///
712    /// The publication point is identified through the rsync URI of its
713    /// manifest.
714    ///
715    /// A stored point instance will be returned whether there actually is
716    /// information stored for the point or not.
717    pub fn get_point(
718        &self, manifest_uri: &uri::Rsync
719    ) -> Result<StoredPoint, Failed> {
720        StoredPoint::open(
721            self.point_path(manifest_uri),
722            manifest_uri, self.rpki_notify.as_ref(),
723        )
724    }
725
726    /// Returns the path for a publication point with the given manifest URI.
727    fn point_path(&self, manifest_uri: &uri::Rsync) -> PathBuf {
728        self.path.join(
729            format!(
730                "rsync/{}/{}/{}",
731                manifest_uri.canonical_authority(),
732                manifest_uri.module_name(),
733                manifest_uri.path(),
734            )
735        )
736    }
737}
738
739
740//------------ StoredPoint ---------------------------------------------------
741
742/// The stored information of a publication point.
743///
744/// This types allows access to the stored manifest via
745/// [`manifest`][Self::manifest] and acts as an iterator over the
746/// publication point’s objects. The method [`update`][Self::update] allows
747/// atomically updating the information.
748pub struct StoredPoint {
749    /// The path to the file-system location of the repository.
750    path: PathBuf,
751
752    /// Is the this a newly discovered stored point?
753    is_new: bool,
754
755    /// The header of the stored point.
756    ///
757    /// This will always be present, even if the point is new.
758    header: StoredPointHeader,
759
760    /// The stored manifest for the point if there is one.
761    manifest: Option<StoredManifest>,
762
763    /// The file with all the information we need.
764    ///
765    /// The file will only be present if a point has been successfully
766    /// stored before. I.e, if the point is present but never was
767    /// successfully updated, this will still be `None`.
768    ///
769    /// If present, the file will be positioned at the begining of the next
770    /// stored object to be loaded.
771    file: Option<BufReader<File>>,
772}
773
774impl StoredPoint {
775    /// Opens the stored point.
776    ///
777    /// If there is a file at the given path, it is opened, the manifest is
778    /// read and positioned at the first stored object. Otherwise there will
779    /// be no manifest and no objects.
780    fn open(
781        path: PathBuf,
782        manifest_uri: &uri::Rsync,
783        rpki_notify: Option<&uri::Https>,
784    ) -> Result<Self, Failed> {
785        let mut file = match File::open(&path) {
786            Ok(file) => BufReader::new(file),
787            Err(ref err) if err.kind() == io::ErrorKind::NotFound => {
788                return Self::create(path, manifest_uri, rpki_notify);
789            }
790            Err(err) => {
791                error!(
792                    "Failed to open stored publication point at {}: {}",
793                    path.display(), err
794                );
795                return Err(Failed)
796            }
797        };
798
799        let mut header = match StoredPointHeader::read(&mut file) {
800            Ok(header) => header,
801            Err(err) if !err.is_fatal() => {
802                return Self::create(path, manifest_uri, rpki_notify);
803            }
804            Err(err) => {
805                error!(
806                    "Failed to read stored publication point at {}: {}",
807                    path.display(), err
808                );
809                return Err(Failed)
810            }
811        };
812
813        // From here on all errors are considered fatal.
814
815        if matches!(header.update_status, UpdateStatus::LastAttempt(_)) {
816            // We never succeeded. Update the status and return.
817            header.update_status = UpdateStatus::LastAttempt(Time::now());
818
819            drop(file);
820            let mut file = File::create(&path).map_err(|err| {
821                error!(
822                    "Failed to update stored publication point at {}: \
823                     re-open: {}",
824                    path.display(), err
825                );
826                Failed
827
828            })?;
829
830            if let Err(err) = file.seek(SeekFrom::Start(0)) {
831                error!(
832                    "Failed to update stored publication point at {}: \
833                     seek failed: {}",
834                    path.display(), err
835                );
836                return Err(Failed)
837            }
838            if let Err(err) = header.write(&mut file) {
839                error!(
840                    "Failed to update stored publication point at {}: \
841                     write failed: {}",
842                    path.display(), err
843                );
844                return Err(Failed)
845            }
846
847            return Ok(Self {
848                path,
849                is_new: false,
850                header,
851                manifest: None,
852                file: None,
853            })
854        }
855
856        let manifest = match StoredManifest::read(&mut file) {
857            Ok(manifest) => manifest,
858            Err(err) => {
859                error!(
860                    "Failed to read stored publication point at {}: {}",
861                    path.display(), err
862                );
863                return Err(Failed)
864            }
865        };
866
867        Ok(Self {
868            path,
869            is_new: false,
870            header,
871            manifest: Some(manifest),
872            file: Some(file)
873        })
874    }
875
876    /// Creates a new, empty stored point.
877    ///
878    /// This is called either when the stored point doesn’t exist or there is
879    /// one but it is of the wrong version.
880    ///
881    /// Creates the file and sets it to an initial status.
882    fn create(
883        path: PathBuf,
884        manifest_uri: &uri::Rsync,
885        rpki_notify: Option<&uri::Https>,
886    ) -> Result<Self, Failed> {
887        if let Some(path) = path.parent() {
888            fatal::create_dir_all(path)?;
889        }
890        let mut file = match File::create(&path) {
891            Ok(file) => file,
892            Err(err) => {
893                error!(
894                    "Failed to create stored publication point at {}: {}",
895                    path.display(), err
896                );
897                return Err(Failed)
898            }
899        };
900        let header = StoredPointHeader::new(
901            manifest_uri.clone(), rpki_notify.cloned(),
902        );
903        if let Err(err) = header.write(&mut file) {
904            error!(
905                "Failed to write stored publication point at {}: {}",
906                path.display(), err
907            );
908            return Err(Failed)
909        }
910
911        Ok(StoredPoint {
912            path,
913            is_new: true,
914            header,
915            manifest: None,
916            file: None,
917        })
918        
919    }
920
921    /// Loads an existing stored point from a path.
922    ///
923    /// Does not create a value if the point does not exist. Does not output
924    /// any error messages and just returns `None` if loading fails.
925    pub fn load_quietly(path: PathBuf) -> Option<Self> {
926        let mut file = BufReader::new(File::open(&path).ok()?);
927        let header = StoredPointHeader::read(&mut file).ok()?;
928        let manifest = match header.update_status {
929            UpdateStatus::Success(_) => {
930                Some(StoredManifest::read(&mut file).ok()?)
931            }
932            UpdateStatus::LastAttempt(_) => None,
933        };
934        Some(Self {
935            path,
936            is_new: false,
937            header, manifest,
938            file: Some(file)
939        })
940    }
941
942    /// Returns whether the point was newly discovered during this run.
943    pub fn is_new(&self) -> bool {
944        self.is_new
945    }
946
947    /// Replaces the data of the stored point.
948    ///
949    /// Updates the manifest with the provided manifest and the objects
950    /// provided by the closure. The closure is called repeatedly until it
951    /// either returns `Ok(None)` or `Err(_)`. In the latter case, the update
952    /// is cancelled, the old point remains unchanged and the error is
953    /// returned. Otherwise, `self` represents the new point. It is
954    /// positioned at the first object, i.e., if it is iterated over, the
955    /// first object will be returned next.
956    ///
957    /// The closure here acts as a poor man’s generator which makes it easier
958    /// to write the necessary code.
959    pub fn update(
960        &mut self,
961        store: &Store,
962        manifest: StoredManifest,
963        objects: impl FnMut() -> Result<Option<StoredObject>, UpdateError>
964    ) -> Result<(), UpdateError> {
965        let tmp_file = store.tmp_file()?;
966        self._update(tmp_file, manifest, objects)
967    }
968
969    fn _update(
970        &mut self,
971        tmp_file: NamedTempFile,
972        manifest: StoredManifest,
973        mut objects: impl FnMut() -> Result<Option<StoredObject>, UpdateError>
974    ) -> Result<(), UpdateError> {
975        let mut tmp_file = BufWriter::new(tmp_file);
976
977        self.header.update_status = UpdateStatus::Success(Time::now());
978
979        if let Err(err) = self.header.write(&mut tmp_file) {
980            error!(
981                "Fatal: failed to write to file {}: {}",
982                tmp_file.get_ref().path().display(), err
983            );
984            return Err(UpdateError::fatal())
985        }
986        if let Err(err) = manifest.write(&mut tmp_file) {
987            error!(
988                "Fatal: failed to write to file {}: {}",
989                tmp_file.get_ref().path().display(), err
990            );
991            return Err(UpdateError::fatal())
992        }
993        let tmp_object_start = match tmp_file.stream_position() {
994            Ok(some) => some,
995            Err(err) => {
996                error!(
997                    "Fatal: failed to get position in file {}: {}",
998                    tmp_file.get_ref().path().display(), err
999                );
1000                return Err(UpdateError::fatal())
1001            }
1002        };
1003        while let Some(object) = objects()? {
1004            if let Err(err) = object.write(&mut tmp_file) {
1005                error!(
1006                    "Fatal: failed to write to file {}: {}",
1007                    tmp_file.get_ref().path().display(), err
1008                );
1009                return Err(UpdateError::fatal())
1010            }
1011        }
1012
1013        let tmp_file = tmp_file.into_inner().map_err(|err| {
1014            let (err, tmp_file) = err.into_parts();
1015            error!(
1016                "Fatal: failed to write to file {}: {}",
1017                tmp_file.get_ref().path().display(), err
1018            );
1019            UpdateError::fatal()
1020        })?;
1021
1022        // I think we need to drop `self.file` first so it gets closed and the
1023        // path unlocked on Windows?
1024        drop(self.file.take());
1025        match tmp_file.persist(&self.path) {
1026            Ok(file) => self.file = Some(BufReader::new(file)),
1027            Err(err) => {
1028                error!(
1029                    "Failed to persist temporary file {} to {}: {}",
1030                    err.file.path().display(), self.path.display(),
1031                    err.error,
1032                );
1033                return Err(UpdateError::fatal())
1034            }
1035        }
1036        self.manifest = Some(manifest);
1037
1038        // Position the file at the first object. (The if will always be
1039        // true, so this is fine.)
1040        if let Some(file) = self.file.as_mut() {
1041            if let Err(err) = file.seek(SeekFrom::Start(tmp_object_start)) {
1042                error!(
1043                    "Fatal: failed to position file {}: {}",
1044                    self.path.display(), err
1045                );
1046                return Err(UpdateError::fatal())
1047            }
1048        }
1049
1050        Ok(())
1051    }
1052
1053    /// Deletes the stored point’s information.
1054    ///
1055    /// Replaces the file with one pretending we’ve never successfully
1056    /// updated the point and changes `self` accordingly.
1057    pub fn reject(&mut self) -> Result<(), Failed> {
1058        self.is_new = true;
1059        self.header.update_status = UpdateStatus::LastAttempt(Time::now());
1060        self.manifest = None;
1061        self.file = None;
1062
1063        let mut file = match File::create(&self.path) {
1064            Ok(file) => file,
1065            Err(err) => {
1066                error!(
1067                    "Failed to create stored publication point at {}: {}",
1068                    self.path.display(), err
1069                );
1070                return Err(Failed)
1071            }
1072        };
1073        if let Err(err) = self.header.write(&mut file) {
1074            error!(
1075                "Failed to write stored publication point at {}: {}",
1076                self.path.display(), err
1077            );
1078            return Err(Failed)
1079        }
1080        Ok(())
1081    }
1082
1083    /// Returns whether the point should be retained.
1084    ///
1085    /// If the point had a successful update, it will retained until the
1086    /// `notAfter` time of the manifest’s certificate. Otherwise it will be
1087    /// retained if the last update attempted was after `update_start` (i.e.,
1088    /// there was an attempt to update the point during this validation run).
1089    fn retain(&self, update_start: Time) -> bool {
1090        if let Some(manifest) = self.manifest.as_ref() {
1091            manifest.not_after > Time::now()
1092        }
1093        else if let UpdateStatus::LastAttempt(when)
1094            = self.header.update_status
1095        {
1096            when >= update_start
1097        }
1098        else {
1099            // Update status says success but we don’t have a manifest? That
1100            // can’t happen, so say “no”.
1101            false
1102        }
1103    }
1104}
1105
1106impl StoredPoint {
1107    /// Returns a reference to the path of the file.
1108    pub fn path(&self) -> &Path {
1109        &self.path
1110    }
1111
1112    /// Returns a reference to the stored manifest if available.
1113    ///
1114    /// The manifest will not be available if there is no previously stored
1115    /// version of the publication point and an update has not succeeded yet,
1116    /// or if the manifest has been taken out via
1117    /// [`take_manifest`][Self::take_manifest].
1118    pub fn manifest(&self) -> Option<&StoredManifest> {
1119        self.manifest.as_ref()
1120    }
1121}
1122
1123impl Iterator for StoredPoint {
1124    type Item = Result<StoredObject, ParseError>;
1125
1126    fn next(&mut self) -> Option<Self::Item> {
1127        StoredObject::read(self.file.as_mut()?).transpose()
1128    }
1129}
1130
1131
1132//------------ StoredPointHeader ---------------------------------------------
1133
1134/// The header of the a stored publication point.
1135///
1136#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1137pub struct StoredPointHeader {
1138    /// The manifest’s rsync URI.
1139    manifest_uri: uri::Rsync,
1140
1141    /// The rpkiNotify URI of the issuing CA certificate.
1142    rpki_notify: Option<uri::Https>,
1143
1144    /// The update status of the point.
1145    ///
1146    /// Tells use whether we have ever seen a successful update or when we
1147    /// last tried.
1148    update_status: UpdateStatus,
1149}
1150
1151impl StoredPointHeader {
1152    /// The version of the type.
1153    ///
1154    /// This was part of `StoredManifest` before 0.15 with value 1 and
1155    /// before 0.14 with value 0.
1156    const VERSION: u8 = 2;
1157
1158    /// Creates a new stored status.
1159    ///
1160    /// Assumes that updates have never succeeded.
1161    pub fn new(
1162        manifest_uri: uri::Rsync,
1163        rpki_notify: Option<uri::Https>,
1164    ) -> Self {
1165        Self {
1166            manifest_uri, rpki_notify,
1167            update_status: UpdateStatus::LastAttempt(Time::now()),
1168        }
1169    }
1170
1171    /// Reads a stored point status from an IO reader.
1172    pub fn read(reader: &mut impl io::Read) -> Result<Self, ParseError> {
1173        // Version number.
1174        let version = u8::parse(reader)?;
1175        if version != Self::VERSION {
1176            return Err(ParseError::format(
1177                    format!("unexpected version {version}")
1178            ))
1179        }
1180        Ok(Self {
1181            manifest_uri: Parse::parse(reader)?,
1182            rpki_notify: Parse::parse(reader)?,
1183            update_status: UpdateStatus::read(reader)?,
1184        })
1185    }
1186
1187    /// Writes the stored point status to a writer.
1188    pub fn write(
1189        &self, writer: &mut impl io::Write
1190    ) -> Result<(), io::Error> {
1191        Self::VERSION.compose(writer)?;
1192
1193        self.manifest_uri.compose(writer)?;
1194        self.rpki_notify.compose(writer)?;
1195        self.update_status.write(writer)?;
1196
1197        Ok(())
1198    }
1199}
1200
1201
1202//------------ UpdateStatus --------------------------------------------------
1203
1204/// The update status of a stored point.
1205#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1206enum UpdateStatus {
1207    /// There was a successful update at the given time.
1208    ///
1209    /// Strictly speaking, we don’t need this time. But it may come in
1210    /// handy.
1211    Success(Time),
1212
1213    /// There never was a successful update, last attempt at the given time.
1214    ///
1215    /// Note that once we had success, we stick with `Self::Success`. This
1216    /// variant is for before first success only.
1217    LastAttempt(Time),
1218}
1219
1220impl UpdateStatus {
1221    /// Reads a stored point status from an IO reader.
1222    pub fn read(reader: &mut impl io::Read) -> Result<Self, ParseError> {
1223        match u8::parse(reader)? {
1224            0 => Ok(UpdateStatus::Success(Parse::parse(reader)?)),
1225            1 => Ok(UpdateStatus::LastAttempt(Parse::parse(reader)?)),
1226            _ => {
1227                Err(ParseError::format(
1228                    "invalid update status".to_string()
1229                ))
1230            }
1231        }
1232    }
1233
1234    /// Writes the stored point status to a writer.
1235    pub fn write(self, writer: &mut impl io::Write) -> Result<(), io::Error> {
1236        match self {
1237            Self::Success(time) => {
1238                0u8.compose(writer)?;
1239                time.compose(writer)?;
1240            }
1241            Self::LastAttempt(time) => {
1242                1u8.compose(writer)?;
1243                time.compose(writer)?;
1244            }
1245        }
1246        Ok(())
1247    }
1248}
1249
1250
1251//------------ StoredManifest ------------------------------------------------
1252
1253/// The content of a manifest placed in the store.
1254///
1255/// This type collects all data that is stored as the manifest for a
1256/// publication point.
1257///
1258/// This contains the raw bytes of both the manifest itself plus data that
1259/// will be needed to use the manifest during processing. In particular:
1260///
1261/// * The expiry time of the manifest’s EE certificate via the
1262///   [`not_after`][Self::not_after] method. This is used during cleanup to
1263///   determine whether to keep a publication point. It is stored to avoid
1264///   having to parse the whole manifest.
1265/// * The manifest number and thisUpdate time. These are used to check whether
1266///   a new manifest tries to go backwards.
1267/// * The caRepository URI of the CA certificate that has issued the manifest
1268///   via the [`ca_repository`][Self::ca_repository] method.  This is
1269///   necessary to convert the file names mentioned on the manifest into their
1270///   full rsync URIs. Confusingly, this information is not available on the
1271///   manifest itself and therefore needs to be stored.
1272/// * The raw bytes of the manifest via the [`manifest`][Self::manifest]
1273///   method.
1274/// * The raw bytes of the CRL referenced by the manifest via the
1275///   [`crl`][Self::crl] method. There must always be exactly one CRL used by
1276///   a publication point. As it needs to be available for validation, we
1277///   might as well store it together with the manifest.
1278#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1279pub struct StoredManifest {
1280    /// The expire time of the EE certificate of the manifest.
1281    pub not_after: Time,
1282
1283    /// The manifest number of the manifest.
1284    pub manifest_number: Serial,
1285
1286    /// The thisUpdate time of the manifest.
1287    pub this_update: Time,
1288
1289    /// The CA repository rsync URI of the issuing CA certificate.
1290    pub ca_repository: uri::Rsync,
1291
1292    /// The raw content of the manifest.
1293    pub manifest: Bytes,
1294
1295    /// The CRL’s rsync URI.
1296    pub crl_uri: uri::Rsync,
1297
1298    /// The raw content of the CRL.
1299    pub crl: Bytes,
1300}
1301
1302impl StoredManifest {
1303    /// Creates a new stored manifest.
1304    ///
1305    /// The new value is created from the components of the stored manifest.
1306    /// See the methods with the same name for their meaning.
1307    pub fn new(
1308        ee_cert: &ResourceCert,
1309        manifest: &ManifestContent,
1310        ca_cert: &CaCert,
1311        manifest_bytes: Bytes,
1312        crl_uri: uri::Rsync,
1313        crl: Bytes,
1314    ) -> Self {
1315        StoredManifest {
1316            not_after: ee_cert.validity().not_after(),
1317            manifest_number: manifest.manifest_number(),
1318            this_update: manifest.this_update(),
1319            ca_repository: ca_cert.ca_repository().clone(),
1320            manifest: manifest_bytes,
1321            crl_uri,
1322            crl
1323        }
1324    }
1325
1326    /// Reads a stored manifest from an IO reader.
1327    pub fn read(reader: &mut impl io::Read) -> Result<Self, ParseError> {
1328        Ok(StoredManifest {
1329            not_after: Parse::parse(reader)?,
1330            manifest_number: Parse::parse(reader)?,
1331            this_update: Parse::parse(reader)?,
1332            ca_repository: Parse::parse(reader)?,
1333            manifest: Parse::parse(reader)?,
1334            crl_uri: Parse::parse(reader)?,
1335            crl: Parse::parse(reader)?,
1336        })
1337    }
1338
1339    /// Appends the stored manifest to a writer.
1340    pub fn write(
1341        &self, writer: &mut impl io::Write
1342    ) -> Result<(), io::Error> {
1343        self.not_after.compose(writer)?;
1344        self.manifest_number.compose(writer)?;
1345        self.this_update.compose(writer)?;
1346        self.ca_repository.compose(writer)?;
1347        self.manifest.compose(writer)?;
1348        self.crl_uri.compose(writer)?;
1349        self.crl.compose(writer)?;
1350
1351        Ok(())
1352    }
1353}
1354
1355
1356//------------ StoredObject --------------------------------------------------
1357
1358/// The content of an object placed in the store.
1359///
1360/// This type collects all the data that is stored for regular objects of a
1361/// publication point: the raw bytes of the object as well as its hash as
1362/// stated on the publication point’s manifest. This hash is currently not
1363/// used since we only store objects when we know the publication point was
1364/// valid. It is retained here solely for compatibility with the existing
1365/// stored object format.
1366#[derive(Clone, Debug, PartialEq, Eq)]
1367pub struct StoredObject {
1368    /// The URI of the object.
1369    pub uri: uri::Rsync,
1370
1371    /// The manifest hash of the object if available.
1372    pub hash: Option<ManifestHash>,
1373
1374    /// The content of the object.
1375    pub content: Bytes,
1376}
1377
1378impl StoredObject {
1379    /// Creates a new stored object from its bytes and manifest hash.
1380    pub fn new(
1381        uri: uri::Rsync,
1382        content: Bytes,
1383        hash: Option<ManifestHash>,
1384    ) -> Self {
1385        StoredObject { uri, hash, content }
1386    }
1387
1388    /// Reads a stored object from an IO reader.
1389    pub fn read(
1390        reader: &mut impl io::Read
1391    ) -> Result<Option<Self>, ParseError> {
1392        let uri = match uri::Rsync::parse(reader) {
1393            Ok(uri) => uri,
1394            Err(err) if err.is_eof() => return Ok(None),
1395            Err(err) => return Err(err),
1396        };
1397        let hash = match u8::parse(reader)? {
1398            0 => None,
1399            1 => {
1400                let algorithm = DigestAlgorithm::sha256();
1401                let mut value = vec![0u8; algorithm.digest_len()];
1402                reader.read_exact(&mut value)?;
1403                Some(ManifestHash::new(value.into(), algorithm))
1404            }
1405            hash_type => {
1406                return Err(ParseError::format(
1407                    format!("unsupported hash type {hash_type}")
1408                ));
1409            }
1410        };
1411        let content = Bytes::parse(reader)?;
1412
1413        Ok(Some(StoredObject { uri, hash, content }))
1414    }
1415
1416    /// Appends the stored object to a writer.
1417    pub fn write(
1418        &self, writer: &mut impl io::Write
1419    ) -> Result<(), io::Error> {
1420        self.uri.compose(writer)?;
1421
1422        // Hash.
1423        //
1424        // One octet hash type: 0 .. None, 1 .. SHA-256
1425        // As many octets as the hash type requires.
1426        //
1427        // Unknown digest algorithms (there is non yet, but there may be) are
1428        // encoded as if the field was None.
1429        match self.hash.as_ref() {
1430            Some(hash) if hash.algorithm().is_sha256() => {
1431                1u8.compose(writer)?;
1432                writer.write_all(hash.as_slice())?;
1433            }
1434            _ => {
1435                0u8.compose(writer)?;
1436            }
1437        }
1438
1439        self.content.compose(writer)?;
1440
1441        Ok(())
1442    }
1443}
1444
1445
1446//------------ StoredStatus --------------------------------------------------
1447
1448/// Information about the status of the store.
1449#[derive(Clone, Debug)]
1450pub struct StoredStatus {
1451    /// The time the last update was finished.
1452    pub last_update: Time,
1453}
1454
1455impl StoredStatus {
1456    /// The version of the type.
1457    const VERSION: u8 = 0;
1458
1459    /// Creates a new value.
1460    pub fn new(last_update: Time) -> Self {
1461        Self { last_update }
1462    }
1463
1464    /// Reads the stored status from an IO reader.
1465    pub fn read(reader: &mut impl io::Read) -> Result<Self, ParseError> {
1466        // Version number.
1467        let version = u8::parse(reader)?;
1468        if version != Self::VERSION {
1469            return Err(ParseError::format(
1470                    format!("unexpected version {version}")
1471            ))
1472        }
1473        Ok(Self {
1474            last_update: Parse::parse(reader)?
1475        })
1476    }
1477
1478    /// Appends the stored status to a writer.
1479    pub fn write(
1480        &self, writer: &mut impl io::Write
1481    ) -> Result<(), io::Error> {
1482        Self::VERSION.compose(writer)?;
1483        self.last_update.compose(writer)?;
1484        Ok(())
1485    }
1486}
1487
1488
1489//============ Error Types ===================================================
1490
1491//------------ UpdateError ---------------------------------------------------
1492
1493/// An error happend while updating a publication point.
1494#[derive(Clone, Copy, Debug)]
1495pub enum UpdateError {
1496    /// The update needs to be aborted and rolled back.
1497    Abort,
1498
1499    /// Something really bad happened that requires aborting the run.
1500    Failed(RunFailed),
1501}
1502
1503impl UpdateError {
1504    pub fn fatal() -> Self {
1505        UpdateError::Failed(RunFailed::fatal())
1506    }
1507}
1508
1509impl From<Failed> for UpdateError {
1510    fn from(_: Failed) -> Self {
1511        UpdateError::Failed(RunFailed::fatal())
1512    }
1513}
1514
1515impl From<RunFailed> for UpdateError {
1516    fn from(err: RunFailed) -> Self {
1517        UpdateError::Failed(err)
1518    }
1519}
1520
1521
1522//============ Tests =========================================================
1523
1524#[cfg(test)]
1525mod test {
1526    use std::str::FromStr;
1527    use super::*;
1528
1529    #[test]
1530    fn read_write() {
1531        let dir = tempfile::tempdir().unwrap();
1532
1533        let path = dir.path().join("stored.bin");
1534        let manifest_uri = uri::Rsync::from_str(
1535            "rsync://example.com/test/test.mft"
1536        ).unwrap();
1537        let rpki_notify = Some(uri::Https::from_str(
1538            "https://example.com/notification.xml"
1539        ).unwrap());
1540
1541        // StoredManifest::new is too smart, so we need to make this manually.
1542        let manifest = StoredManifest {
1543            not_after: Time::utc(2025, 10, 1, 16, 10, 22),
1544            manifest_number: Serial::default(),
1545            this_update: Time::utc(2010, 6, 3, 8, 11, 0),
1546            ca_repository: uri::Rsync::from_str(
1547                "rsync://example.com/test/"
1548            ).unwrap(),
1549            manifest: Bytes::from_static(b"deadbeef"),
1550            crl_uri: uri::Rsync::from_str(
1551                "rsync://example.com/test/test.crl"
1552            ).unwrap(),
1553            crl: Bytes::from_static(b"crlbytesgohere"),
1554        };
1555
1556        let objects = [
1557            StoredObject::new(
1558                uri::Rsync::from_str(
1559                    "rsync://example.com/test/obj1.bin"
1560                ).unwrap(),
1561                Bytes::from_static(b"object1content"),
1562                Some(ManifestHash::new(
1563                    Bytes::copy_from_slice(
1564                        DigestAlgorithm::sha256().digest(
1565                            b"object1content"
1566                        ).as_ref()
1567                    ),
1568                    DigestAlgorithm::sha256()
1569                ))
1570            ),
1571            StoredObject::new(
1572                uri::Rsync::from_str(
1573                    "rsync://example.com/test/obj2.bin"
1574                ).unwrap(),
1575                Bytes::from_static(b"object2stuff"),
1576                None,
1577            ),
1578        ];
1579
1580        let mut point = StoredPoint::open(
1581            path.clone(), &manifest_uri, rpki_notify.as_ref()
1582        ).unwrap();
1583        let mut objects_iter = objects.iter();
1584        point._update(
1585            NamedTempFile::new_in(&dir).unwrap(),
1586            manifest.clone(),
1587            || Ok(objects_iter.next().cloned())
1588        ).unwrap();
1589        drop(point);
1590
1591        let mut point = StoredPoint::load_quietly(path.clone()).unwrap();
1592        assert_eq!(point.manifest, Some(manifest));
1593        assert_eq!(point.next().unwrap().unwrap(), objects[0]);
1594        assert_eq!(point.next().unwrap().unwrap(), objects[1]);
1595        assert!(point.next().is_none());
1596    }
1597}
1598