fidl_fuchsia_pkg_ext/
base_package_index.rs
1use crate::BlobId;
8use anyhow::Error;
9use fidl_fuchsia_pkg::{PackageCacheProxy, PackageIndexIteratorMarker};
10use fuchsia_url::{AbsolutePackageUrl, UnpinnedAbsolutePackageUrl};
11use std::collections::HashMap;
12
13#[derive(Debug)]
15pub struct BasePackageIndex {
16 index: HashMap<UnpinnedAbsolutePackageUrl, BlobId>,
17}
18
19impl BasePackageIndex {
20 pub async fn from_proxy(cache: &PackageCacheProxy) -> Result<Self, Error> {
22 let (pkg_iterator, server_end) =
23 fidl::endpoints::create_proxy::<PackageIndexIteratorMarker>();
24 cache.base_package_index(server_end)?;
25
26 let mut index = HashMap::with_capacity(256);
27 let mut chunk = pkg_iterator.next().await?;
28 while !chunk.is_empty() {
29 for entry in chunk {
30 let pkg_url = UnpinnedAbsolutePackageUrl::parse(&entry.package_url.url)?;
31 let blob_id = BlobId::from(entry.meta_far_blob_id);
32 index.insert(pkg_url, blob_id);
33 }
34 chunk = pkg_iterator.next().await?;
35 }
36 index.shrink_to_fit();
37
38 Ok(Self { index })
39 }
40
41 pub fn is_unpinned_base_package(&self, pkg_url: &AbsolutePackageUrl) -> Option<BlobId> {
44 let pkg_url = match pkg_url {
47 AbsolutePackageUrl::Unpinned(unpinned) => unpinned,
48 AbsolutePackageUrl::Pinned(_) => return None,
49 };
50
51 let stripped_url;
53 let url_without_zero_variant = match pkg_url.variant() {
54 Some(variant) if variant.is_zero() => {
55 let mut url = pkg_url.clone();
56 url.clear_variant();
57 stripped_url = url;
58 &stripped_url
59 }
60 _ => pkg_url,
61 };
62 match self.index.get(&url_without_zero_variant) {
63 Some(base_merkle) => Some(base_merkle.clone()),
64 None => None,
65 }
66 }
67
68 pub fn create_mock(index: HashMap<UnpinnedAbsolutePackageUrl, BlobId>) -> Self {
71 Self { index }
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78 use fidl::endpoints::create_proxy_and_stream;
79 use fidl_fuchsia_pkg::{
80 self as fpkg, PackageCacheMarker, PackageCacheRequest, PackageCacheRequestStream,
81 PackageIndexEntry, PackageIndexIteratorRequest, PackageIndexIteratorRequestStream,
82 };
83 use fuchsia_async as fasync;
84 use futures::prelude::*;
85 use log::error;
86 use std::sync::Arc;
87
88 const PACKAGE_INDEX_CHUNK_SIZE: u32 = 30;
92
93 struct MockPackageCacheService {
94 base_packages: Arc<HashMap<UnpinnedAbsolutePackageUrl, BlobId>>,
95 }
96
97 impl MockPackageCacheService {
98 fn new_with_base_packages(
99 base_packages: Arc<HashMap<UnpinnedAbsolutePackageUrl, BlobId>>,
100 ) -> Self {
101 Self { base_packages: base_packages }
102 }
103
104 async fn run_service(self, mut stream: PackageCacheRequestStream) {
105 while let Some(req) = stream.try_next().await.unwrap() {
106 match req {
107 PackageCacheRequest::BasePackageIndex { iterator, control_handle: _ } => {
108 let iterator = iterator.into_stream();
109 self.serve_package_iterator(iterator);
110 }
111 _ => panic!("unexpected PackageCache request: {:?}", req),
112 }
113 }
114 }
115
116 fn serve_package_iterator(&self, mut stream: PackageIndexIteratorRequestStream) {
117 let packages = self
118 .base_packages
119 .iter()
120 .map(|(path, hash)| PackageIndexEntry {
121 package_url: fpkg::PackageUrl {
122 url: format!("fuchsia-pkg://fuchsia.com/{}", path.name()),
123 },
124 meta_far_blob_id: BlobId::from(hash.clone()).into(),
125 })
126 .collect::<Vec<PackageIndexEntry>>();
127
128 fasync::Task::spawn(
129 async move {
130 let mut iter = packages.chunks(PACKAGE_INDEX_CHUNK_SIZE as usize);
131 while let Some(request) = stream.try_next().await? {
132 let PackageIndexIteratorRequest::Next { responder } = request;
133 responder.send(iter.next().unwrap_or(&[]))?;
134 }
135 Ok(())
136 }
137 .unwrap_or_else(|e: fidl::Error| {
138 error!("while serving package index iterator: {:?}", e)
139 }),
140 )
141 .detach();
142 }
143 }
144
145 async fn spawn_pkg_cache(
146 base_package_index: HashMap<UnpinnedAbsolutePackageUrl, BlobId>,
147 ) -> PackageCacheProxy {
148 let (client, request_stream) = create_proxy_and_stream::<PackageCacheMarker>();
149 let cache = MockPackageCacheService::new_with_base_packages(Arc::new(base_package_index));
150 fasync::Task::spawn(cache.run_service(request_stream)).detach();
151 client
152 }
153
154 #[fasync::run_singlethreaded(test)]
155 async fn empty_base_packages() {
156 let expected_packages = HashMap::new();
157 let client = spawn_pkg_cache(expected_packages.clone()).await;
158 let base = BasePackageIndex::from_proxy(&client).await.unwrap();
159 assert_eq!(base.index, expected_packages);
160 }
161
162 fn index_with_n_entries(n: u32) -> HashMap<UnpinnedAbsolutePackageUrl, BlobId> {
164 let mut base = HashMap::new();
165 for i in 0..n {
166 let pkg_url = format!("fuchsia-pkg://fuchsia.com/{}", i)
167 .parse::<UnpinnedAbsolutePackageUrl>()
168 .unwrap();
169 let blob_id = format!("{:064}", i).parse::<BlobId>().unwrap();
170 base.insert(pkg_url, blob_id);
171 }
172 base
173 }
174
175 #[fasync::run_singlethreaded(test)]
176 async fn chunk_size_boundary() {
177 let package_counts = [
178 PACKAGE_INDEX_CHUNK_SIZE - 1,
179 PACKAGE_INDEX_CHUNK_SIZE,
180 PACKAGE_INDEX_CHUNK_SIZE + 1,
181 2 * PACKAGE_INDEX_CHUNK_SIZE + 1,
182 ];
183 for count in package_counts.iter() {
184 let expected_packages = index_with_n_entries(*count);
185 let client = spawn_pkg_cache(expected_packages.clone()).await;
186 let base = BasePackageIndex::from_proxy(&client).await.unwrap();
187 assert_eq!(base.index, expected_packages);
188 }
189 }
190
191 fn zeroes_hash() -> BlobId {
192 "0000000000000000000000000000000000000000000000000000000000000000".parse().unwrap()
193 }
194
195 #[test]
196 fn reject_pinned_urls() {
197 let url: AbsolutePackageUrl = "fuchsia-pkg://fuchsia.com/package-name?\
198 hash=0000000000000000000000000000000000000000000000000000000000000000"
199 .parse()
200 .unwrap();
201 let index = BasePackageIndex { index: [(url.as_unpinned().clone(), zeroes_hash())].into() };
202
203 assert_eq!(index.is_unpinned_base_package(&url), None);
204 }
205
206 #[test]
207 fn strip_0_variant() {
208 let url_no_variant: UnpinnedAbsolutePackageUrl =
209 "fuchsia-pkg://fuchsia.com/package-name".parse().unwrap();
210 let index = BasePackageIndex { index: [(url_no_variant.clone(), zeroes_hash())].into() };
211
212 let url_with_variant: AbsolutePackageUrl =
213 "fuchsia-pkg://fuchsia.com/package-name/0".parse().unwrap();
214 assert_eq!(index.is_unpinned_base_package(&url_with_variant), Some(zeroes_hash()));
215 }
216
217 #[test]
218 fn leave_1_variant() {
219 let url_no_variant: UnpinnedAbsolutePackageUrl =
220 "fuchsia-pkg://fuchsia.com/package-name".parse().unwrap();
221 let index = BasePackageIndex { index: [(url_no_variant.clone(), zeroes_hash())].into() };
222
223 let url_with_variant: AbsolutePackageUrl =
224 "fuchsia-pkg://fuchsia.com/package-name/1".parse().unwrap();
225 assert_eq!(index.is_unpinned_base_package(&url_with_variant), None);
226 }
227}