Skip to main content

fuchsia_repo/
repository.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::range::Range;
6use crate::resource::Resource;
7use anyhow::Result;
8use camino::{Utf8Path, Utf8PathBuf};
9use delivery_blob::DeliveryBlobType;
10use fuchsia_merkle::Hash;
11use futures::future::BoxFuture;
12use futures::stream::BoxStream;
13use serde::{Deserialize, Serialize};
14use std::collections::BTreeSet;
15use std::fmt::Debug;
16use std::io;
17use std::sync::Arc;
18use std::time::SystemTime;
19use tuf::pouf::Pouf1;
20use tuf::repository::{
21    RepositoryProvider as TufRepositoryProvider, RepositoryStorage as TufRepositoryStorage,
22};
23use url::ParseError;
24
25pub(crate) mod file_system;
26mod pm;
27
28#[cfg(test)]
29pub(crate) mod repo_tests;
30
31pub use file_system::{CopyMode, FileSystemRepository, FileSystemRepositoryBuilder};
32pub use pm::PmRepository;
33
34#[cfg(not(target_os = "fuchsia"))]
35mod gcs_repository;
36#[cfg(not(target_os = "fuchsia"))]
37pub use gcs_repository::GcsRepository;
38
39#[cfg(not(target_os = "fuchsia"))]
40mod http_repository;
41#[cfg(not(target_os = "fuchsia"))]
42pub use http_repository::HttpRepository;
43
44#[derive(thiserror::Error, Debug)]
45pub enum Error {
46    #[error("not found")]
47    NotFound,
48    #[error("invalid path '{0}'")]
49    InvalidPath(Utf8PathBuf),
50    #[error("I/O error")]
51    Io(#[source] io::Error),
52    #[error("URL Parsing Error")]
53    URLParseError(#[source] ParseError),
54    #[error(transparent)]
55    Tuf(#[from] tuf::Error),
56    #[error(transparent)]
57    Far(#[from] fuchsia_archive::Error),
58    #[error(transparent)]
59    Meta(#[from] fuchsia_pkg::MetaContentsError),
60    #[error(transparent)]
61    Http(#[from] http::uri::InvalidUri),
62    #[error(transparent)]
63    Hyper(#[from] hyper::Error),
64    #[error(transparent)]
65    ParseInt(#[from] std::num::ParseIntError),
66    #[error(transparent)]
67    ToStr(#[from] hyper::header::ToStrError),
68    #[error(transparent)]
69    MirrorConfig(#[from] fidl_fuchsia_pkg_ext::MirrorConfigError),
70    #[error(transparent)]
71    Other(#[from] anyhow::Error),
72    #[error("range not satisfiable")]
73    RangeNotSatisfiable,
74    #[error(transparent)]
75    Hash(#[from] fuchsia_hash::ParseHashError),
76}
77
78impl From<ParseError> for Error {
79    fn from(err: ParseError) -> Self {
80        Error::URLParseError(err)
81    }
82}
83
84pub trait RepoProvider: TufRepositoryProvider<Pouf1> + Debug + Send + Sync {
85    #[cfg(not(target_os = "fuchsia"))]
86    /// Get a [RepositorySpec] for this [Repository]
87    fn spec(&self) -> RepositorySpec;
88
89    /// Get the repository aliases.
90    fn aliases(&self) -> &BTreeSet<String>;
91
92    /// Fetch a metadata [Resource] from this repository.
93    fn fetch_metadata_range<'a>(
94        &'a self,
95        path: &str,
96        range: Range,
97    ) -> BoxFuture<'a, Result<Resource, Error>>;
98
99    /// Fetch a blob [Resource] from this repository.
100    fn fetch_blob_range<'a>(
101        &'a self,
102        path: &str,
103        range: Range,
104    ) -> BoxFuture<'a, Result<Resource, Error>>;
105
106    /// Whether or not the backend supports watching for file changes.
107    fn supports_watch(&self) -> bool {
108        false
109    }
110
111    /// Returns a stream which sends a unit value every time the given path is modified.
112    fn watch(&self) -> anyhow::Result<BoxStream<'static, ()>> {
113        Err(anyhow::anyhow!("Watching not supported for this repo type"))
114    }
115
116    /// Get the modification time of a blob in this repository if available.
117    fn blob_modification_time<'a>(
118        &'a self,
119        path: &str,
120    ) -> BoxFuture<'a, anyhow::Result<Option<SystemTime>>>;
121
122    /// Get the type of delivery blobs in this repository.
123    fn blob_type(&self) -> DeliveryBlobType {
124        DeliveryBlobType::Type1
125    }
126}
127
128pub trait RepoStorage: TufRepositoryStorage<Pouf1> + Send + Sync {
129    /// Store a blob in this repository.
130    fn store_blob<'a>(
131        &'a self,
132        hash: &Hash,
133        len: u64,
134        path: &Utf8Path,
135    ) -> BoxFuture<'a, Result<()>>;
136
137    /// Store a delivery blob in this repository.
138    fn store_delivery_blob<'a>(
139        &'a self,
140        hash: &Hash,
141        path: &Utf8Path,
142        delivery_blob_type: DeliveryBlobType,
143    ) -> BoxFuture<'a, Result<()>>;
144}
145
146pub trait RepoStorageProvider: RepoStorage + RepoProvider {}
147impl<T: RepoStorage + RepoProvider> RepoStorageProvider for T {}
148
149macro_rules! impl_provider {
150    (
151        <$($desc:tt)+
152    ) => {
153        impl <$($desc)+ {
154            #[cfg(not(target_os = "fuchsia"))]
155            fn spec(&self) -> RepositorySpec {
156                (**self).spec()
157            }
158
159            fn aliases(&self) -> &BTreeSet<String> {
160                (**self).aliases()
161            }
162
163            fn fetch_metadata_range<'a>(
164                &'a self,
165                path: &str,
166                range: Range,
167            ) -> BoxFuture<'a, Result<Resource, Error>> {
168                (**self).fetch_metadata_range(path, range)
169            }
170
171            fn fetch_blob_range<'a>(
172                &'a self,
173                path: &str,
174                range: Range,
175            ) -> BoxFuture<'a, Result<Resource, Error>> {
176                (**self).fetch_blob_range(path, range)
177            }
178
179            /// Whether or not the backend supports watching for file changes.
180            fn supports_watch(&self) -> bool {
181                (**self).supports_watch()
182            }
183
184            /// Returns a stream which sends a unit value every time the given path is modified.
185            fn watch(&self) -> anyhow::Result<BoxStream<'static, ()>> {
186                (**self).watch()
187            }
188
189            /// Get the modification time of a blob in this repository if available.
190            fn blob_modification_time<'a>(
191                &'a self,
192                path: &str,
193            ) -> BoxFuture<'a, anyhow::Result<Option<SystemTime>>> {
194                (**self).blob_modification_time(path)
195            }
196
197            /// Get the type of delivery blobs in this repository.
198            fn blob_type(&self) -> DeliveryBlobType {
199                (**self).blob_type()
200            }
201        }
202    };
203}
204
205impl_provider!(<T: RepoProvider + ?Sized> RepoProvider for &T);
206impl_provider!(<T: RepoProvider + ?Sized> RepoProvider for &mut T);
207impl_provider!(<T: RepoProvider + ?Sized> RepoProvider for Box<T>);
208impl_provider!(<T: RepoProvider + ?Sized> RepoProvider for Arc<T>);
209
210macro_rules! impl_storage {
211    (
212        <$($desc:tt)+
213    ) => {
214        impl <$($desc)+ {
215            fn store_blob<'a>(&'a self, hash: &Hash, len: u64, path: &Utf8Path) -> BoxFuture<'a, Result<()>> {
216                (**self).store_blob(hash, len, path)
217            }
218
219            fn store_delivery_blob<'a>(
220                &'a self,
221                hash: &Hash,
222                path: &Utf8Path,
223                delivery_blob_type: DeliveryBlobType,
224            ) -> BoxFuture<'a, Result<()>> {
225                (**self).store_delivery_blob(hash, path, delivery_blob_type)
226            }
227        }
228    };
229}
230
231impl_storage!(<T: RepoStorage + ?Sized> RepoStorage for &T);
232impl_storage!(<T: RepoStorage + ?Sized> RepoStorage for &mut T);
233impl_storage!(<T: RepoStorage + ?Sized> RepoStorage for Box<T>);
234impl_storage!(<T: RepoStorage + ?Sized> RepoStorage for Arc<T>);
235
236/// RepositorySpec describes all the different supported repositories.
237#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
238#[serde(tag = "type", rename_all = "snake_case")]
239pub enum RepositorySpec {
240    FileSystem {
241        metadata_repo_path: Utf8PathBuf,
242        blob_repo_path: Utf8PathBuf,
243        #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
244        aliases: BTreeSet<String>,
245    },
246
247    Pm {
248        path: Utf8PathBuf,
249        #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
250        aliases: BTreeSet<String>,
251    },
252
253    Http {
254        metadata_repo_url: String,
255        blob_repo_url: String,
256        #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
257        aliases: BTreeSet<String>,
258    },
259
260    Gcs {
261        metadata_repo_url: String,
262        blob_repo_url: String,
263        #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
264        aliases: BTreeSet<String>,
265    },
266}
267
268impl RepositorySpec {
269    pub fn aliases(&self) -> BTreeSet<String> {
270        match self {
271            RepositorySpec::FileSystem { aliases, .. } => aliases.clone(),
272            RepositorySpec::Pm { aliases, .. } => aliases.clone(),
273            RepositorySpec::Http { aliases, .. } => aliases.clone(),
274            RepositorySpec::Gcs { aliases, .. } => aliases.clone(),
275        }
276    }
277}