clawft_kernel/
mesh_mode.rs1use std::path::{Path, PathBuf};
14
15use clawft_types::config::{MeshConfig, MeshServicePolicy};
16
17pub const DEFAULT_SERVICE_SOCKET: &str = "/var/run/weftos/mesh.sock";
19pub const SERVICE_SOCKET_ENV: &str = "WEFTOS_MESH_SOCKET";
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24#[non_exhaustive]
25pub enum MeshMode {
26 Off,
28 Collapsed,
30 Service {
33 sock: PathBuf,
35 },
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum ServiceProbe {
41 Present,
43 Absent,
45 Refused(String),
48}
49
50pub fn service_socket(cfg: &MeshConfig) -> PathBuf {
53 resolve_socket(cfg, std::env::var(SERVICE_SOCKET_ENV).ok().as_deref())
54}
55
56fn resolve_socket(cfg: &MeshConfig, env: Option<&str>) -> PathBuf {
57 cfg.service_socket
58 .as_deref()
59 .filter(|s| !s.is_empty())
60 .or(env.filter(|s| !s.is_empty()))
61 .map_or_else(|| PathBuf::from(DEFAULT_SERVICE_SOCKET), PathBuf::from)
62}
63
64pub fn select(cfg: &MeshConfig) -> MeshMode {
69 if cfg.enabled {
70 MeshMode::Collapsed
71 } else {
72 MeshMode::Off
73 }
74}
75
76pub fn wants_probe(cfg: &MeshConfig) -> bool {
78 match cfg.service {
79 MeshServicePolicy::Off => false,
80 MeshServicePolicy::Auto => cfg.enabled,
81 MeshServicePolicy::Required => true,
82 }
83}
84
85pub fn decide(cfg: &MeshConfig, probe: Option<ServiceProbe>) -> Result<MeshMode, String> {
92 let sock = service_socket(cfg);
93 decide_at(cfg, probe, &sock)
94}
95
96fn decide_at(cfg: &MeshConfig, probe: Option<ServiceProbe>, sock: &Path) -> Result<MeshMode, String> {
97 match (cfg.service, probe) {
98 (MeshServicePolicy::Off, _) => Ok(select(cfg)),
99 (MeshServicePolicy::Auto, _) if !cfg.enabled => Ok(MeshMode::Off),
100 (_, Some(ServiceProbe::Refused(why))) => Err(format!(
101 "the machine mesh service at {} refused or failed verification: {why}",
102 sock.display()
103 )),
104 (_, Some(ServiceProbe::Present)) => Ok(MeshMode::Service { sock: sock.to_path_buf() }),
105 (MeshServicePolicy::Required, _) => Err(format!(
106 "kernel.mesh.service = \"required\" but no machine mesh service answers at {}; \
107 start it (`weaver mesh serve`, or the installed service) or set \
108 kernel.mesh.service = \"auto\"",
109 sock.display()
110 )),
111 (_, _) => Ok(select(cfg)),
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 fn cfg(policy: MeshServicePolicy, enabled: bool) -> MeshConfig {
120 MeshConfig { enabled, service: policy, ..MeshConfig::default() }
121 }
122
123 #[test]
124 fn disabled_is_off() {
125 assert_eq!(select(&MeshConfig::default()), MeshMode::Off);
126 }
127
128 #[test]
129 fn enabled_is_collapsed() {
130 assert_eq!(select(&cfg(MeshServicePolicy::Auto, true)), MeshMode::Collapsed);
131 }
132
133 #[test]
134 fn auto_uses_a_present_service_else_collapses() {
135 let c = cfg(MeshServicePolicy::Auto, true);
136 assert!(matches!(
137 decide(&c, Some(ServiceProbe::Present)),
138 Ok(MeshMode::Service { .. })
139 ));
140 assert_eq!(decide(&c, Some(ServiceProbe::Absent)), Ok(MeshMode::Collapsed));
141 }
142
143 #[test]
144 fn required_without_a_service_fails_with_the_reason() {
145 let c = cfg(MeshServicePolicy::Required, true);
146 let e = decide(&c, Some(ServiceProbe::Absent)).unwrap_err();
147 assert!(e.contains("required") && e.contains("mesh.sock"), "{e}");
148 }
149
150 #[test]
151 fn a_refusing_service_is_fatal_even_under_auto() {
152 let c = cfg(MeshServicePolicy::Auto, true);
153 let e = decide(&c, Some(ServiceProbe::Refused("machine_key_changed".into()))).unwrap_err();
154 assert!(e.contains("machine_key_changed"), "{e}");
155 }
156
157 #[test]
158 fn auto_with_the_mesh_disabled_stays_off_without_probing() {
159 let c = cfg(MeshServicePolicy::Auto, false);
160 assert!(!wants_probe(&c));
161 assert_eq!(decide(&c, Some(ServiceProbe::Present)), Ok(MeshMode::Off));
162 }
163
164 #[test]
165 fn off_never_uses_the_service() {
166 let c = cfg(MeshServicePolicy::Off, true);
167 assert_eq!(decide(&c, None), Ok(MeshMode::Collapsed));
168 assert!(!wants_probe(&c));
169 }
170
171 #[test]
172 fn socket_resolution_order() {
173 let mut c = MeshConfig::default();
174 assert_eq!(resolve_socket(&c, None), Path::new(DEFAULT_SERVICE_SOCKET));
175 assert_eq!(resolve_socket(&c, Some("/e.sock")), Path::new("/e.sock"));
176 c.service_socket = Some("/c.sock".into());
177 assert_eq!(resolve_socket(&c, Some("/e.sock")), Path::new("/c.sock"));
178 }
179}