jocalsend 1.6.1

A terminal implementation of the LocalSend protocol
Documentation
use std::{net::SocketAddr, path::Path};

use axum::{
    Json, Router,
    extract::DefaultBodyLimit,
    routing::{get, post},
};
use axum_server::{Handle, tls_rustls::RustlsConfig};
use tokio_rustls::rustls::{
    ServerConfig,
    pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject},
};
use tower_http::limit::RequestBodyLimitLayer;

use crate::{
    JocalService,
    discovery::register_device,
    transfer::{handle_prepare_upload, handle_receive_upload},
};

impl JocalService {
    pub async fn start_http_server(&self) -> crate::error::Result<()> {
        let app = self.create_router();
        // TODO: make addr config
        let addr = SocketAddr::from(([0, 0, 0, 0], self.config.device.port));
        let (key, cert) = self.config.ssl();

        let ssl_config = rustls_server_config(key, cert);

        let handle = Handle::new();
        self.http_handle.get_or_init(|| handle.clone());

        log::info!("starting http server");

        axum_server::bind_rustls(addr, ssl_config)
            .handle(handle)
            .serve(app.into_make_service_with_connect_info::<SocketAddr>())
            .await?;

        Ok(())
    }

    fn create_router(&self) -> Router {
        let device = self.config.device.clone();
        let d2 = device.clone();
        Router::new()
            .route("/api/localsend/v2/register", post(register_device))
            .route(
                "/api/localsend/v2/info",
                get(move || async move { Json(device) }),
            )
            .route(
                // the mobile client is trying to hit this one
                "/api/localsend/v1/info",
                get(move || async move { Json(d2) }),
            )
            .route(
                "/api/localsend/v2/prepare-upload",
                post(handle_prepare_upload),
            )
            .route("/api/localsend/v2/upload", post(handle_receive_upload))
            .layer(DefaultBodyLimit::disable())
            .layer(RequestBodyLimitLayer::new(1024 * 1024 * 1024))
            .with_state(self.clone())
    }
}

fn rustls_server_config(key: impl AsRef<Path>, cert: impl AsRef<Path>) -> RustlsConfig {
    let key = match PrivateKeyDer::from_pem_file(&key) {
        Ok(k) => k,
        Err(e) => {
            let path = key.as_ref().display();
            log::error!("could not open {path} for reading; got {e:?}");
            panic!()
        }
    };

    let certs = CertificateDer::pem_file_iter(cert)
        .unwrap()
        .map(|cert| cert.unwrap())
        .collect();

    let mut config = ServerConfig::builder()
        .with_no_client_auth()
        .with_single_cert(certs, key)
        .expect("bad certificate/key");

    config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];

    RustlsConfig::from_config(config.into())
}