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