eva_common/
services.rs

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            &registry::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(&registry::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(&registry::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(&registry::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(&registry::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(&registry::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(&registry::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/// Initial properties for services
125#[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, // microseconds
664    #[serde(default = "default_bus_queue_size")]
665    queue_size: usize,
666    // deprecated field, as BUS/RT RPC uses timeout as a ping interval
667    #[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
698/// info-structure only, can be used by clients for auto-completion
699pub 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/// Returned by all services on "info" RPC command
730#[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/// Used by services to announce their status (for "*")
764#[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/// Used by services and the core to notify about its state
785#[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}