1use serde::{Deserialize, Serialize};
2use std::fs;
3use std::path::Path;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct Config {
8 pub bind_addr: String,
10
11 pub port: u16,
13
14 pub threads: usize,
16
17 pub data_path: Option<String>,
19
20 pub max_connections_per_thread: usize,
22
23 pub connection_buffer_size: usize,
25
26 pub tcp_nodelay: bool,
28
29 pub max_pipeline_depth: usize,
31
32 pub numa_aware: bool,
34
35 pub max_memory_per_shard: Option<usize>,
37
38 pub enable_ttl: bool,
40
41 pub file_size: Option<u64>,
44
45 pub log_level: String,
47
48 #[serde(skip_serializing_if = "Option::is_none")]
51 pub requirepass: Option<String>,
52}
53
54impl Default for Config {
55 fn default() -> Self {
56 Self {
57 bind_addr: "127.0.0.1".to_string(),
58 port: 6379,
59 threads: num_cpus::get(),
60 data_path: None,
61 max_connections_per_thread: 10000,
62 connection_buffer_size: 16 * 1024, tcp_nodelay: true,
64 max_pipeline_depth: 1000,
65 numa_aware: false,
66 max_memory_per_shard: Some(1024 * 1024 * 1024), enable_ttl: true,
68 file_size: Some(10 * 1024 * 1024 * 1024), log_level: "info".to_string(),
70 requirepass: None,
71 }
72 }
73}
74
75impl Config {
76 pub fn from_file<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
89 let contents = fs::read_to_string(path)?;
90 let mut config: Config = toml::from_str(&contents)?;
91
92 if config.requirepass.is_none() {
94 if let Ok(password) = std::env::var("FEOX_AUTH_PASSWORD") {
95 config.requirepass = Some(password);
96 std::env::remove_var("FEOX_AUTH_PASSWORD");
98 }
99 }
100
101 Ok(config)
102 }
103
104 pub fn to_file<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
106 let contents = toml::to_string_pretty(self)?;
107 fs::write(path, contents)?;
108 Ok(())
109 }
110
111 pub fn validate(&self) -> anyhow::Result<()> {
113 if self.threads == 0 {
114 anyhow::bail!("threads must be > 0");
115 }
116
117 if self.port == 0 {
118 anyhow::bail!("port must be > 0");
119 }
120
121 if self.connection_buffer_size < 1024 {
122 anyhow::bail!("connection_buffer_size must be >= 1024");
123 }
124
125 Ok(())
126 }
127
128 pub fn auth_required(&self) -> bool {
130 self.requirepass.is_some()
131 }
132
133 pub fn check_password(&self, password: &str) -> bool {
135 match &self.requirepass {
136 Some(correct) => constant_time_eq(password.as_bytes(), correct.as_bytes()),
137 None => false,
138 }
139 }
140}
141
142fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
144 if a.len() != b.len() {
145 return false;
146 }
147
148 let mut result = 0u8;
149 for (byte_a, byte_b) in a.iter().zip(b.iter()) {
150 result |= byte_a ^ byte_b;
151 }
152 result == 0
153}