romp 0.5.2

STOMP server and WebSockets platform
Documentation
//! Loads `romp.toml` and provides access to properties to the running application.
//! The crate contains a documented example configuration.  Where possible config is similar to `xtomp`.

use log::*;

use std::collections::HashMap;

use serde_derive::Deserialize;
use crate::system::limits::increase_rlimit_nofile;
use crate::util::some_string;

pub static CONFIG_FILE: &str = "romp.toml";


#[derive(Deserialize, Debug)]
#[serde(default)]
pub struct ServerConfig {
    pub name: String,
    pub bind_address: String,
    pub admin_bind_address: Option<String>,
    pub ports: Vec<u16>,
    pub admin_port: u16,
    pub socket_addresses: Vec<String>,
    pub login: Option<String>,
    pub passcode: Option<String>,
    pub user_file: Option<String>,
    pub pid_file: String,
    pub secret: Option<String>,
    pub secret_timeout: i64,
    /// enable administration filters, e.g. shutdown
    pub enable_admin: bool,
    /// enable GUI console filters
    pub enable_console: bool,
    pub max_message_size: usize,
    pub max_headers: usize,
    pub max_header_len: usize,
    pub max_subs: usize,
    pub rlimit_nofile: u64,
    pub request_client_buffer: usize,
    pub response_client_buffer: usize,
    pub access_log: LevelFilter,

    pub heart_beat_read: u32,
    pub heart_beat_write_min: u32,
    pub heart_beat_write_max: u32,

    pub websockets: bool,
    pub websockets_origin: Option<String>,
    pub server_flags: Vec<String>,

    pub destinations: HashMap<String, Destination>,
    pub downstreams: HashMap<String, Downstream>,
}

impl Default for ServerConfig {
    fn default() -> Self {
        ServerConfig {
            name: "romp".to_string(),
            bind_address: "127.0.0.1".to_string(),
            admin_bind_address: None,
            ports: vec!(61613),
            admin_port: 61616,
            socket_addresses: vec!("127.0.0.1:61613".to_string()),
            login: None,
            passcode: None,
            user_file: None,
            pid_file: "/run/romp/romp.pid".to_string(),
            secret: None,
            secret_timeout: 60000,
            enable_admin: false,
            enable_console: false,
            max_message_size: 1048576,
            max_headers: 10,
            max_header_len: 200,
            max_subs: 4,
            rlimit_nofile: 1048576,
            request_client_buffer: 4096,
            response_client_buffer: 200,
            access_log: LevelFilter::Info,

            heart_beat_read: 120000,
            heart_beat_write_min: 60000,
            heart_beat_write_max: 180000,

            websockets: true,
            websockets_origin: None,
            server_flags: vec![],

            destinations: HashMap::default(),
            downstreams: HashMap::default(),
        }
    }
}

impl ServerConfig {

    pub fn init(&mut self) {

        self.socket_addresses.clear();
        for port in self.ports.iter() {
            let port_string = port.to_string();

            let mut addr: String = self.bind_address.clone();
            // trusted port is only opened on localhost, or admin_bind_address
            if self.admin_port == *port {
                if let Some(admin_bind) = &self.admin_bind_address {
                    addr = admin_bind.clone();
                } else {
                    addr = String::from("127.0.0.1");
                }
            }

            addr.push_str(":");
            addr.push_str(port_string.as_str());
            self.socket_addresses.push(addr);
        }
        
        if increase_rlimit_nofile(self.rlimit_nofile) == 0 {
            info!("nofile: {}", self.rlimit_nofile);
        } else {
            warn!("nofile not set");
        }

        for (_, dest) in self.destinations.iter_mut() {
            if dest.max_message_size == 0 {
                dest.max_message_size = self.max_message_size;
            }
        }

        if let Some(secret) = &self.secret {
            if secret.len() < 12 {
                warn!("crap password: {}", secret);
            }
        }
    }

    pub fn name(&self) -> &String {
        &self.name
    }

    /// Returns true if this server requires login
    pub fn requires_auth(&self) -> bool {
        if self.login.is_some() && self.passcode.is_some() {
             return true;
        }
        if self.user_file.is_some() {
             return true;
        }
        if self.secret.is_some() {
             return true;
        }
        return false;
    }

    pub fn requires_sha_auth(&self) -> bool {
        self.secret.is_some()
    }

    pub fn supports_simple_auth(&self) -> bool {
        self.login.is_some() && self.passcode.is_some()
    }

    pub fn supports_user_auth(&self) -> bool {
        self.user_file.is_some()
    }

    pub fn login(&self) -> &String {
        match &self.login {
            Some(login) => login,
            None => panic!("login not configured")
        }
    }

    pub fn passcode(&self) -> &String {
        match &self.passcode {
            Some(passcode) => passcode,
            None => panic!("passcode not configured")
        }
    }

    /// Get configuration of a named destination
    pub fn destination(&self, name: &String) -> Option<&Destination> {
        self.destinations.get(name)
    }

    /// Get configuration of a named donwstream destination
    pub fn downstream(&self, name: &String) -> Option<&Downstream> {
        self.downstreams.get(name)
    }
}

#[derive(Deserialize, Debug)]
#[serde(default)]
pub struct Destination {
    pub name: String,
    pub max_connections: usize,
    pub max_messages: usize,
    pub expiry: u64,
    pub pedantic_expiry: bool,
    pub filter: bool,
    pub filter_self: bool,
    pub sub_by_user: bool,
    pub auto_ack: bool,
    pub max_message_size: usize,
    pub min_delivery: usize,
    pub write_block: bool,
    pub read_block: bool,
    pub web_write_block: bool,
    pub web_read_block: bool,
    pub stats: bool,
    // pub log_messages: bool,
}

impl Destination {
}

impl Default for Destination {
    fn default() -> Self {
        Destination {
            name: "".to_string(),
            max_connections: 1000000,
            max_messages: 100,
            expiry: 120000,
            pedantic_expiry: false,
            filter: true,
            filter_self: false,
            sub_by_user: false,
            // default when init() is called is the server's max_message_size
            auto_ack: false,
            max_message_size: 1048576,
            min_delivery: 0,
            write_block: false,
            read_block: false,
            web_write_block: false,
            web_read_block: false,
            stats: false,
        }
    }
}

#[derive(Deserialize, Debug)]
#[serde(default)]
pub struct Downstream {
    pub name: String,
    pub address: String,
    pub login: Option<String>,
    pub passcode: String,
    pub destinations: Vec<String>,
    pub local_destinations: Vec<String>,
}

impl Downstream {
    // TODO shuly this is not necessary

    pub fn copy(&self) -> Downstream {
        Downstream {
            name: self.name.clone(),
            address: self.address.clone(),
            login: self.login.clone(),
            passcode: self.passcode.clone(),
            destinations: self.destinations.clone(),
            local_destinations: self.local_destinations.clone(),
        }
    }
}

impl Default for Downstream {
    fn default() -> Self {
        Downstream {
            name: "".to_string(),
            address: "127.0.0.1:61614".to_string(),
            login: some_string("xtomp"),
            passcode: "password".to_string(),
            destinations: vec![],
            local_destinations: vec![],
        }
    }
}
#[cfg(test)]
mod tests {

    use crate::init::CONFIG;

    #[test]
    fn test_init() {
        // println does not work here ?? it does in other code
        println!("CONFIG.socket_addr {:?}", CONFIG.socket_addresses);
        println!("CONFIG.login {:?}", CONFIG.login);
        println!("CONFIG.login {:?}", CONFIG.destinations);
    }
}