toe_beans/v4/server/config/
mod.rs

1mod lease_time;
2
3use crate::v4::MessageOptions;
4use ip_network::Ipv4Network;
5pub use lease_time::LeaseTime;
6use log::{info, warn};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::fs::{read_to_string, OpenOptions};
10use std::io::Write;
11use std::net::{Ipv4Addr, SocketAddrV4};
12use std::os::unix::fs::OpenOptionsExt;
13use std::path::PathBuf;
14use toml;
15
16/// Representation of a `toe-beans.toml` file. Used by the [Server](crate::v4::Server).
17///
18/// This is exported for you to use in your application to generate a `toe-beans.toml`.
19#[derive(Serialize, Deserialize, Debug)]
20pub struct Config {
21    /// The path to a directory where this config will exist on disk.
22    #[serde(skip)]
23    pub path: PathBuf,
24    /// See: [LeaseTime](lease_time::LeaseTime)
25    pub lease_time: LeaseTime,
26    /// Controls whether the server agrees to use [Rapid Commit](crate::v4::MessageOptions::RapidCommit).
27    pub rapid_commit: bool,
28    /// A range of IP addresses for the server to lease. Specified in CIDR notation. For example: `10.1.9.32/16`
29    pub network_cidr: Ipv4Network,
30    /// The server will bind to this address and port.
31    pub listen_address: SocketAddrV4,
32    /// The name of the network interface to broadcast server responses to.
33    /// Currently only takes one interface.
34    pub interface: Option<String>,
35    /// May or may not match the IP address in `listen_address`.
36    /// For example, you might be listening to `0.0.0.0`, but your server has another address it can be reached from.
37    /// This is used in both the `siaddr` field and `ServerIdentifier` option.
38    ///
39    /// If `None`, then `listen_address` is used.
40    pub server_address: Option<Ipv4Addr>,
41    /// DHCP option numbers (as a String) mapped to values that are used to respond to parameter request list options
42    /// Please note that this field, in particular, is subject to change.
43    pub parameters: HashMap<String, MessageOptions>,
44}
45
46impl Default for Config {
47    fn default() -> Self {
48        Self {
49            path: PathBuf::new(),                        // empty, the current directory
50            lease_time: LeaseTime::new(86_400).unwrap(), // seconds in a day
51            rapid_commit: true,
52            network_cidr: Ipv4Network::new(Ipv4Addr::new(10, 0, 0, 0), 16).unwrap(), // 2^(32-16) addresses
53            listen_address: SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), 8067),
54            interface: None,
55            server_address: None,
56            parameters: HashMap::with_capacity(u8::MAX as usize),
57        }
58    }
59}
60
61impl Config {
62    /// The file name that the Config is read from and written to.
63    pub const FILE_NAME: &'static str = "toe-beans.toml";
64
65    /// Reads the `toe-beans.toml` file and deserializes it into a Config.
66    /// Returns default options if there are any issues.
67    pub fn read(from_where: PathBuf) -> Self {
68        let read_result = read_to_string(from_where.join(Self::FILE_NAME));
69        if let Ok(config_string) = read_result {
70            let toml_result = toml::from_str(&config_string);
71            match toml_result {
72                Ok(config) => {
73                    return Self {
74                        path: from_where, // from_where might be different if passed through cli args
75                        ..config
76                    };
77                }
78                Err(error) => {
79                    warn!("{error}");
80                    warn!(
81                        "Warning: invalid {}. Using default values.",
82                        Self::FILE_NAME
83                    );
84                }
85            }
86        } else {
87            warn!(
88                "Warning: can't read {}. Using default values.",
89                Self::FILE_NAME
90            );
91        }
92
93        Self {
94            path: from_where,
95            ..Config::default()
96        }
97    }
98
99    /// Serializes a Config and writes it to a `toe-beans.toml` file.
100    pub fn write(&self) {
101        info!("Writing {}", Self::FILE_NAME);
102
103        let config_content = toml::to_string_pretty(self).expect("Failed to generate toml data");
104        let mode = 0o600;
105
106        let mut file = OpenOptions::new()
107            .read(false)
108            .write(true)
109            .create(true)
110            .truncate(true)
111            .mode(mode) // ensure that file permissions are set before creating file
112            .open(self.path.join(Self::FILE_NAME))
113            .unwrap_or_else(|_| panic!("Failed to open config {} for writing", Self::FILE_NAME));
114
115        file.write_all(config_content.as_bytes())
116            .unwrap_or_else(|_| panic!("Failed to write config to {}", Self::FILE_NAME));
117    }
118}