summaryrefslogtreecommitdiff
path: root/src/server.rs
blob: 9a809a56a44015268b01e6e44d29133c234d71cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
use crate::daemon::{Daemon, DaemonError, DaemonManager};
use crate::tlsgen::{Tls, TlsError};
use log::debug;
use rand::random;
use serde::Serialize;
use std::ops::Range;
use std::path::{Path, PathBuf};
use std::time::Instant;
use tempfile::{tempdir, TempDir};

const PORT_RANGE: Range<PortNumber> = 2048..32000;
const TIMEOUT_MS: u128 = 10_000;

type PortNumber = u16;

#[derive(Debug, thiserror::Error)]
pub enum ObnamServerError {
    #[error("took too long to pick a random port for server")]
    Port,

    #[error("failed to create temporary directory for server: {0}")]
    TempDir(std::io::Error),

    #[error("failed to write server configuration to {0}: {1}")]
    WriteConfig(PathBuf, std::io::Error),

    #[error("failed to create TLS certificate: {0}")]
    Tls(TlsError),

    #[error("failed to write TLS certificate: {0}")]
    WriteTls(PathBuf, std::io::Error),

    #[error("failed to start Obnam server: {0}")]
    Daemon(DaemonError),
}

#[derive(Debug)]
pub struct ObnamServer {
    #[allow(dead_code)]
    tempdir: TempDir,
    chunks: PathBuf,
    daemon: Option<Daemon>,
}

impl ObnamServer {
    pub fn new(manager: &DaemonManager) -> Result<Self, ObnamServerError> {
        debug!("creating ObamServer");
        let tempdir = tempdir().map_err(ObnamServerError::TempDir)?;
        let config_filename = tempdir.path().join("server.yaml");
        let chunks = tempdir.path().join("chunks");
        let tls_key = tempdir.path().join("tls_key");
        let tls_cert = tempdir.path().join("tls_cert");

        let tls = Tls::new().map_err(ObnamServerError::Tls)?;
        write(&tls_key, tls.key())?;
        write(&tls_cert, tls.cert())?;

        let port = pick_port()?;

        let config = ServerConfig::new(port, chunks.clone(), tls_key, tls_cert);
        config.write(&config_filename)?;

        let daemon = manager
            .start(&["/bin/sleep", "1000"])
            .map_err(ObnamServerError::Daemon)?;

        Ok(Self {
            tempdir,
            chunks,
            daemon: Some(daemon),
        })
    }

    pub fn stop(&mut self) {
        self.daemon.take();
    }

    pub fn chunks(&self) -> &Path {
        &self.chunks
    }
}

fn write(filename: &Path, data: &[u8]) -> Result<(), ObnamServerError> {
    std::fs::write(filename, data)
        .map_err(|err| ObnamServerError::WriteTls(filename.to_path_buf(), err))
}

fn pick_port() -> Result<PortNumber, ObnamServerError> {
    let started = Instant::now();
    while started.elapsed().as_millis() < TIMEOUT_MS {
        let port: PortNumber = random();
        if PORT_RANGE.contains(&port) {
            return Ok(port);
        }
    }
    Err(ObnamServerError::Port)
}

#[derive(Debug, Serialize)]
pub struct ServerConfig {
    address: String,
    chunks: PathBuf,
    tls_key: PathBuf,
    tls_cert: PathBuf,
}

impl ServerConfig {
    fn new(port: u16, chunks: PathBuf, tls_key: PathBuf, tls_cert: PathBuf) -> Self {
        Self {
            address: format!("localhost:{}", port),
            chunks,
            tls_key,
            tls_cert,
        }
    }

    fn write(&self, filename: &Path) -> Result<(), ObnamServerError> {
        std::fs::write(filename, serde_yaml::to_string(self).unwrap())
            .map_err(|err| ObnamServerError::WriteConfig(filename.to_path_buf(), err))?;
        Ok(())
    }
}