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