blob: ca1efd8f5008166dbae9a90bf028cdbcc10be924 [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::{anyhow, Context, Error};
use futures::channel::mpsc;
use rouille::{self, router, Request, Response};
use serde::{Deserialize, Serialize};
use std::io::Read;
use std::thread;
const SERVER_IP: &str = "::";
const SERVER_PORT: &str = "8880";
pub enum SetupEvent {
Root,
DevhostOta { cfg: DevhostConfig },
}
/// Devhost configuration, passed to the actual OTA process.
pub struct DevhostConfig {
pub url: String,
pub authorized_keys: 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,
/// Contents of SSH authorized keys file to install to minfs.
pub authorized_keys: String,
}
fn parse_ota_json(request: &Request) -> Result<DevhostConfig, Error> {
let mut host_addr = request.remote_addr().ip().to_string();
if request.remote_addr().is_ipv6() {
host_addr = format!("[{}]", host_addr);
}
let mut body = request.data().ok_or(anyhow!("Post body already read?"))?;
let mut json_str = String::new();
body.read_to_string(&mut json_str).context("Failed to read request body")?;
let cfg: DevhostRequestInfo =
serde_json::from_str(&json_str).context("Failed to parse JSON")?;
let result = DevhostConfig {
url: format!("http://{}:{}/config.json", host_addr, cfg.port),
authorized_keys: cfg.authorized_keys.clone(),
};
Ok(result)
}
fn serve(request: &Request, rouille_sender: mpsc::UnboundedSender<SetupEvent>) -> Response {
router!(request,
(GET) (/) => {
rouille_sender.unbounded_send(SetupEvent::Root).expect("Async thread closed the channel.");
rouille::Response::text("Root document\n")
},
(POST) (/ota/devhost) => {
// get devhost info out of POST request.
let result = parse_ota_json(request);
match result {
Err(e) => rouille::Response::text(format!("Bad request: {:?}", e)).with_status_code(400),
Ok(cfg) => {
rouille_sender.unbounded_send(SetupEvent::DevhostOta { cfg }).expect("Async thread closed the channel.");
rouille::Response::text("Started OTA\n")
},
}
},
_ => {
rouille::Response::text("Unknown command\n").with_status_code(404)
}
)
}
pub fn start_server() -> Result<mpsc::UnboundedReceiver<SetupEvent>, Error> {
println!("recovery: start_server");
let address = format!("{}:{}", SERVER_IP, SERVER_PORT);
let (rouille_sender, async_receiver) = mpsc::unbounded();
thread::Builder::new().name("setup-server".into()).spawn(move || {
rouille::start_server(address, move |request| serve(&request, rouille_sender.clone()));
})?;
Ok(async_receiver)
}