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