1use crate::repo::Repository;
8use anyhow::{Context as _, Error, bail, format_err};
9use chrono::Utc;
10use fidl_fuchsia_pkg_ext::{
11 MirrorConfig, MirrorConfigBuilder, RepositoryConfig, RepositoryStorageType,
12};
13use fuchsia_async::net::TcpListener;
14use fuchsia_async::{self as fasync, Task};
15use fuchsia_repo::body::Body;
16use fuchsia_sync::Mutex;
17use fuchsia_url::RepositoryUrl;
18use futures::future::BoxFuture;
19use futures::prelude::*;
20use http::Uri;
21use http_body_util::BodyExt;
22use http_sse::{Event, EventSender, SseResponseCreator};
23use hyper::service::service_fn;
24use hyper::{Method, Request, Response, StatusCode, header};
25use std::convert::{Infallible, TryInto as _};
26use std::io::{Cursor, Read as _, Seek as _};
27use std::net::{IpAddr, Ipv6Addr, SocketAddr};
28use std::path::{Path, PathBuf};
29use std::pin::Pin;
30use std::sync::Arc;
31use std::sync::atomic::{AtomicU64, Ordering};
32use std::time::Duration;
33
34pub mod responder;
35
36trait AsyncReadWrite: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send {}
37impl<T> AsyncReadWrite for T where T: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send {}
38
39pub enum Domain {
41 TestFuchsiaCom,
43 WildcardFuchsiaUpdatesGoogleusercontentCom,
45}
46
47pub struct ServedRepositoryBuilder {
49 repo: Arc<Repository>,
50 response_overriders: Vec<Arc<dyn HttpResponder>>,
51 bind_addr: IpAddr,
52 bind_port: u16,
53 https_domain: Option<Domain>,
54}
55
56pub trait HttpResponder: 'static + Send + Sync {
59 fn respond<'a>(
61 &'a self,
62 request: &'a Request<Body>,
63 response: Response<Body>,
64 ) -> BoxFuture<'a, Response<Body>>;
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct HistoryEntry {
70 pub method: http::Method,
72 pub path: String,
74 pub status: http::StatusCode,
76}
77
78impl ServedRepositoryBuilder {
79 pub(crate) fn new(repo: Arc<Repository>) -> Self {
80 ServedRepositoryBuilder {
81 repo,
82 response_overriders: vec![],
83 bind_addr: Ipv6Addr::UNSPECIFIED.into(),
84 bind_port: 0,
85 https_domain: None,
86 }
87 }
88
89 pub fn response_overrider(mut self, responder: impl HttpResponder) -> Self {
94 self.response_overriders.push(Arc::new(responder));
95 self
96 }
97
98 pub fn use_https_domain(mut self, domain: Domain) -> Self {
101 self.https_domain = Some(domain);
102 self
103 }
104
105 pub fn bind_to_addr(mut self, addr: impl Into<IpAddr>) -> Self {
108 self.bind_addr = addr.into();
109 self
110 }
111
112 pub fn bind_to_port(mut self, port: u16) -> Self {
115 self.bind_port = port;
116 self
117 }
118
119 pub fn start(self) -> Result<ServedRepository, Error> {
121 let (listener, addr) = {
122 let addr = SocketAddr::new(self.bind_addr, self.bind_port);
123 let listener = TcpListener::bind(&addr).context("bind")?;
124 let local_addr = listener.local_addr().context("local_addr")?;
125 (listener, local_addr)
126 };
127
128 let listener = listener
129 .accept_stream()
130 .map_err(Error::from)
131 .map_ok(|(conn, _addr)| fuchsia_hyper::TcpStream { stream: conn });
132
133 let connection_attempts = Arc::new(AtomicU64::new(0));
134 #[allow(clippy::type_complexity)]
135 let connections: Pin<
136 Box<dyn Stream<Item = Result<Pin<Box<dyn AsyncReadWrite>>, Error>> + Send>,
137 > = if let Some(ref https_domain) = self.https_domain {
138 let (certs, key) = match https_domain {
140 Domain::TestFuchsiaCom => (
141 parse_cert_chain(&include_bytes!("../certs/test.fuchsia.com.certchain")[..]),
142 parse_private_key(&include_bytes!("../certs/test.fuchsia.com.rsa")[..]),
143 ),
144 Domain::WildcardFuchsiaUpdatesGoogleusercontentCom => (
145 parse_cert_chain(
146 &include_bytes!(
147 "../certs/wildcard.fuchsia-updates.googleusercontent.com.certchain"
148 )[..],
149 ),
150 parse_private_key(
151 &include_bytes!(
152 "../certs/wildcard.fuchsia-updates.googleusercontent.com.rsa"
153 )[..],
154 ),
155 ),
156 };
157 let mut tls_config = rustls::ServerConfig::builder()
158 .with_no_client_auth()
159 .with_single_cert(certs, key)
160 .unwrap();
161
162 tls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
164
165 let tls_acceptor = tokio_rustls::TlsAcceptor::from(Arc::new(tls_config));
166 let connection_attempts = Arc::clone(&connection_attempts);
167
168 listener
170 .and_then(move |conn| {
171 connection_attempts.fetch_add(1, Ordering::SeqCst);
172 tls_acceptor.accept(conn).map(|res| match res {
173 Ok(conn) => Ok(Pin::new(Box::new(conn)) as Pin<Box<dyn AsyncReadWrite>>),
174 Err(e) => Err(Error::from(e)),
175 })
176 })
177 .boxed()
178 } else {
179 let connection_attempts = Arc::clone(&connection_attempts);
180 listener
181 .map_ok(move |conn| {
182 connection_attempts.fetch_add(1, Ordering::SeqCst);
183 Pin::new(Box::new(conn)) as Pin<Box<dyn AsyncReadWrite>>
184 })
185 .boxed()
186 };
187
188 let root = self.repo.path();
189 let response_overriders = Arc::new(self.response_overriders);
190
191 let (auto_response_creator, auto_event_sender) =
192 SseResponseCreator::with_additional_buffer_size(10);
193 let auto_response_creator = Arc::new(auto_response_creator);
194
195 let history = Arc::new(Mutex::new(vec![]));
196 let history_clone = Arc::clone(&history);
197
198 let (stop, rx_stop) = futures::channel::oneshot::channel();
199 let rx_stop = rx_stop.map(|res| res.unwrap_or(())).shared();
200
201 let server = Task::spawn(async move {
202 let mut connections = connections.fuse();
203 let mut tasks = futures::stream::FuturesUnordered::new();
204 loop {
205 futures::select! {
206 conn = connections.next() => {
207 match conn {
208 Some(Ok(conn)) => {
209 let root = root.clone();
210 let response_overriders = Arc::clone(&response_overriders);
211 let auto_response_creator = Arc::clone(&auto_response_creator);
212 let history = Arc::clone(&history_clone);
213 let rx_stop = rx_stop.clone();
214 tasks.push(fuchsia_async::Task::spawn(async move {
215 let service = service_fn(move |req: Request<hyper::body::Incoming>| {
216 let (parts, _body) = req.into_parts();
217 let req = Request::from_parts(parts, Body::empty());
218 let version = req.version();
219 let method = req.method().to_owned();
220 let path = req.uri().path().to_owned();
221 let headers = req.headers().clone();
222 let history = Arc::clone(&history);
223 ServedRepository::handle_tuf_repo_request_infallible(
224 root.clone(),
225 Arc::clone(&response_overriders),
226 Arc::clone(&auto_response_creator),
227 req,
228 )
229 .inspect(move |x| {
230 history.lock().push(HistoryEntry {
231 method: method.clone(),
232 path: path.clone(),
233 status: x.status(),
234 });
235 println!(
236 "{} [http repo] {:?} {} {} {:?} => {}",
237 Utc::now().format("%T.%6f"),
238 version,
239 method,
240 path,
241 headers,
242 x.status()
243 )
244 })
245 .map(Ok::<_, Infallible>)
246 });
247 let builder = hyper_util::server::conn::auto::Builder::new(fuchsia_hyper::Executor);
248 let conn = builder.serve_connection(hyper_util::rt::TokioIo::new(conn), service);
249 let mut conn = std::pin::pin!(conn);
250 futures::select! {
251 res = conn.as_mut().fuse() => {
252 if let Err(e) = res {
253 log::warn!("error serving repo connection: {e}");
254 }
255 }
256 _ = rx_stop.clone() => {
257 conn.as_mut().graceful_shutdown();
258 let _ = conn.await;
259 }
260 }
261 }));
262 }
263 Some(Err(e)) => {
264 log::error!("error accepting repo connection: {e}");
265 }
266 None => break,
267 }
268 }
269 _ = rx_stop.clone() => break,
270 _ = tasks.next() => {}
271 }
272 }
273 while tasks.next().await.is_some() {}
274 });
275
276 Ok(ServedRepository {
277 repo: self.repo,
278 stop,
279 server,
280 addr,
281 https_domain: self.https_domain,
282 auto_event_sender,
283 connection_attempts,
284 history,
285 })
286 }
287}
288
289fn parse_cert_chain(mut bytes: &[u8]) -> Vec<rustls::pki_types::CertificateDer<'static>> {
290 rustls_pemfile::certs(&mut bytes).collect::<Result<Vec<_>, _>>().expect("certs to parse")
291}
292
293fn parse_private_key(mut bytes: &[u8]) -> rustls::pki_types::PrivateKeyDer<'static> {
294 rustls_pemfile::private_key(&mut bytes)
295 .expect("private keys to parse")
296 .expect("one private key")
297}
298
299pub struct ServedRepository {
301 repo: Arc<Repository>,
302 stop: futures::channel::oneshot::Sender<()>,
303 server: Task<()>,
304 addr: SocketAddr,
305 auto_event_sender: EventSender,
306 connection_attempts: Arc<AtomicU64>,
307 https_domain: Option<Domain>,
308 history: Arc<Mutex<Vec<HistoryEntry>>>,
309}
310
311impl ServedRepository {
312 fn scheme(&self) -> &'static str {
313 if self.https_domain.is_some() { "https" } else { "http" }
314 }
315 pub async fn get(&self, path: impl AsRef<str>) -> Result<Vec<u8>, Error> {
317 let url = format!("{}/{}", self.local_url(), path.as_ref());
318 get(url).await
319 }
320
321 pub fn local_url(&self) -> String {
323 format!("{}://localhost:{}", self.scheme(), self.addr.port())
324 }
325
326 pub async fn list_packages(&self) -> Result<Vec<crate::repo::PackageEntry>, Error> {
328 let targets_json = self.get("targets.json").await?;
329 let mut packages = crate::repo::iter_packages(Cursor::new(targets_json))?
330 .collect::<Result<Vec<_>, _>>()?;
331 packages.sort_unstable();
332 Ok(packages)
333 }
334
335 pub fn get_mirror_config_builder(&self) -> MirrorConfigBuilder {
337 MirrorConfigBuilder::new(self.local_url().parse::<Uri>().unwrap()).unwrap()
338 }
339
340 fn get_mirror_config(&self, subscribe: bool) -> MirrorConfig {
342 self.get_mirror_config_builder().subscribe(subscribe).build()
343 }
344
345 pub fn make_repo_config(&self, url: RepositoryUrl) -> RepositoryConfig {
348 self.repo.make_repo_config(url, Some(self.get_mirror_config(false)), false)
349 }
350
351 pub fn make_repo_config_with_subscribe(&self, url: RepositoryUrl) -> RepositoryConfig {
354 self.repo.make_repo_config(url, Some(self.get_mirror_config(true)), false)
355 }
356
357 pub fn make_repo_config_with_local_mirror(&self, url: RepositoryUrl) -> RepositoryConfig {
361 self.repo.make_repo_config(url, Some(self.get_mirror_config(false)), true)
362 }
363
364 pub fn make_repo_config_with_persistent_storage(&self, url: RepositoryUrl) -> RepositoryConfig {
366 self.repo
367 .make_repo_config_builder(url)
368 .add_mirror(self.get_mirror_config(false))
369 .use_local_mirror(false)
370 .repo_storage_type(RepositoryStorageType::Persistent)
371 .build()
372 }
373
374 pub async fn send_auto_event(&self, event: &Event) {
376 self.auto_event_sender.send(event).await
377 }
378
379 pub async fn wait_for_n_connected_auto_clients(&self, n: usize) {
382 loop {
383 let connected = self.auto_event_sender.client_count().await;
384 match connected.cmp(&n) {
385 std::cmp::Ordering::Equal => {
386 break;
387 }
388 std::cmp::Ordering::Greater => {
389 panic!("ServedRepository too many auto clients connected.");
390 }
391 _ => {}
392 }
393 fasync::Timer::new(Duration::from_millis(10)).await;
394 }
395 }
396
397 pub async fn drop_all_auto_clients(&self) {
399 self.auto_event_sender.drop_all_clients().await
400 }
401
402 pub fn stop(self) -> impl Future<Output = ()> {
404 self.stop.send(()).expect("remote end to still be open");
405 self.server
406 }
407
408 pub fn connection_attempts(&self) -> u64 {
410 self.connection_attempts.load(Ordering::SeqCst)
411 }
412
413 pub fn history(&self) -> Arc<Mutex<Vec<HistoryEntry>>> {
415 self.history.clone()
416 }
417
418 async fn handle_tuf_repo_request_infallible(
419 repo: PathBuf,
420 response_overriders: Arc<Vec<Arc<dyn HttpResponder>>>,
421 auto_response_creator: Arc<SseResponseCreator>,
422 req: Request<Body>,
423 ) -> Response<Body> {
424 let mut response = Self::handle_tuf_repo_request(repo, auto_response_creator, &req)
425 .await
426 .unwrap_or_else(|e| {
427 eprintln!("hyper tuf server error creating response for request {req:?}: {e:#}");
428 Response::builder()
429 .status(StatusCode::INTERNAL_SERVER_ERROR)
430 .body(Body::from("Error creating response".to_owned().into_bytes()))
431 .unwrap()
432 });
433
434 for responder in response_overriders.iter() {
435 response = responder.respond(&req, response).await
436 }
437
438 response
439 }
440
441 async fn handle_tuf_repo_request(
442 repo: PathBuf,
443 auto_response_creator: Arc<SseResponseCreator>,
444 req: &Request<Body>,
445 ) -> Result<Response<Body>, Error> {
446 let fail =
447 |status: StatusCode| Response::builder().status(status).body(Body::empty()).unwrap();
448 if *req.method() != Method::GET {
449 return Ok(fail(StatusCode::NOT_FOUND));
450 } else if req.uri().query().is_some() {
451 return Ok(fail(StatusCode::BAD_REQUEST));
452 }
453
454 let uri_path = Path::new(req.uri().path());
455
456 if uri_path.components().any(|component| component == std::path::Component::ParentDir) {
458 return Ok(fail(StatusCode::NOT_FOUND));
459 }
460
461 let response = if uri_path.to_str() == Some("/auto") {
462 let resp = auto_response_creator.create().await;
463 let (parts, body) = resp.into_parts();
464 let body = body.filter_map(|res| async move {
465 match res {
466 Ok(frame) => frame.into_data().ok().map(Ok),
467 Err(e) => Some(Err(e)),
468 }
469 });
470 Response::from_parts(parts, Body::wrap_stream(body))
471 } else {
472 let fs_path = repo.join(uri_path.strip_prefix("/").unwrap_or(uri_path));
473 let mut file = match std::fs::File::open(fs_path) {
475 Ok(file) => file,
476 Err(ref err) if err.kind() == std::io::ErrorKind::NotFound => {
477 return Ok(Response::builder()
478 .status(StatusCode::NOT_FOUND)
479 .body(Body::from("File did not exist".to_owned().into_bytes()))
480 .unwrap());
481 }
482 Err(e) => Err(e).context("opening file")?,
483 };
484
485 if let Some(range) = req.headers().get(http::header::RANGE) {
486 make_range_response(file, range).context("error making range response")?
487 } else {
488 let mut body = vec![];
489 file.read_to_end(&mut body).context("reading repo file")?;
490 Response::builder()
491 .status(StatusCode::OK)
492 .header(header::CONTENT_LENGTH, body.len())
493 .body(Body::from(body))
494 .unwrap()
495 }
496 };
497
498 Ok(response)
499 }
500}
501
502fn make_range_response(
505 mut file: std::fs::File,
506 range: &http::HeaderValue,
507) -> Result<Response<Body>, Error> {
508 let HttpRange { first_byte_pos, last_byte_pos } =
509 range.try_into().context("parse range header")?;
510 let file_size = file.metadata().context("file metadata")?.len();
511 file.seek(std::io::SeekFrom::Start(first_byte_pos)).context("seeking file")?;
513 let mut data = vec![0; 1 + last_byte_pos as usize - first_byte_pos as usize];
514 file.read_exact(&mut data).context("reading file for range request")?;
515
516 Ok(Response::builder()
517 .status(StatusCode::PARTIAL_CONTENT)
518 .header(header::CONTENT_LENGTH, data.len())
519 .header(
520 header::CONTENT_RANGE,
521 format!("bytes {first_byte_pos}-{last_byte_pos}/{file_size}"),
522 )
523 .body(Body::from(data))
524 .unwrap())
525}
526
527pub struct HttpRange {
529 first_byte_pos: u64,
530 last_byte_pos: u64,
531}
532
533impl HttpRange {
534 pub fn first_byte_pos(&self) -> u64 {
536 self.first_byte_pos
537 }
538
539 pub fn last_byte_pos(&self) -> u64 {
541 self.last_byte_pos
542 }
543}
544
545impl TryFrom<&http::HeaderValue> for HttpRange {
547 type Error = anyhow::Error;
548
549 fn try_from(range: &http::HeaderValue) -> Result<Self, Self::Error> {
550 let range = range.to_str().context("range header should be ascii")?;
551 let range = if let Some(range) = range.strip_prefix("bytes=") {
552 range
553 } else {
554 bail!("range header should start with 'bytes='");
555 };
556 let dash =
557 range.find('-').ok_or_else(|| anyhow::anyhow!("range header should have dash"))?;
558 let (first, last) = range.split_at(dash);
559 if last.len() < 2 {
560 bail!("range header last_byte_pos empty");
561 }
562 let first = first.parse().context("valid range first_byte_pos")?;
563 let last = last[1..].parse().context("valid range last_byte_pos")?;
564
565 if first > last {
566 bail!("first_byte_pos {} > last_byte_pos {}", first, last);
567 }
568
569 Ok(HttpRange { first_byte_pos: first, last_byte_pos: last })
570 }
571}
572
573async fn get(url: impl AsRef<str>) -> Result<Vec<u8>, Error> {
574 let request = Request::get(url.as_ref())
575 .body(http_body_util::Full::<hyper::body::Bytes>::new("".into()))
576 .map_err(Error::from)?;
577 let client = fuchsia_hyper::new_client();
578 let response = client.request(request).await?;
579
580 if response.status() != StatusCode::OK {
581 return Err(format_err!("unexpected status code: {:?}", response.status()));
582 }
583
584 let body = response.into_body().collect().await?.to_bytes();
585
586 Ok(body.to_vec())
587}
588
589#[cfg(test)]
590mod tests {
591 use super::*;
592 use crate::package::PackageBuilder;
593 use crate::repo::RepositoryBuilder;
594 use assert_matches::assert_matches;
595
596 #[fuchsia::test]
597 #[ignore]
598 async fn test_serve_empty_hangs_on_last_get() {
599 let repo = Arc::new(RepositoryBuilder::new().build().await.unwrap());
600 let served_repo = repo.server().start().unwrap();
601
602 let packages = served_repo.list_packages().await.unwrap();
604 assert_eq!(packages, vec![]);
605
606 assert_matches!(served_repo.get("blobs/../root.json").await, Err(_));
608
609 let bytes = served_repo.get("targets.json").await.unwrap();
611 assert_ne!(bytes, Vec::<u8>::new());
612
613 let url = format!("{}/targets.json", served_repo.local_url());
615 let also_bytes = get(&url).await.unwrap();
616 assert_eq!(bytes, also_bytes);
617
618 served_repo.stop().await;
620
621 assert_matches!(get(url).await, Err(_));
623 }
624
625 #[fuchsia::test]
626 async fn test_serve_empty() {
627 let repo = Arc::new(RepositoryBuilder::new().build().await.unwrap());
628 let served_repo = repo.server().start().unwrap();
629
630 let packages = served_repo.list_packages().await.unwrap();
632 assert_eq!(packages, vec![]);
633
634 assert_matches!(served_repo.get("blobs/../root.json").await, Err(_));
636
637 let bytes = served_repo.get("targets.json").await.unwrap();
639 assert_ne!(bytes, Vec::<u8>::new());
640
641 let url = format!("{}/targets.json", served_repo.local_url());
643 let also_bytes = get(&url).await.unwrap();
644 assert_eq!(bytes, also_bytes);
645 }
646
647 #[fuchsia::test]
648 async fn test_serve_packages() {
649 let same_contents = "same contents";
650 let repo = RepositoryBuilder::new()
651 .add_package(
652 PackageBuilder::new("rolldice")
653 .add_resource_at("bin/rolldice", "#!/boot/bin/sh\necho 4\n".as_bytes())
654 .add_resource_at(
655 "meta/rolldice.cml",
656 r#"{"program":{"binary":"bin/rolldice"}}"#.as_bytes(),
657 )
658 .add_resource_at("data/duplicate_a", "same contents".as_bytes())
659 .build()
660 .await
661 .unwrap(),
662 )
663 .add_package(
664 PackageBuilder::new("fortune")
665 .add_resource_at(
666 "bin/fortune",
667 "#!/boot/bin/sh\necho ask again later\n".as_bytes(),
668 )
669 .add_resource_at(
670 "meta/fortune.cml",
671 r#"{"program":{"binary":"bin/fortune"}}"#.as_bytes(),
672 )
673 .add_resource_at("data/duplicate_b", same_contents.as_bytes())
674 .add_resource_at("data/duplicate_c", same_contents.as_bytes())
675 .build()
676 .await
677 .unwrap(),
678 )
679 .build()
680 .await
681 .unwrap();
682 let repo = Arc::new(repo);
683
684 let local_packages = repo.list_packages().unwrap();
685
686 let served_repository = repo.server().start().unwrap();
687 let served_packages = served_repository.list_packages().await.unwrap();
688 assert_eq!(local_packages, served_packages);
689 }
690}