1use crate::crypto::{self, HashAlgorithm, HashValue};
4use crate::metadata::{
5 Metadata, MetadataPath, MetadataVersion, RawSignedMetadata, TargetDescription, TargetPath,
6};
7use crate::pouf::Pouf;
8use crate::util::SafeAsyncRead;
9use crate::{Error, Result};
10
11use futures_io::AsyncRead;
12use futures_util::future::BoxFuture;
13use futures_util::io::AsyncReadExt;
14use std::marker::PhantomData;
15use std::sync::Arc;
16
17mod file_system;
18pub use self::file_system::{
19 FileSystemBatchUpdate, FileSystemRepository, FileSystemRepositoryBuilder,
20};
21
22#[cfg(feature = "hyper")]
23mod http;
24
25#[cfg(feature = "hyper")]
26pub use self::http::{HttpRepository, HttpRepositoryBuilder};
27
28mod ephemeral;
29pub use self::ephemeral::{EphemeralBatchUpdate, EphemeralRepository};
30
31#[cfg(test)]
32mod error_repo;
33#[cfg(test)]
34pub(crate) use self::error_repo::ErrorRepository;
35
36#[cfg(test)]
37mod track_repo;
38#[cfg(test)]
39pub(crate) use self::track_repo::{Track, TrackRepository};
40
41pub trait RepositoryProvider<D>
43where
44 D: Pouf,
45{
46 fn fetch_metadata<'a>(
57 &'a self,
58 meta_path: &MetadataPath,
59 version: MetadataVersion,
60 ) -> BoxFuture<'a, Result<Box<dyn AsyncRead + Send + Unpin + 'a>>>;
61
62 fn fetch_target<'a>(
71 &'a self,
72 target_path: &TargetPath,
73 ) -> BoxFuture<'a, Result<Box<dyn AsyncRead + Send + Unpin + 'a>>>;
74}
75
76#[cfg(test)]
78pub(crate) async fn fetch_metadata_to_string<D, R>(
79 repo: &R,
80 meta_path: &MetadataPath,
81 version: MetadataVersion,
82) -> Result<String>
83where
84 D: Pouf,
85 R: RepositoryProvider<D>,
86{
87 let mut reader = repo.fetch_metadata(meta_path, version).await?;
88 let mut buf = String::new();
89 reader.read_to_string(&mut buf).await.unwrap();
90 Ok(buf)
91}
92
93#[cfg(test)]
95pub(crate) async fn fetch_target_to_string<D, R>(
96 repo: &R,
97 target_path: &TargetPath,
98) -> Result<String>
99where
100 D: Pouf,
101 R: RepositoryProvider<D>,
102{
103 let mut reader = repo.fetch_target(target_path).await?;
104 let mut buf = String::new();
105 reader.read_to_string(&mut buf).await.unwrap();
106 Ok(buf)
107}
108
109pub trait RepositoryStorage<D>
112where
113 D: Pouf,
114{
115 fn store_metadata<'a>(
120 &'a self,
121 meta_path: &MetadataPath,
122 version: MetadataVersion,
123 metadata: &'a mut (dyn AsyncRead + Send + Unpin),
124 ) -> BoxFuture<'a, Result<()>>;
125
126 fn store_target<'a>(
129 &'a self,
130 target_path: &TargetPath,
131 target: &'a mut (dyn AsyncRead + Send + Unpin),
132 ) -> BoxFuture<'a, Result<()>>;
133}
134
135pub trait RepositoryStorageProvider<D>: RepositoryStorage<D> + RepositoryProvider<D>
138where
139 D: Pouf,
140{
141}
142
143impl<D, T> RepositoryStorageProvider<D> for T
144where
145 D: Pouf,
146 T: RepositoryStorage<D> + RepositoryProvider<D>,
147{
148}
149
150macro_rules! impl_provider {
151 (
152 <$($desc:tt)+
153 ) => {
154 impl<$($desc)+ {
155 fn fetch_metadata<'a>(
156 &'a self,
157 meta_path: &MetadataPath,
158 version: MetadataVersion,
159 ) -> BoxFuture<'a, Result<Box<dyn AsyncRead + Send + Unpin + 'a>>> {
160 (**self).fetch_metadata(meta_path, version)
161 }
162
163 fn fetch_target<'a>(
164 &'a self,
165 target_path: &TargetPath,
166 ) -> BoxFuture<'a, Result<Box<dyn AsyncRead + Send + Unpin + 'a>>> {
167 (**self).fetch_target(target_path)
168 }
169 }
170 };
171}
172
173impl_provider!(<D: Pouf, T: RepositoryProvider<D> + ?Sized> RepositoryProvider<D> for &T);
174impl_provider!(<D: Pouf, T: RepositoryProvider<D> + ?Sized> RepositoryProvider<D> for &mut T);
175impl_provider!(<D: Pouf, T: RepositoryProvider<D> + ?Sized> RepositoryProvider<D> for Box<T>);
176impl_provider!(<D: Pouf, T: RepositoryProvider<D> + ?Sized> RepositoryProvider<D> for Arc<T>);
177
178macro_rules! impl_storage {
179 (
180 <$($desc:tt)+
181 ) => {
182 impl<$($desc)+ {
183 fn store_metadata<'a>(
184 &'a self,
185 meta_path: &MetadataPath,
186 version: MetadataVersion,
187 metadata: &'a mut (dyn AsyncRead + Send + Unpin),
188 ) -> BoxFuture<'a, Result<()>> {
189 (**self).store_metadata(meta_path, version, metadata)
190 }
191
192 fn store_target<'a>(
193 &'a self,
194 target_path: &TargetPath,
195 target: &'a mut (dyn AsyncRead + Send + Unpin),
196 ) -> BoxFuture<'a, Result<()>> {
197 (**self).store_target(target_path, target)
198 }
199 }
200 };
201}
202
203impl_storage!(<D: Pouf, T: RepositoryStorage<D> + ?Sized> RepositoryStorage<D> for &T);
204impl_storage!(<D: Pouf, T: RepositoryStorage<D> + ?Sized> RepositoryStorage<D> for &mut T);
205impl_storage!(<D: Pouf, T: RepositoryStorage<D> + ?Sized> RepositoryStorage<D> for Box<T>);
206impl_storage!(<D: Pouf, T: RepositoryStorage<D> + ?Sized> RepositoryStorage<D> for Arc<T>);
207
208#[derive(Debug, Clone)]
211pub(crate) struct Repository<R, D> {
212 repository: R,
213 _pouf: PhantomData<D>,
214}
215
216impl<R, D> Repository<R, D> {
217 pub(crate) fn new(repository: R) -> Self {
219 Self {
220 repository,
221 _pouf: PhantomData,
222 }
223 }
224
225 fn check<M>(meta_path: &MetadataPath) -> Result<()>
227 where
228 M: Metadata,
229 {
230 if !M::ROLE.fuzzy_matches_path(meta_path) {
231 return Err(Error::IllegalArgument(format!(
232 "Role {} does not match path {:?}",
233 M::ROLE,
234 meta_path
235 )));
236 }
237
238 Ok(())
239 }
240
241 pub(crate) fn into_inner(self) -> R {
242 self.repository
243 }
244
245 pub(crate) fn as_inner(&self) -> &R {
246 &self.repository
247 }
248
249 pub(crate) fn as_inner_mut(&mut self) -> &mut R {
250 &mut self.repository
251 }
252}
253
254impl<R, D> Repository<R, D>
255where
256 R: RepositoryProvider<D>,
257 D: Pouf,
258{
259 pub(crate) async fn fetch_metadata<'a, M>(
267 &'a self,
268 meta_path: &'a MetadataPath,
269 version: MetadataVersion,
270 max_length: Option<usize>,
271 hashes: Vec<(&'static HashAlgorithm, HashValue)>,
272 ) -> Result<RawSignedMetadata<D, M>>
273 where
274 M: Metadata,
275 {
276 Self::check::<M>(meta_path)?;
277
278 let mut reader = self
282 .repository
283 .fetch_metadata(meta_path, version)
284 .await?
285 .check_length_and_hash(max_length.unwrap_or(usize::MAX) as u64, hashes)?;
286
287 let mut buf = Vec::new();
288 reader.read_to_end(&mut buf).await?;
289
290 Ok(RawSignedMetadata::new(buf))
291 }
292
293 pub(crate) async fn fetch_target(
301 &self,
302 consistent_snapshot: bool,
303 target_path: &TargetPath,
304 target_description: TargetDescription,
305 ) -> Result<impl AsyncRead + Send + Unpin + '_> {
306 let length = target_description.length();
311 let hashes = crypto::retain_supported_hashes(target_description.hashes());
312 if hashes.is_empty() {
313 return Err(Error::NoSupportedHashAlgorithm);
314 }
315
316 let target = if consistent_snapshot {
322 let mut hashes = hashes.iter();
323 loop {
324 if let Some((_, hash)) = hashes.next() {
325 let target_path = target_path.with_hash_prefix(hash)?;
326 match self.repository.fetch_target(&target_path).await {
327 Ok(target) => break target,
328 Err(Error::TargetNotFound(_)) => {}
329 Err(err) => return Err(err),
330 }
331 } else {
332 return Err(Error::TargetNotFound(target_path.clone()));
333 }
334 }
335 } else {
336 self.repository.fetch_target(target_path).await?
337 };
338
339 target.check_length_and_hash(length, hashes)
340 }
341}
342
343impl<R, D> Repository<R, D>
344where
345 R: RepositoryStorage<D>,
346 D: Pouf,
347{
348 pub async fn store_metadata<'a, M>(
353 &'a mut self,
354 path: &MetadataPath,
355 version: MetadataVersion,
356 metadata: &'a RawSignedMetadata<D, M>,
357 ) -> Result<()>
358 where
359 M: Metadata + Sync,
360 {
361 Self::check::<M>(path)?;
362
363 self.repository
364 .store_metadata(path, version, &mut metadata.as_bytes())
365 .await
366 }
367
368 pub async fn store_target<'a>(
370 &'a mut self,
371 target_path: &TargetPath,
372 target: &'a mut (dyn AsyncRead + Send + Unpin + 'a),
373 ) -> Result<()> {
374 self.repository.store_target(target_path, target).await
375 }
376}
377
378#[cfg(test)]
379mod test {
380 use super::*;
381 use crate::metadata::{MetadataPath, MetadataVersion, RootMetadata, SnapshotMetadata};
382 use crate::pouf::Pouf1;
383 use crate::repository::EphemeralRepository;
384 use assert_matches::assert_matches;
385 use futures_executor::block_on;
386
387 #[test]
388 fn repository_forwards_not_found_error() {
389 block_on(async {
390 let repo = Repository::<_, Pouf1>::new(EphemeralRepository::new());
391
392 assert_matches!(
393 repo.fetch_metadata::<RootMetadata>(
394 &MetadataPath::root(),
395 MetadataVersion::None,
396 None,
397 vec![],
398 )
399 .await,
400 Err(Error::MetadataNotFound { path, version })
401 if path == MetadataPath::root() && version == MetadataVersion::None
402 );
403 });
404 }
405
406 #[test]
407 fn repository_rejects_mismatched_path() {
408 block_on(async {
409 let mut repo = Repository::<_, Pouf1>::new(EphemeralRepository::new());
410 let fake_metadata = RawSignedMetadata::<Pouf1, RootMetadata>::new(vec![]);
411
412 repo.store_metadata(&MetadataPath::root(), MetadataVersion::None, &fake_metadata)
413 .await
414 .unwrap();
415
416 assert_matches!(
417 repo.store_metadata(
418 &MetadataPath::snapshot(),
419 MetadataVersion::None,
420 &fake_metadata,
421 )
422 .await,
423 Err(Error::IllegalArgument(_))
424 );
425
426 assert_matches!(
427 repo.fetch_metadata::<SnapshotMetadata>(
428 &MetadataPath::root(),
429 MetadataVersion::None,
430 None,
431 vec![],
432 )
433 .await,
434 Err(Error::IllegalArgument(_))
435 );
436 });
437 }
438
439 #[test]
440 fn repository_verifies_metadata_hash() {
441 block_on(async {
442 let path = MetadataPath::root();
443 let version = MetadataVersion::None;
444 let data: &[u8] = b"valid metadata";
445 let _metadata = RawSignedMetadata::<Pouf1, RootMetadata>::new(data.to_vec());
446 let data_hash = crypto::calculate_hash(data, &HashAlgorithm::Sha256);
447
448 let repo = EphemeralRepository::new();
449 repo.store_metadata(&path, version, &mut &*data)
450 .await
451 .unwrap();
452
453 let client = Repository::<_, Pouf1>::new(repo);
454
455 assert_matches!(
456 client
457 .fetch_metadata::<RootMetadata>(
458 &path,
459 version,
460 None,
461 vec![(&HashAlgorithm::Sha256, data_hash)],
462 )
463 .await,
464 Ok(_metadata)
465 );
466 })
467 }
468
469 #[test]
470 fn repository_rejects_corrupt_metadata() {
471 block_on(async {
472 let path = MetadataPath::root();
473 let version = MetadataVersion::None;
474 let data: &[u8] = b"corrupt metadata";
475
476 let repo = EphemeralRepository::new();
477 repo.store_metadata(&path, version, &mut &*data)
478 .await
479 .unwrap();
480
481 let client = Repository::<_, Pouf1>::new(repo);
482
483 assert_matches!(
484 client
485 .fetch_metadata::<RootMetadata>(
486 &path,
487 version,
488 None,
489 vec![(&HashAlgorithm::Sha256, HashValue::new(vec![]))],
490 )
491 .await,
492 Err(_)
493 );
494 })
495 }
496
497 #[test]
498 fn repository_verifies_metadata_size() {
499 block_on(async {
500 let path = MetadataPath::root();
501 let version = MetadataVersion::None;
502 let data: &[u8] = b"reasonably sized metadata";
503 let _metadata = RawSignedMetadata::<Pouf1, RootMetadata>::new(data.to_vec());
504
505 let repo = EphemeralRepository::new();
506 repo.store_metadata(&path, version, &mut &*data)
507 .await
508 .unwrap();
509
510 let client = Repository::<_, Pouf1>::new(repo);
511
512 assert_matches!(
513 client
514 .fetch_metadata::<RootMetadata>(&path, version, Some(100), vec![])
515 .await,
516 Ok(_metadata)
517 );
518 })
519 }
520
521 #[test]
522 fn repository_rejects_oversized_metadata() {
523 block_on(async {
524 let path = MetadataPath::root();
525 let version = MetadataVersion::None;
526 let data: &[u8] = b"very big metadata";
527
528 let repo = EphemeralRepository::new();
529 repo.store_metadata(&path, version, &mut &*data)
530 .await
531 .unwrap();
532
533 let client = Repository::<_, Pouf1>::new(repo);
534
535 assert_matches!(
536 client
537 .fetch_metadata::<RootMetadata>(&path, version, Some(4), vec![])
538 .await,
539 Err(_)
540 );
541 })
542 }
543
544 #[test]
545 fn repository_rejects_corrupt_targets() {
546 block_on(async {
547 let repo = EphemeralRepository::new();
548 let mut client = Repository::<_, Pouf1>::new(repo);
549
550 let data: &[u8] = b"like tears in the rain";
551 let target_description =
552 TargetDescription::from_slice(data, &[HashAlgorithm::Sha256]).unwrap();
553 let path = TargetPath::new("batty").unwrap();
554 client.store_target(&path, &mut &*data).await.unwrap();
555
556 let mut read = client
557 .fetch_target(false, &path, target_description.clone())
558 .await
559 .unwrap();
560 let mut buf = Vec::new();
561 read.read_to_end(&mut buf).await.unwrap();
562 assert_eq!(buf.as_slice(), data);
563 drop(read);
564
565 let bad_data: &[u8] = b"you're in a desert";
566 client.store_target(&path, &mut &*bad_data).await.unwrap();
567 let mut read = client
568 .fetch_target(false, &path, target_description)
569 .await
570 .unwrap();
571 assert!(read.read_to_end(&mut buf).await.is_err());
572 })
573 }
574
575 #[test]
576 fn repository_takes_trait_objects() {
577 block_on(async {
578 let repo: Box<dyn RepositoryStorageProvider<Pouf1>> =
579 Box::new(EphemeralRepository::new());
580 let mut client = Repository::<_, Pouf1>::new(repo);
581
582 let data: &[u8] = b"like tears in the rain";
583 let target_description =
584 TargetDescription::from_slice(data, &[HashAlgorithm::Sha256]).unwrap();
585 let path = TargetPath::new("batty").unwrap();
586 client.store_target(&path, &mut &*data).await.unwrap();
587
588 let mut read = client
589 .fetch_target(false, &path, target_description)
590 .await
591 .unwrap();
592 let mut buf = Vec::new();
593 read.read_to_end(&mut buf).await.unwrap();
594 assert_eq!(buf.as_slice(), data);
595 })
596 }
597
598 #[test]
599 fn repository_dyn_impls_repository_traits() {
600 let mut repo = EphemeralRepository::new();
601
602 fn storage<T: RepositoryStorage<Pouf1>>(_t: T) {}
603 fn provider<T: RepositoryProvider<Pouf1>>(_t: T) {}
604
605 provider(&repo as &dyn RepositoryProvider<Pouf1>);
606 provider(&mut repo as &mut dyn RepositoryProvider<Pouf1>);
607 storage(&mut repo as &mut dyn RepositoryStorage<Pouf1>);
608 }
609}