eva_common/
services.rs

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