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    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, // microseconds
674    #[serde(default = "default_bus_queue_size")]
675    queue_size: usize,
676    // deprecated field, as BUS/RT RPC uses timeout as a ping interval
677    #[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
708/// info-structure only, can be used by clients for auto-completion
709pub 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/// Returned by all services on "info" RPC command
740#[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/// Used by services to announce their status (for "*")
774#[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/// Used by services and the core to notify about its state
795#[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}