1use crate::Value;
2use crate::registry;
3use crate::{EResult, Error};
4use busrt::rpc::{self, RpcClient, RpcHandlers};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7#[cfg(target_os = "linux")]
8use std::ffi::CString;
9use std::fmt;
10#[cfg(feature = "extended-value")]
11use std::path::Path;
12use std::sync::atomic;
13use std::sync::{Arc, OnceLock};
14use std::time::Duration;
15
16pub const SERVICE_CONFIG_VERSION: u16 = 4;
17
18pub const SERVICE_PAYLOAD_PING: u8 = 0;
19pub const SERVICE_PAYLOAD_INITIAL: u8 = 1;
20
21#[allow(dead_code)]
22static FIPS_LOADED: OnceLock<()> = OnceLock::new();
23
24#[cfg(any(feature = "openssl-vendored", not(feature = "fips")))]
25pub fn enable_fips() -> EResult<()> {
26 Err(Error::failed(
27 "FIPS can not be enabled, consider using a native OS distribution",
28 ))
29}
30
31#[cfg(all(not(feature = "openssl-vendored"), feature = "fips"))]
32pub fn enable_fips() -> EResult<()> {
33 {
34 FIPS_LOADED
35 .set(())
36 .map_err(|_| Error::core("FIPS provided already loaded"))?;
37 std::mem::forget(openssl::provider::Provider::load(None, "fips")?);
38 }
39 Ok(())
40}
41
42pub struct Registry {
43 id: String,
44 rpc: Arc<RpcClient>,
45}
46
47impl Registry {
48 #[inline]
49 pub async fn key_set<V>(&self, key: &str, value: V) -> EResult<Value>
50 where
51 V: Serialize,
52 {
53 registry::key_set(
54 ®istry::format_svc_data_subkey(&self.id),
55 key,
56 value,
57 &self.rpc,
58 )
59 .await
60 }
61 #[inline]
62 pub async fn key_get(&self, key: &str) -> EResult<Value> {
63 registry::key_get(®istry::format_svc_data_subkey(&self.id), key, &self.rpc).await
64 }
65 #[inline]
66 pub async fn key_userdata_get(&self, key: &str) -> EResult<Value> {
67 registry::key_get(registry::R_USER_DATA, key, &self.rpc).await
68 }
69 #[inline]
70 pub async fn key_increment(&self, key: &str) -> EResult<i64> {
71 registry::key_increment(®istry::format_svc_data_subkey(&self.id), key, &self.rpc).await
72 }
73
74 #[inline]
75 pub async fn key_decrement(&self, key: &str) -> EResult<i64> {
76 registry::key_decrement(®istry::format_svc_data_subkey(&self.id), key, &self.rpc).await
77 }
78 #[inline]
79 pub async fn key_get_recursive(&self, key: &str) -> EResult<Vec<(String, Value)>> {
80 registry::key_get_recursive(®istry::format_svc_data_subkey(&self.id), key, &self.rpc)
81 .await
82 }
83 #[inline]
84 pub async fn key_delete(&self, key: &str) -> EResult<Value> {
85 registry::key_delete(®istry::format_svc_data_subkey(&self.id), key, &self.rpc).await
86 }
87 #[inline]
88 pub async fn key_delete_recursive(&self, key: &str) -> EResult<Value> {
89 registry::key_delete_recursive(®istry::format_svc_data_subkey(&self.id), key, &self.rpc)
90 .await
91 }
92}
93
94#[inline]
95fn default_workers() -> u32 {
96 1
97}
98
99#[derive(Default, Clone, Debug, Serialize, Deserialize)]
100pub struct RealtimeConfig {
101 #[serde(default)]
102 pub priority: Option<i32>,
103 #[serde(default)]
104 pub cpu_ids: Vec<usize>,
105 #[serde(default)]
106 pub prealloc_heap: Option<usize>,
107}
108
109fn default_restart_delay() -> Duration {
110 Duration::from_secs(2)
111}
112
113#[derive(Debug, Serialize, Deserialize)]
115pub struct Initial {
116 #[serde(rename = "version")]
117 config_version: u16,
118 system_name: String,
119 id: String,
120 command: String,
121 #[serde(default)]
122 env: HashMap<String, String>,
123 #[serde(default)]
124 prepare_command: Option<String>,
125 data_path: String,
126 timeout: Timeout,
127 core: CoreInfo,
128 bus: BusConfig,
129 #[serde(default)]
130 realtime: RealtimeConfig,
131 #[serde(default)]
132 config: Option<Value>,
133 #[serde(default = "default_workers")]
134 workers: u32,
135 #[serde(default)]
136 user: Option<String>,
137 #[serde(default)]
138 react_to_fail: bool,
139 #[serde(
140 serialize_with = "crate::tools::serialize_atomic_bool",
141 deserialize_with = "crate::tools::deserialize_atomic_bool"
142 )]
143 fail_mode: atomic::AtomicBool,
144 #[serde(default)]
145 fips: bool,
146 #[serde(default)]
147 call_tracing: bool,
148 #[serde(
149 default = "default_restart_delay",
150 deserialize_with = "crate::tools::de_float_as_duration",
151 serialize_with = "crate::tools::serialize_duration_as_f64"
152 )]
153 restart_delay: Duration,
154}
155
156impl Initial {
157 #[allow(clippy::too_many_arguments)]
158 pub fn new(
159 id: &str,
160 system_name: &str,
161 command: &str,
162 prepare_command: Option<&str>,
163 data_path: &str,
164 timeout: &Timeout,
165 core_info: CoreInfo,
166 bus: BusConfig,
167 config: Option<&Value>,
168 workers: u32,
169 user: Option<&str>,
170 react_to_fail: bool,
171 fips: bool,
172 call_tracing: bool,
173 ) -> Self {
174 Self {
175 config_version: SERVICE_CONFIG_VERSION,
176 system_name: system_name.to_owned(),
177 id: id.to_owned(),
178 command: command.to_owned(),
179 env: <_>::default(),
180 prepare_command: prepare_command.map(ToOwned::to_owned),
181 data_path: data_path.to_owned(),
182 timeout: timeout.clone(),
183 core: core_info,
184 bus,
185 realtime: <_>::default(),
186 config: config.cloned(),
187 workers,
188 user: user.map(ToOwned::to_owned),
189 react_to_fail,
190 fail_mode: atomic::AtomicBool::new(false),
191 fips,
192 call_tracing,
193 restart_delay: default_restart_delay(),
194 }
195 }
196 pub fn with_realtime(mut self, realtime: RealtimeConfig) -> Self {
197 self.realtime = realtime;
198 self
199 }
200 pub fn with_restart_delay(mut self, delay: Duration) -> Self {
201 self.restart_delay = delay;
202 self
203 }
204 pub fn with_env(mut self, env: HashMap<String, String>) -> Self {
205 self.env = env;
206 self
207 }
208 #[inline]
209 pub fn init(&self) -> EResult<()> {
210 #[cfg(any(feature = "openssl-vendored", feature = "fips"))]
211 if self.fips {
212 return Err(Error::not_implemented(
213 "no FIPS 140 support, disable FIPS or switch to native package",
214 ));
215 }
216 if self.fips {
217 enable_fips()?;
218 }
219 Ok(())
220 }
221 #[inline]
222 pub fn config_version(&self) -> u16 {
223 self.config_version
224 }
225 #[inline]
226 pub fn system_name(&self) -> &str {
227 &self.system_name
228 }
229 #[inline]
230 pub fn id(&self) -> &str {
231 &self.id
232 }
233 #[inline]
234 pub fn command(&self) -> &str {
235 &self.command
236 }
237 pub fn realtime(&self) -> &RealtimeConfig {
238 &self.realtime
239 }
240 pub fn env(&self) -> &HashMap<String, String> {
241 &self.env
242 }
243 #[inline]
244 pub fn prepare_command(&self) -> Option<&str> {
245 self.prepare_command.as_deref()
246 }
247 #[inline]
248 pub fn user(&self) -> Option<&str> {
249 self.user.as_deref()
250 }
251 pub fn set_user(&mut self, user: Option<&str>) {
252 self.user = user.map(ToOwned::to_owned);
253 }
254 pub fn set_id(&mut self, id: &str) {
255 id.clone_into(&mut self.id);
256 }
257 #[inline]
258 pub fn data_path(&self) -> Option<&str> {
259 if let Some(ref user) = self.user
260 && user == "nobody"
261 {
262 return None;
263 }
264 Some(&self.data_path)
265 }
266 #[inline]
267 pub fn planned_data_path(&self) -> &str {
268 &self.data_path
269 }
270 pub fn set_data_path(&mut self, path: &str) {
271 path.clone_into(&mut self.data_path);
272 }
273 #[inline]
274 pub fn timeout(&self) -> Duration {
275 self.timeout
276 .default
277 .map_or(crate::DEFAULT_TIMEOUT, Duration::from_secs_f64)
278 }
279 #[inline]
280 pub fn startup_timeout(&self) -> Duration {
281 self.timeout
282 .startup
283 .map_or_else(|| self.timeout(), Duration::from_secs_f64)
284 }
285 #[inline]
286 pub fn shutdown_timeout(&self) -> Duration {
287 self.timeout
288 .shutdown
289 .map_or_else(|| self.timeout(), Duration::from_secs_f64)
290 }
291 #[inline]
292 pub fn bus_timeout(&self) -> Duration {
293 self.bus
294 .timeout
295 .map_or_else(|| self.timeout(), Duration::from_secs_f64)
296 }
297 #[inline]
298 pub fn eva_build(&self) -> u64 {
299 self.core.build
300 }
301 #[inline]
302 pub fn eva_version(&self) -> &str {
303 &self.core.version
304 }
305 #[inline]
306 pub fn eapi_version(&self) -> u16 {
307 self.core.eapi_verion
308 }
309 #[inline]
310 pub fn eva_dir(&self) -> &str {
311 &self.core.path
312 }
313 #[inline]
314 pub fn eva_log_level(&self) -> u8 {
315 self.core.log_level
316 }
317 #[inline]
318 pub fn core_active(&self) -> bool {
319 self.core.active
320 }
321 #[inline]
322 pub fn call_tracing(&self) -> bool {
323 self.call_tracing
324 }
325 #[inline]
326 pub fn restart_delay(&self) -> Duration {
327 self.restart_delay
328 }
329 #[inline]
330 pub fn eva_log_level_filter(&self) -> log::LevelFilter {
331 match self.core.log_level {
332 crate::LOG_LEVEL_TRACE => log::LevelFilter::Trace,
333 crate::LOG_LEVEL_DEBUG => log::LevelFilter::Debug,
334 crate::LOG_LEVEL_WARN => log::LevelFilter::Warn,
335 crate::LOG_LEVEL_ERROR => log::LevelFilter::Error,
336 crate::LOG_LEVEL_OFF => log::LevelFilter::Off,
337 _ => log::LevelFilter::Info,
338 }
339 }
340 #[inline]
341 pub fn bus_config(&self) -> EResult<busrt::ipc::Config> {
342 if self.bus.tp == "native" {
343 let mut bus_config = busrt::ipc::Config::new(&self.bus.path, &self.id)
344 .buf_size(self.bus.buf_size)
345 .buf_ttl(Duration::from_micros(self.bus.buf_ttl))
346 .queue_size(self.bus.queue_size)
347 .timeout(self.bus_timeout());
348 if let Some(ref token) = self.bus.token {
349 bus_config = bus_config.token(token);
350 }
351 Ok(bus_config)
352 } else {
353 Err(Error::not_implemented(format!(
354 "bus type {} is not supported",
355 self.bus.tp
356 )))
357 }
358 }
359 #[inline]
360 pub fn bus_config_for_sub(&self, sub_id: &str) -> EResult<busrt::ipc::Config> {
361 if self.bus.tp == "native" {
362 Ok(
363 busrt::ipc::Config::new(&self.bus.path, &format!("{}::{}", self.id, sub_id))
364 .buf_size(self.bus.buf_size)
365 .buf_ttl(Duration::from_micros(self.bus.buf_ttl))
366 .queue_size(self.bus.queue_size)
367 .timeout(self.bus_timeout()),
368 )
369 } else {
370 Err(Error::not_implemented(format!(
371 "bus type {} is not supported",
372 self.bus.tp
373 )))
374 }
375 }
376 pub fn set_bus_path(&mut self, path: &str) {
377 path.clone_into(&mut self.bus.path);
378 }
379 #[inline]
380 pub fn bus_path(&self) -> &str {
381 &self.bus.path
382 }
383 #[inline]
384 pub fn config(&self) -> Option<&Value> {
385 self.config.as_ref()
386 }
387 #[cfg(feature = "extended-value")]
388 #[inline]
389 pub async fn extend_config(&mut self, timeout: Duration, base: &Path) -> EResult<()> {
390 self.config = if let Some(config) = self.config.take() {
391 Some(config.extend(timeout, base).await?)
392 } else {
393 None
394 };
395 Ok(())
396 }
397 #[inline]
398 pub fn workers(&self) -> u32 {
399 self.workers
400 }
401 #[inline]
402 pub fn bus_queue_size(&self) -> usize {
403 self.bus.queue_size
404 }
405 #[inline]
406 pub fn take_config(&mut self) -> Option<Value> {
407 self.config.take()
408 }
409 #[inline]
410 pub async fn init_rpc<R>(&self, handlers: R) -> EResult<Arc<RpcClient>>
411 where
412 R: RpcHandlers + Send + Sync + 'static,
413 {
414 self.init_rpc_opts(handlers, rpc::Options::default()).await
415 }
416 #[inline]
417 pub async fn init_rpc_blocking<R>(&self, handlers: R) -> EResult<Arc<RpcClient>>
418 where
419 R: RpcHandlers + Send + Sync + 'static,
420 {
421 self.init_rpc_opts(
422 handlers,
423 rpc::Options::new()
424 .blocking_notifications()
425 .blocking_frames(),
426 )
427 .await
428 }
429 #[inline]
430 pub async fn init_rpc_blocking_with_secondary<R>(
431 &self,
432 handlers: R,
433 ) -> EResult<(Arc<RpcClient>, Arc<RpcClient>)>
434 where
435 R: RpcHandlers + Send + Sync + 'static,
436 {
437 let bus = self.init_bus_client().await?;
438 let bus_secondary = bus.register_secondary().await?;
439 let opts = rpc::Options::new()
440 .blocking_notifications()
441 .blocking_frames();
442 let rpc = Arc::new(RpcClient::create(bus, handlers, opts.clone()));
443 let rpc_secondary = Arc::new(RpcClient::create0(bus_secondary, opts));
444 Ok((rpc, rpc_secondary))
445 }
446 pub async fn init_rpc_opts<R>(&self, handlers: R, opts: rpc::Options) -> EResult<Arc<RpcClient>>
447 where
448 R: RpcHandlers + Send + Sync + 'static,
449 {
450 let bus = self.init_bus_client().await?;
451 let rpc = RpcClient::create(bus, handlers, opts);
452 Ok(Arc::new(rpc))
453 }
454 pub async fn init_bus_client(&self) -> EResult<busrt::ipc::Client> {
455 let bus = tokio::time::timeout(
456 self.bus_timeout(),
457 busrt::ipc::Client::connect(&self.bus_config()?),
458 )
459 .await??;
460 Ok(bus)
461 }
462 pub async fn init_bus_client_sub(&self, sub_id: &str) -> EResult<busrt::ipc::Client> {
463 let bus = tokio::time::timeout(
464 self.bus_timeout(),
465 busrt::ipc::Client::connect(&self.bus_config_for_sub(sub_id)?),
466 )
467 .await??;
468 Ok(bus)
469 }
470 #[inline]
471 pub fn init_registry(&self, rpc: &Arc<RpcClient>) -> Registry {
472 Registry {
473 id: self.id.clone(),
474 rpc: rpc.clone(),
475 }
476 }
477 #[inline]
478 pub fn can_rtf(&self) -> bool {
479 self.react_to_fail
480 }
481 #[inline]
482 pub fn is_mode_normal(&self) -> bool {
483 !self.fail_mode.load(atomic::Ordering::SeqCst)
484 }
485 #[inline]
486 pub fn is_mode_rtf(&self) -> bool {
487 self.fail_mode.load(atomic::Ordering::SeqCst)
488 }
489 #[inline]
490 pub fn set_fail_mode(&self, mode: bool) {
491 self.fail_mode.store(mode, atomic::Ordering::SeqCst);
492 }
493 #[cfg(target_os = "linux")]
494 #[inline]
495 pub fn drop_privileges(&self) -> EResult<()> {
496 if let Some(ref user) = self.user {
497 if !user.is_empty() {
498 let u = get_system_user(user)?;
499 if nix::unistd::getuid() != u.uid {
500 let c_user = CString::new(user.as_str()).map_err(|e| {
501 Error::failed(format!("Failed to parse user {}: {}", user, e))
502 })?;
503
504 let groups = nix::unistd::getgrouplist(&c_user, u.gid).map_err(|e| {
505 Error::failed(format!("Failed to get groups for user {}: {}", user, e))
506 })?;
507 nix::unistd::setgroups(&groups).map_err(|e| {
508 Error::failed(format!(
509 "Failed to switch the process groups for user {}: {}",
510 user, e
511 ))
512 })?;
513 nix::unistd::setgid(u.gid).map_err(|e| {
514 Error::failed(format!(
515 "Failed to switch the process group for user {}: {}",
516 user, e
517 ))
518 })?;
519 nix::unistd::setuid(u.uid).map_err(|e| {
520 Error::failed(format!(
521 "Failed to switch the process user to {}: {}",
522 user, e
523 ))
524 })?;
525 }
526 }
527 }
528 Ok(())
529 }
530
531 #[cfg(not(target_os = "linux"))]
532 #[inline]
533 pub fn drop_privileges(&self) -> EResult<()> {
534 eprintln!("WARNING privileges not dropped");
535 Ok(())
536 }
537 pub fn into_legacy_compat(mut self) -> Self {
538 self.data_path = self.data_path().unwrap_or_default().to_owned();
539 let user = self.user.take().unwrap_or_default();
540 self.user.replace(user);
541 let timeout = self
542 .timeout
543 .default
544 .unwrap_or(crate::DEFAULT_TIMEOUT.as_secs_f64());
545 self.timeout.default.replace(timeout);
546 if self.timeout.startup.is_none() {
547 self.timeout.startup.replace(timeout);
548 }
549 if self.timeout.shutdown.is_none() {
550 self.timeout.shutdown.replace(timeout);
551 }
552 let config = self
553 .take_config()
554 .unwrap_or_else(|| Value::Map(<_>::default()));
555 self.config.replace(config);
556 self
557 }
558}
559
560#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
561pub fn get_system_user(user: &str) -> EResult<nix::unistd::User> {
562 let u = nix::unistd::User::from_name(user)
563 .map_err(|e| Error::failed(format!("failed to get the system user {}: {}", user, e)))?
564 .ok_or_else(|| Error::failed(format!("Failed to locate the system user {}", user)))?;
565 Ok(u)
566}
567
568#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
569pub fn get_system_group(group: &str) -> EResult<nix::unistd::Group> {
570 let g = nix::unistd::Group::from_name(group)
571 .map_err(|e| Error::failed(format!("failed to get the system group {}: {}", group, e)))?
572 .ok_or_else(|| Error::failed(format!("Failed to locate the system group {}", group)))?;
573 Ok(g)
574}
575
576#[derive(Debug, Serialize, Deserialize, Clone, Default)]
577pub struct Timeout {
578 startup: Option<f64>,
579 shutdown: Option<f64>,
580 default: Option<f64>,
581}
582
583impl Timeout {
584 pub fn offer(&mut self, timeout: f64) {
585 if self.startup.is_none() {
586 self.startup.replace(timeout);
587 }
588 if self.shutdown.is_none() {
589 self.shutdown.replace(timeout);
590 }
591 if self.default.is_none() {
592 self.default.replace(timeout);
593 }
594 }
595 pub fn get(&self) -> Option<Duration> {
596 self.default.map(Duration::from_secs_f64)
597 }
598 pub fn startup(&self) -> Option<Duration> {
599 self.startup.map(Duration::from_secs_f64)
600 }
601 pub fn shutdown(&self) -> Option<Duration> {
602 self.shutdown.map(Duration::from_secs_f64)
603 }
604}
605
606#[derive(Debug, Serialize, Deserialize)]
607pub struct CoreInfo {
608 build: u64,
609 version: String,
610 eapi_verion: u16,
611 path: String,
612 log_level: u8,
613 active: bool,
614}
615
616impl CoreInfo {
617 pub fn new(
618 build: u64,
619 version: &str,
620 eapi_verion: u16,
621 path: &str,
622 log_level: u8,
623 active: bool,
624 ) -> Self {
625 Self {
626 build,
627 version: version.to_owned(),
628 eapi_verion,
629 path: path.to_owned(),
630 log_level,
631 active,
632 }
633 }
634}
635
636#[inline]
637fn default_bus_type() -> String {
638 "native".to_owned()
639}
640
641#[inline]
642fn default_bus_buf_size() -> usize {
643 busrt::DEFAULT_BUF_SIZE
644}
645
646#[allow(clippy::cast_possible_truncation)]
647#[inline]
648fn default_bus_buf_ttl() -> u64 {
649 busrt::DEFAULT_BUF_TTL.as_micros() as u64
650}
651
652#[inline]
653fn default_bus_queue_size() -> usize {
654 busrt::DEFAULT_QUEUE_SIZE
655}
656
657#[derive(Debug, Clone, Deserialize, Serialize)]
658pub struct BusConfig {
659 #[serde(rename = "type", default = "default_bus_type")]
660 tp: String,
661 path: String,
662 timeout: Option<f64>,
663 #[serde(default = "default_bus_buf_size")]
664 buf_size: usize,
665 #[serde(default = "default_bus_buf_ttl")]
666 buf_ttl: u64, #[serde(default = "default_bus_queue_size")]
668 queue_size: usize,
669 #[serde(rename = "ping_interval", skip_serializing, default)]
671 _ping_interval: f64,
672 token: Option<String>,
673}
674
675impl BusConfig {
676 pub fn path(&self) -> &str {
677 &self.path
678 }
679 pub fn set_path(&mut self, path: &str) {
680 path.clone_into(&mut self.path);
681 }
682 pub fn offer_timeout(&mut self, timeout: f64) {
683 if self.timeout.is_none() {
684 self.timeout.replace(timeout);
685 }
686 }
687}
688
689#[derive(Debug, Clone, Serialize, Deserialize)]
690pub struct MethodParamInfo {
691 #[serde(default)]
692 pub required: bool,
693}
694
695#[derive(Debug, Clone, Serialize, Deserialize)]
696pub struct MethodInfo {
697 #[serde(default)]
698 pub description: String,
699 pub params: HashMap<String, MethodParamInfo>,
700}
701
702pub struct ServiceMethod {
704 pub name: String,
705 pub description: String,
706 pub params: HashMap<String, MethodParamInfo>,
707}
708
709impl ServiceMethod {
710 pub fn new(name: &str) -> Self {
711 Self {
712 name: name.to_owned(),
713 description: String::new(),
714 params: <_>::default(),
715 }
716 }
717 pub fn description(mut self, desc: &str) -> Self {
718 desc.clone_into(&mut self.description);
719 self
720 }
721 pub fn required(mut self, name: &str) -> Self {
722 self.params
723 .insert(name.to_owned(), MethodParamInfo { required: true });
724 self
725 }
726 pub fn optional(mut self, name: &str) -> Self {
727 self.params
728 .insert(name.to_owned(), MethodParamInfo { required: false });
729 self
730 }
731}
732
733#[derive(Serialize, Deserialize, Debug, Clone)]
735pub struct ServiceInfo {
736 #[serde(default)]
737 pub author: String,
738 #[serde(default)]
739 pub version: String,
740 #[serde(default)]
741 pub description: String,
742 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
743 pub methods: HashMap<String, MethodInfo>,
744}
745
746impl ServiceInfo {
747 pub fn new(author: &str, version: &str, description: &str) -> Self {
748 Self {
749 author: author.to_owned(),
750 version: version.to_owned(),
751 description: description.to_owned(),
752 methods: <_>::default(),
753 }
754 }
755 #[inline]
756 pub fn add_method(&mut self, method: ServiceMethod) {
757 self.methods.insert(
758 method.name,
759 MethodInfo {
760 description: method.description,
761 params: method.params,
762 },
763 );
764 }
765}
766
767#[derive(Serialize, Deserialize)]
769pub struct ServiceStatusBroadcastEvent {
770 pub status: ServiceStatusBroadcast,
771}
772
773impl ServiceStatusBroadcastEvent {
774 #[inline]
775 pub fn ready() -> Self {
776 Self {
777 status: ServiceStatusBroadcast::Ready,
778 }
779 }
780 #[inline]
781 pub fn terminating() -> Self {
782 Self {
783 status: ServiceStatusBroadcast::Terminating,
784 }
785 }
786}
787
788#[derive(Serialize, Deserialize)]
790#[serde(rename_all = "lowercase")]
791#[repr(u8)]
792pub enum ServiceStatusBroadcast {
793 Starting = 0,
794 Ready = 1,
795 Terminating = 0xef,
796 Unknown = 0xff,
797}
798
799impl fmt::Display for ServiceStatusBroadcast {
800 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
801 write!(
802 f,
803 "{}",
804 match self {
805 ServiceStatusBroadcast::Starting => "starting",
806 ServiceStatusBroadcast::Ready => "ready",
807 ServiceStatusBroadcast::Terminating => "terminating",
808 ServiceStatusBroadcast::Unknown => "unknown",
809 }
810 )
811 }
812}