1use std::ffi::{CStr, c_char};
2use std::sync::{Arc, Mutex};
3
4use figment::Figment;
5use figment::providers::{Format, Json, Serialized};
6use tokio::runtime::{Builder, Runtime};
7
8use ombrac_macros::{error, info};
9
10use crate::config::{ConfigFile, ServiceConfig};
11#[cfg(feature = "tracing")]
12use crate::logging::LogCallback;
13#[cfg(feature = "transport-quic")]
14use crate::service::QuicServiceBuilder;
15use crate::service::Service;
16
17static SERVICE_HANDLE: Mutex<Option<ServiceHandle>> = Mutex::new(None);
19
20struct ServiceHandle {
22 #[cfg(feature = "transport-quic")]
23 service: Option<
24 Box<Service<ombrac_transport::quic::client::Client, ombrac_transport::quic::Connection>>,
25 >,
26 runtime: Runtime,
27}
28
29unsafe fn c_str_to_str<'a>(s: *const c_char) -> &'a str {
32 if s.is_null() {
33 return "";
34 }
35 unsafe { CStr::from_ptr(s).to_str().unwrap_or("") }
36}
37
38#[cfg(feature = "tracing")]
55#[unsafe(no_mangle)]
56pub unsafe extern "C" fn ombrac_client_set_log_callback(callback: *const LogCallback) {
57 let callback = if callback.is_null() {
58 None
59 } else {
60 Some(unsafe { *callback })
61 };
62 crate::logging::set_log_callback(callback);
63}
64
65#[unsafe(no_mangle)]
89pub unsafe extern "C" fn ombrac_client_service_startup(config_json: *const c_char) -> i32 {
90 let config_str = unsafe { c_str_to_str(config_json) };
91
92 let config_file: ConfigFile = match Figment::new()
93 .merge(Serialized::defaults(ConfigFile::default()))
94 .merge(Json::string(config_str))
95 .extract()
96 {
97 Ok(cfg) => cfg,
98 Err(_e) => {
99 error!("Failed to parse config JSON: {_e}");
100 return -1;
101 }
102 };
103
104 let service_config = match (config_file.secret, config_file.server) {
105 (Some(secret), Some(server)) => ServiceConfig {
106 secret,
107 server,
108 handshake_option: config_file.handshake_option,
109 endpoint: config_file.endpoint,
110 #[cfg(feature = "transport-quic")]
111 transport: config_file.transport,
112 #[cfg(feature = "tracing")]
113 logging: config_file.logging,
114 },
115 (None, _) => {
116 error!("Configuration error: missing required field `secret` in JSON config");
117 return -1;
118 }
119 (_, None) => {
120 error!("Configuration error: missing required field `server` in JSON config");
121 return -1;
122 }
123 };
124
125 #[cfg(feature = "tracing")]
126 crate::logging::init_for_ffi(&service_config.logging);
127
128 let runtime = match Builder::new_multi_thread().enable_all().build() {
129 Ok(rt) => rt,
130 Err(_e) => {
131 error!("Failed to create Tokio runtime: {}", _e);
132 return -1;
133 }
134 };
135
136 let service = runtime.block_on(async {
137 #[cfg(feature = "transport-quic")]
138 Service::build::<QuicServiceBuilder>(Arc::new(service_config)).await
139 });
140
141 #[cfg(feature = "transport-quic")]
142 let service = match service {
143 Ok(s) => s,
144 Err(e) => {
145 error!("Failed to build service: {}", e);
146 return -1;
147 }
148 };
149
150 #[cfg(not(feature = "transport-quic"))]
151 {
152 error!("The application was compiled without a transport feature");
153 return -1;
154 }
155
156 let mut handle_guard = SERVICE_HANDLE.lock().unwrap();
157 if handle_guard.is_some() {
158 error!("Service is already running. Please shut down the existing service first.");
159 return -1;
160 }
161
162 *handle_guard = Some(ServiceHandle {
163 #[cfg(feature = "transport-quic")]
164 service: Some(Box::new(service)),
165 runtime,
166 });
167
168 info!("Service started successfully");
169
170 0
171}
172
173#[unsafe(no_mangle)]
188pub extern "C" fn ombrac_client_service_rebind() -> i32 {
189 let handle_guard = SERVICE_HANDLE.lock().unwrap();
190 if let Some(handle) = handle_guard.as_ref() {
191 #[cfg(feature = "transport-quic")]
192 if let Some(service) = &handle.service {
193 let result = handle.runtime.block_on(service.rebind());
194 if let Err(e) = result {
195 error!("Failed to rebind: {}", e);
196 return -1;
197 } else {
198 info!("Service rebind successful");
199 return 0;
200 }
201 }
202 }
203 -1
204}
205
206#[unsafe(no_mangle)]
221pub extern "C" fn ombrac_client_service_shutdown() -> i32 {
222 let mut handle_guard = SERVICE_HANDLE.lock().unwrap();
223
224 if let Some(mut handle) = handle_guard.take() {
225 info!("Shutting down service");
226
227 #[cfg(feature = "transport-quic")]
228 if let Some(service) = handle.service.take() {
229 handle.runtime.block_on(async {
230 service.shutdown().await;
231 });
232 }
233
234 handle.runtime.shutdown_background();
235
236 info!("Service shut down complete.");
237 } else {
238 info!("Service was not running.");
239 }
240
241 0
242}
243
244#[unsafe(no_mangle)]
249pub extern "C" fn ombrac_client_get_version() -> *const c_char {
250 const VERSION_WITH_NULL: &str = concat!(env!("CARGO_PKG_VERSION"), "\0");
251 VERSION_WITH_NULL.as_ptr() as *const c_char
252}