blob: 541074f4e9ab5283bd231d4b9012be6405573f12 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use anyhow::{Context as _, Error};
use fuchsia_async as fasync;
use hyper::{Body, Request, Response};
use serde::{Deserialize, Serialize};
use std::convert::Infallible;
use std::future::Future;
use std::net::{IpAddr, SocketAddr};
const SERVER_PORT: u16 = 8880;
pub enum SetupEvent {
Root,
DevhostOta { cfg: DevhostConfig },
}
/// Devhost configuration, passed to the actual OTA process.
pub struct DevhostConfig {
pub url: String,
}
#[derive(Deserialize, Serialize)]
/// Configuration provided by the host for the devhost OTA. Only used for de/serialization.
struct DevhostRequestInfo {
/// We assume that the OTA server is running on the requester's address
/// at the given port.
pub port: u16,
}
async fn parse_ota_json(
request: Request<Body>,
remote_addr: IpAddr,
) -> Result<DevhostConfig, Error> {
use bytes::Buf as _;
let body = hyper::body::aggregate(request.into_body()).await.context("read request")?;
let DevhostRequestInfo { port } =
serde_json::from_reader(body.reader()).context("Failed to parse JSON")?;
let url = format!("http://{}/config.json", SocketAddr::new(remote_addr, port));
Ok(DevhostConfig { url })
}
async fn serve<Fut, F>(
request: Request<Body>,
remote_addr: SocketAddr,
handler: F,
) -> Response<Body>
where
Fut: Future<Output = ()>,
F: FnOnce(SetupEvent) -> Fut,
{
use hyper::{Method, StatusCode};
match (request.method(), request.uri().path()) {
(&Method::GET, "/") => {
let () = handler(SetupEvent::Root).await;
Response::new("Root document".into())
}
(&Method::POST, "/ota/devhost") => {
// get devhost info out of POST request.
match parse_ota_json(request, remote_addr.ip()).await {
Err(e) => {
let mut response = Response::new(format!("Bad request: {:?}", e).into());
*response.status_mut() = StatusCode::BAD_REQUEST;
response
}
Ok(cfg) => {
let () = handler(SetupEvent::DevhostOta { cfg }).await;
Response::new("Started OTA".into())
}
}
}
_ => {
let mut response = Response::new("Unknown command".into());
*response.status_mut() = StatusCode::NOT_FOUND;
response
}
}
}
pub fn start_server<Fut, F>(handler: F) -> impl Future<Output = Result<(), hyper::Error>>
where
Fut: Future<Output = ()>,
F: FnOnce(SetupEvent) -> Fut,
Fut: Send + 'static,
F: Clone + Send + 'static,
{
use futures::{FutureExt as _, TryStreamExt as _};
use hyper::service::{make_service_fn, service_fn};
println!("recovery: start_server");
let addr = SocketAddr::new(IpAddr::V6(std::net::Ipv6Addr::UNSPECIFIED), SERVER_PORT);
let listener = fasync::net::TcpListener::bind(&addr).expect("bind");
let listener = listener
.accept_stream()
.map_ok(|(stream, _): (_, SocketAddr)| fuchsia_hyper::TcpStream { stream });
let make_svc = make_service_fn(move |fuchsia_hyper::TcpStream { stream }| {
let handler = handler.clone();
std::future::ready((|| {
let remote_addr = stream.std().peer_addr().context("peer addr")?;
Ok::<_, Error>(service_fn(move |request| {
let handler = handler.clone();
serve(request, remote_addr, handler).map(Ok::<_, Infallible>)
}))
})())
});
hyper::Server::builder(hyper::server::accept::from_stream(listener))
.executor(fuchsia_hyper::Executor)
.serve(make_svc)
}