1use 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 fn spec(&self) -> RepositorySpec;
88
89 fn aliases(&self) -> &BTreeSet<String>;
91
92 fn fetch_metadata_range<'a>(
94 &'a self,
95 path: &str,
96 range: Range,
97 ) -> BoxFuture<'a, Result<Resource, Error>>;
98
99 fn fetch_blob_range<'a>(
101 &'a self,
102 path: &str,
103 range: Range,
104 ) -> BoxFuture<'a, Result<Resource, Error>>;
105
106 fn supports_watch(&self) -> bool {
108 false
109 }
110
111 fn watch(&self) -> anyhow::Result<BoxStream<'static, ()>> {
113 Err(anyhow::anyhow!("Watching not supported for this repo type"))
114 }
115
116 fn blob_modification_time<'a>(
118 &'a self,
119 path: &str,
120 ) -> BoxFuture<'a, anyhow::Result<Option<SystemTime>>>;
121
122 fn blob_type(&self) -> DeliveryBlobType {
124 DeliveryBlobType::Type1
125 }
126}
127
128pub trait RepoStorage: TufRepositoryStorage<Pouf1> + Send + Sync {
129 fn store_blob<'a>(
131 &'a self,
132 hash: &Hash,
133 len: u64,
134 path: &Utf8Path,
135 ) -> BoxFuture<'a, Result<()>>;
136
137 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 fn supports_watch(&self) -> bool {
181 (**self).supports_watch()
182 }
183
184 fn watch(&self) -> anyhow::Result<BoxStream<'static, ()>> {
186 (**self).watch()
187 }
188
189 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 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#[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}