1use crate::process::command::{
5 NativeConfigCheck, SingBoxChildPoll, SingBoxChildProcess, SingBoxCommandPort,
6 TokioSingBoxCommandPort,
7};
8use async_trait::async_trait;
9use lurker_core_contract::core_process::CoreProcessStartContext;
10use lurker_core_contract::error::{ErrorCode, Failure};
11use lurker_core_contract::identity::{CoreIdentity, CoreInstanceId, EngineId};
12use lurker_core_contract::lifecycle::CoreLifecycle;
13use lurker_core_ports::core_process::CoreProcess;
14use lurker_core_ports::error::PortError;
15use std::mem;
16use std::net::SocketAddr;
17use std::path::PathBuf;
18use std::sync::Arc;
19use tokio::sync::Mutex;
20use tokio::sync::OwnedMutexGuard;
21
22#[derive(Default)]
25pub struct SingBoxCoreOperationGate {
26 lock: Arc<Mutex<()>>,
27}
28
29impl SingBoxCoreOperationGate {
30 pub fn new() -> Self {
32 Self::default()
33 }
34
35 pub async fn acquire(&self) -> OwnedMutexGuard<()> {
37 Arc::clone(&self.lock).lock_owned().await
38 }
39}
40
41enum ProcessState {
42 Stopped,
43 Running(Box<dyn SingBoxChildProcess>),
44 Exited,
45}
46
47pub struct SingBoxProcess {
49 binary_path: PathBuf,
50 config_path: PathBuf,
51 identity: CoreIdentity,
52 controller_endpoint: String,
53 command: Arc<dyn SingBoxCommandPort>,
54 operation_gate: Arc<SingBoxCoreOperationGate>,
55 state: Mutex<ProcessState>,
56}
57
58impl SingBoxProcess {
59 pub fn with_tokio(
64 binary_path: PathBuf,
65 config_path: PathBuf,
66 instance_id: CoreInstanceId,
67 controller_address: SocketAddr,
68 ) -> Result<Self, PortError> {
69 Self::new(
70 binary_path,
71 config_path,
72 instance_id,
73 controller_address,
74 Arc::new(TokioSingBoxCommandPort),
75 )
76 }
77
78 pub fn new(
80 binary_path: PathBuf,
81 config_path: PathBuf,
82 instance_id: CoreInstanceId,
83 controller_address: SocketAddr,
84 command: Arc<dyn SingBoxCommandPort>,
85 ) -> Result<Self, PortError> {
86 Self::new_with_operation_gate(
87 binary_path,
88 config_path,
89 instance_id,
90 controller_address,
91 command,
92 Arc::new(SingBoxCoreOperationGate::new()),
93 )
94 }
95
96 pub fn new_with_operation_gate(
98 binary_path: PathBuf,
99 config_path: PathBuf,
100 instance_id: CoreInstanceId,
101 controller_address: SocketAddr,
102 command: Arc<dyn SingBoxCommandPort>,
103 operation_gate: Arc<SingBoxCoreOperationGate>,
104 ) -> Result<Self, PortError> {
105 if !controller_address.ip().is_loopback() || controller_address.port() == 0 {
106 return Err(PortError::Failed(
107 "sing-box controller must bind to a fixed loopback address".to_owned(),
108 ));
109 }
110
111 Ok(Self {
112 binary_path,
113 config_path,
114 identity: CoreIdentity::new(EngineId::sing_box(), instance_id, None),
115 controller_endpoint: controller_address.to_string(),
116 command,
117 operation_gate,
118 state: Mutex::new(ProcessState::Stopped),
119 })
120 }
121
122 pub fn binary_path(&self) -> &PathBuf {
124 &self.binary_path
125 }
126
127 pub fn config_path(&self) -> &PathBuf {
129 &self.config_path
130 }
131
132 pub fn operation_gate(&self) -> Arc<SingBoxCoreOperationGate> {
134 Arc::clone(&self.operation_gate)
135 }
136
137 async fn prepare_start(&self, state: &mut ProcessState) -> Result<(), PortError> {
138 match mem::replace(state, ProcessState::Stopped) {
139 ProcessState::Stopped | ProcessState::Exited => Ok(()),
140 ProcessState::Running(mut child) => match child.poll().await {
141 Ok(SingBoxChildPoll::Running) => {
142 *state = ProcessState::Running(child);
143 Err(PortError::Failed(
144 "sing-box process is already running".to_owned(),
145 ))
146 }
147 Ok(SingBoxChildPoll::Exited) => {
148 *state = ProcessState::Exited;
149 Ok(())
150 }
151 Err(error) => {
152 *state = ProcessState::Running(child);
153 Err(error)
154 }
155 },
156 }
157 }
158}
159
160#[async_trait]
161impl CoreProcess for SingBoxProcess {
162 fn core_identity(&self) -> Option<CoreIdentity> {
163 Some(self.identity.clone())
164 }
165
166 async fn start(&self, context: CoreProcessStartContext) -> Result<(), PortError> {
167 if !context.identity().same_instance(&self.identity) {
168 return Err(PortError::Failed(
169 "Core start context targets a different sing-box process".to_owned(),
170 ));
171 }
172 let _gate = self.operation_gate.acquire().await;
173 let mut state = self.state.lock().await;
174 self.prepare_start(&mut state).await?;
175
176 match self
177 .command
178 .check_config(&self.binary_path, &self.config_path)
179 .await?
180 {
181 NativeConfigCheck::Accepted => {}
182 NativeConfigCheck::Rejected => {
183 return Err(PortError::Rejected(Failure::new(
184 ErrorCode::Configuration,
185 "sing-box native config check rejected the active document",
186 false,
187 )));
188 }
189 }
190
191 let child = self
192 .command
193 .spawn_core(&self.binary_path, &self.config_path)
194 .await?;
195 *state = ProcessState::Running(child);
196 Ok(())
197 }
198
199 async fn stop(&self) -> Result<(), PortError> {
200 let _gate = self.operation_gate.acquire().await;
201 let mut state = self.state.lock().await;
202 match mem::replace(&mut *state, ProcessState::Stopped) {
203 ProcessState::Stopped | ProcessState::Exited => Ok(()),
204 ProcessState::Running(mut child) => match child.terminate().await {
205 Ok(()) => Ok(()),
206 Err(error) => {
207 *state = ProcessState::Running(child);
208 Err(error)
209 }
210 },
211 }
212 }
213
214 async fn status(&self) -> Result<CoreLifecycle, PortError> {
215 let _gate = self.operation_gate.acquire().await;
216 let mut state = self.state.lock().await;
217 match mem::replace(&mut *state, ProcessState::Stopped) {
218 ProcessState::Stopped => Ok(CoreLifecycle::Stopped),
219 ProcessState::Exited => {
220 *state = ProcessState::Exited;
221 Ok(CoreLifecycle::Failed)
222 }
223 ProcessState::Running(mut child) => match child.poll().await {
224 Ok(SingBoxChildPoll::Running) => {
225 *state = ProcessState::Running(child);
226 Ok(CoreLifecycle::Running)
227 }
228 Ok(SingBoxChildPoll::Exited) => {
229 *state = ProcessState::Exited;
230 Ok(CoreLifecycle::Failed)
231 }
232 Err(error) => {
233 *state = ProcessState::Running(child);
234 Err(error)
235 }
236 },
237 }
238 }
239
240 fn controller_endpoint(&self) -> Option<String> {
241 Some(self.controller_endpoint.clone())
242 }
243
244 async fn pid(&self) -> Option<u32> {
245 match self.status().await {
246 Ok(CoreLifecycle::Running) => {
247 let state = self.state.lock().await;
248 match &*state {
249 ProcessState::Running(child) => Some(child.pid()),
250 ProcessState::Stopped | ProcessState::Exited => None,
251 }
252 }
253 Ok(
254 CoreLifecycle::Stopped
255 | CoreLifecycle::Starting
256 | CoreLifecycle::Ready
257 | CoreLifecycle::Stopping
258 | CoreLifecycle::Failed,
259 )
260 | Err(_) => None,
261 }
262 }
263}