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 Ok(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 } else {
349 Err(Error::not_implemented(format!(
350 "bus type {} is not supported",
351 self.bus.tp
352 )))
353 }
354 }
355 #[inline]
356 pub fn bus_config_for_sub(&self, sub_id: &str) -> EResult<busrt::ipc::Config> {
357 if self.bus.tp == "native" {
358 Ok(
359 busrt::ipc::Config::new(&self.bus.path, &format!("{}::{}", self.id, sub_id))
360 .buf_size(self.bus.buf_size)
361 .buf_ttl(Duration::from_micros(self.bus.buf_ttl))
362 .queue_size(self.bus.queue_size)
363 .timeout(self.bus_timeout()),
364 )
365 } else {
366 Err(Error::not_implemented(format!(
367 "bus type {} is not supported",
368 self.bus.tp
369 )))
370 }
371 }
372 pub fn set_bus_path(&mut self, path: &str) {
373 path.clone_into(&mut self.bus.path);
374 }
375 #[inline]
376 pub fn bus_path(&self) -> &str {
377 &self.bus.path
378 }
379 #[inline]
380 pub fn config(&self) -> Option<&Value> {
381 self.config.as_ref()
382 }
383 #[cfg(feature = "extended-value")]
384 #[inline]
385 pub async fn extend_config(&mut self, timeout: Duration, base: &Path) -> EResult<()> {
386 self.config = if let Some(config) = self.config.take() {
387 Some(config.extend(timeout, base).await?)
388 } else {
389 None
390 };
391 Ok(())
392 }
393 #[inline]
394 pub fn workers(&self) -> u32 {
395 self.workers
396 }
397 #[inline]
398 pub fn bus_queue_size(&self) -> usize {
399 self.bus.queue_size
400 }
401 #[inline]
402 pub fn take_config(&mut self) -> Option<Value> {
403 self.config.take()
404 }
405 #[inline]
406 pub async fn init_rpc<R>(&self, handlers: R) -> EResult<Arc<RpcClient>>
407 where
408 R: RpcHandlers + Send + Sync + 'static,
409 {
410 self.init_rpc_opts(handlers, rpc::Options::default()).await
411 }
412 #[inline]
413 pub async fn init_rpc_blocking<R>(&self, handlers: R) -> EResult<Arc<RpcClient>>
414 where
415 R: RpcHandlers + Send + Sync + 'static,
416 {
417 self.init_rpc_opts(
418 handlers,
419 rpc::Options::new()
420 .blocking_notifications()
421 .blocking_frames(),
422 )
423 .await
424 }
425 #[inline]
426 pub async fn init_rpc_blocking_with_secondary<R>(
427 &self,
428 handlers: R,
429 ) -> EResult<(Arc<RpcClient>, Arc<RpcClient>)>
430 where
431 R: RpcHandlers + Send + Sync + 'static,
432 {
433 let bus = self.init_bus_client().await?;
434 let bus_secondary = bus.register_secondary().await?;
435 let opts = rpc::Options::new()
436 .blocking_notifications()
437 .blocking_frames();
438 let rpc = Arc::new(RpcClient::create(bus, handlers, opts.clone()));
439 let rpc_secondary = Arc::new(RpcClient::create0(bus_secondary, opts));
440 Ok((rpc, rpc_secondary))
441 }
442 pub async fn init_rpc_opts<R>(&self, handlers: R, opts: rpc::Options) -> EResult<Arc<RpcClient>>
443 where
444 R: RpcHandlers + Send + Sync + 'static,
445 {
446 let bus = self.init_bus_client().await?;
447 let rpc = RpcClient::create(bus, handlers, opts);
448 Ok(Arc::new(rpc))
449 }
450 pub async fn init_bus_client(&self) -> EResult<busrt::ipc::Client> {
451 let bus = tokio::time::timeout(
452 self.bus_timeout(),
453 busrt::ipc::Client::connect(&self.bus_config()?),
454 )
455 .await??;
456 Ok(bus)
457 }
458 pub async fn init_bus_client_sub(&self, sub_id: &str) -> EResult<busrt::ipc::Client> {
459 let bus = tokio::time::timeout(
460 self.bus_timeout(),
461 busrt::ipc::Client::connect(&self.bus_config_for_sub(sub_id)?),
462 )
463 .await??;
464 Ok(bus)
465 }
466 #[inline]
467 pub fn init_registry(&self, rpc: &Arc<RpcClient>) -> Registry {
468 Registry {
469 id: self.id.clone(),
470 rpc: rpc.clone(),
471 }
472 }
473 #[inline]
474 pub fn can_rtf(&self) -> bool {
475 self.react_to_fail
476 }
477 #[inline]
478 pub fn is_mode_normal(&self) -> bool {
479 !self.fail_mode.load(atomic::Ordering::SeqCst)
480 }
481 #[inline]
482 pub fn is_mode_rtf(&self) -> bool {
483 self.fail_mode.load(atomic::Ordering::SeqCst)
484 }
485 #[inline]
486 pub fn set_fail_mode(&self, mode: bool) {
487 self.fail_mode.store(mode, atomic::Ordering::SeqCst);
488 }
489 #[cfg(target_os = "linux")]
490 #[inline]
491 pub fn drop_privileges(&self) -> EResult<()> {
492 if let Some(ref user) = self.user {
493 if !user.is_empty() {
494 let u = get_system_user(user)?;
495 if nix::unistd::getuid() != u.uid {
496 let c_user = CString::new(user.as_str()).map_err(|e| {
497 Error::failed(format!("Failed to parse user {}: {}", user, e))
498 })?;
499
500 let groups = nix::unistd::getgrouplist(&c_user, u.gid).map_err(|e| {
501 Error::failed(format!("Failed to get groups for user {}: {}", user, e))
502 })?;
503 nix::unistd::setgroups(&groups).map_err(|e| {
504 Error::failed(format!(
505 "Failed to switch the process groups for user {}: {}",
506 user, e
507 ))
508 })?;
509 nix::unistd::setgid(u.gid).map_err(|e| {
510 Error::failed(format!(
511 "Failed to switch the process group for user {}: {}",
512 user, e
513 ))
514 })?;
515 nix::unistd::setuid(u.uid).map_err(|e| {
516 Error::failed(format!(
517 "Failed to switch the process user to {}: {}",
518 user, e
519 ))
520 })?;
521 }
522 }
523 }
524 Ok(())
525 }
526
527 #[cfg(not(target_os = "linux"))]
528 #[inline]
529 pub fn drop_privileges(&self) -> EResult<()> {
530 eprintln!("WARNING privileges not dropped");
531 Ok(())
532 }
533 pub fn into_legacy_compat(mut self) -> Self {
534 self.data_path = self.data_path().unwrap_or_default().to_owned();
535 let user = self.user.take().unwrap_or_default();
536 self.user.replace(user);
537 let timeout = self
538 .timeout
539 .default
540 .unwrap_or(crate::DEFAULT_TIMEOUT.as_secs_f64());
541 self.timeout.default.replace(timeout);
542 if self.timeout.startup.is_none() {
543 self.timeout.startup.replace(timeout);
544 }
545 if self.timeout.shutdown.is_none() {
546 self.timeout.shutdown.replace(timeout);
547 }
548 let config = self
549 .take_config()
550 .unwrap_or_else(|| Value::Map(<_>::default()));
551 self.config.replace(config);
552 self
553 }
554}
555
556#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
557pub fn get_system_user(user: &str) -> EResult<nix::unistd::User> {
558 let u = nix::unistd::User::from_name(user)
559 .map_err(|e| Error::failed(format!("failed to get the system user {}: {}", user, e)))?
560 .ok_or_else(|| Error::failed(format!("Failed to locate the system user {}", user)))?;
561 Ok(u)
562}
563
564#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
565pub fn get_system_group(group: &str) -> EResult<nix::unistd::Group> {
566 let g = nix::unistd::Group::from_name(group)
567 .map_err(|e| Error::failed(format!("failed to get the system group {}: {}", group, e)))?
568 .ok_or_else(|| Error::failed(format!("Failed to locate the system group {}", group)))?;
569 Ok(g)
570}
571
572#[derive(Debug, Serialize, Deserialize, Clone, Default)]
573pub struct Timeout {
574 startup: Option<f64>,
575 shutdown: Option<f64>,
576 default: Option<f64>,
577}
578
579impl Timeout {
580 pub fn offer(&mut self, timeout: f64) {
581 if self.startup.is_none() {
582 self.startup.replace(timeout);
583 }
584 if self.shutdown.is_none() {
585 self.shutdown.replace(timeout);
586 }
587 if self.default.is_none() {
588 self.default.replace(timeout);
589 }
590 }
591 pub fn get(&self) -> Option<Duration> {
592 self.default.map(Duration::from_secs_f64)
593 }
594 pub fn startup(&self) -> Option<Duration> {
595 self.startup.map(Duration::from_secs_f64)
596 }
597 pub fn shutdown(&self) -> Option<Duration> {
598 self.shutdown.map(Duration::from_secs_f64)
599 }
600}
601
602#[derive(Debug, Serialize, Deserialize)]
603pub struct CoreInfo {
604 build: u64,
605 version: String,
606 eapi_verion: u16,
607 path: String,
608 log_level: u8,
609 active: bool,
610}
611
612impl CoreInfo {
613 pub fn new(
614 build: u64,
615 version: &str,
616 eapi_verion: u16,
617 path: &str,
618 log_level: u8,
619 active: bool,
620 ) -> Self {
621 Self {
622 build,
623 version: version.to_owned(),
624 eapi_verion,
625 path: path.to_owned(),
626 log_level,
627 active,
628 }
629 }
630}
631
632#[inline]
633fn default_bus_type() -> String {
634 "native".to_owned()
635}
636
637#[inline]
638fn default_bus_buf_size() -> usize {
639 busrt::DEFAULT_BUF_SIZE
640}
641
642#[allow(clippy::cast_possible_truncation)]
643#[inline]
644fn default_bus_buf_ttl() -> u64 {
645 busrt::DEFAULT_BUF_TTL.as_micros() as u64
646}
647
648#[inline]
649fn default_bus_queue_size() -> usize {
650 busrt::DEFAULT_QUEUE_SIZE
651}
652
653#[derive(Debug, Clone, Deserialize, Serialize)]
654pub struct BusConfig {
655 #[serde(rename = "type", default = "default_bus_type")]
656 tp: String,
657 path: String,
658 timeout: Option<f64>,
659 #[serde(default = "default_bus_buf_size")]
660 buf_size: usize,
661 #[serde(default = "default_bus_buf_ttl")]
662 buf_ttl: u64, #[serde(default = "default_bus_queue_size")]
664 queue_size: usize,
665 #[serde(rename = "ping_interval", skip_serializing, default)]
667 _ping_interval: f64,
668}
669
670impl BusConfig {
671 pub fn path(&self) -> &str {
672 &self.path
673 }
674 pub fn set_path(&mut self, path: &str) {
675 path.clone_into(&mut self.path);
676 }
677 pub fn offer_timeout(&mut self, timeout: f64) {
678 if self.timeout.is_none() {
679 self.timeout.replace(timeout);
680 }
681 }
682}
683
684#[derive(Debug, Clone, Serialize, Deserialize)]
685pub struct MethodParamInfo {
686 #[serde(default)]
687 pub required: bool,
688}
689
690#[derive(Debug, Clone, Serialize, Deserialize)]
691pub struct MethodInfo {
692 #[serde(default)]
693 pub description: String,
694 pub params: HashMap<String, MethodParamInfo>,
695}
696
697pub struct ServiceMethod {
699 pub name: String,
700 pub description: String,
701 pub params: HashMap<String, MethodParamInfo>,
702}
703
704impl ServiceMethod {
705 pub fn new(name: &str) -> Self {
706 Self {
707 name: name.to_owned(),
708 description: String::new(),
709 params: <_>::default(),
710 }
711 }
712 pub fn description(mut self, desc: &str) -> Self {
713 desc.clone_into(&mut self.description);
714 self
715 }
716 pub fn required(mut self, name: &str) -> Self {
717 self.params
718 .insert(name.to_owned(), MethodParamInfo { required: true });
719 self
720 }
721 pub fn optional(mut self, name: &str) -> Self {
722 self.params
723 .insert(name.to_owned(), MethodParamInfo { required: false });
724 self
725 }
726}
727
728#[derive(Serialize, Deserialize, Debug, Clone)]
730pub struct ServiceInfo {
731 #[serde(default)]
732 pub author: String,
733 #[serde(default)]
734 pub version: String,
735 #[serde(default)]
736 pub description: String,
737 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
738 pub methods: HashMap<String, MethodInfo>,
739}
740
741impl ServiceInfo {
742 pub fn new(author: &str, version: &str, description: &str) -> Self {
743 Self {
744 author: author.to_owned(),
745 version: version.to_owned(),
746 description: description.to_owned(),
747 methods: <_>::default(),
748 }
749 }
750 #[inline]
751 pub fn add_method(&mut self, method: ServiceMethod) {
752 self.methods.insert(
753 method.name,
754 MethodInfo {
755 description: method.description,
756 params: method.params,
757 },
758 );
759 }
760}
761
762#[derive(Serialize, Deserialize)]
764pub struct ServiceStatusBroadcastEvent {
765 pub status: ServiceStatusBroadcast,
766}
767
768impl ServiceStatusBroadcastEvent {
769 #[inline]
770 pub fn ready() -> Self {
771 Self {
772 status: ServiceStatusBroadcast::Ready,
773 }
774 }
775 #[inline]
776 pub fn terminating() -> Self {
777 Self {
778 status: ServiceStatusBroadcast::Terminating,
779 }
780 }
781}
782
783#[derive(Serialize, Deserialize)]
785#[serde(rename_all = "lowercase")]
786#[repr(u8)]
787pub enum ServiceStatusBroadcast {
788 Starting = 0,
789 Ready = 1,
790 Terminating = 0xef,
791 Unknown = 0xff,
792}
793
794impl fmt::Display for ServiceStatusBroadcast {
795 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
796 write!(
797 f,
798 "{}",
799 match self {
800 ServiceStatusBroadcast::Starting => "starting",
801 ServiceStatusBroadcast::Ready => "ready",
802 ServiceStatusBroadcast::Terminating => "terminating",
803 ServiceStatusBroadcast::Unknown => "unknown",
804 }
805 )
806 }
807}