Skip to main content

pubhubs/servers/config/
core.rs

1//! Configuration (files)
2use core::fmt::Debug;
3use std::ops::{Deref, DerefMut};
4use std::path::{Path, PathBuf};
5
6use anyhow::{Context as _, Result};
7use url::Url;
8
9use crate::misc::serde_ext::bytes_wrapper::B64UU;
10use crate::servers::{for_all_servers, server::Server as _};
11use crate::{
12    api::{self},
13    attr,
14    common::elgamal,
15    hub,
16    misc::{jwt, serde_ext, time_ext},
17    servers::yivi,
18};
19
20use super::host_aliases::{HostAliases, UrlPwa};
21use super::log::LogConfig;
22
23/// Configuration for one, or several, of the PubHubs servers
24///
25/// Also used for the `pubhubs admin` cli command.  In that case only `phc_url` needs to be set.
26#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
27#[serde(deny_unknown_fields)]
28pub struct Config {
29    /// URL of the PubHubs Central server.
30    ///
31    /// Any information on the other servers that can be stored at PHC is stored at PHC.
32    pub phc_url: UrlPwa,
33
34    /// Configure logging.  Overwrites what's passed through `RUST_LOG`.
35    #[serde(default)]
36    pub log: Option<LogConfig>,
37
38    #[serde(skip)]
39    pub(crate) preparation_state: PreparationState,
40
41    /// Specify abbreviations for an IP address that are only valid in this configuration file.
42    ///
43    /// Any [UrlPwa] that contains one of the aliases as host name exactly (so no subdomain)
44    /// will be modified to have as host name the associated IP address.
45    #[serde(default)]
46    pub host_aliases: HostAliases,
47
48    /// Path with respect to which relative paths are interpretted.
49    #[serde(default)]
50    pub wd: PathBuf,
51
52    /// Configuration to run PubHubs Central
53    pub phc: Option<ServerConfig<phc::ExtraConfig>>,
54
55    /// Configuration to run the Transcryptor
56    pub transcryptor: Option<ServerConfig<transcryptor::ExtraConfig>>,
57
58    /// Configuration to run the Authentication Server
59    pub auths: Option<ServerConfig<auths::ExtraConfig>>,
60}
61
62/// Represents the level of preparation of a [Config] instance.
63#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
64pub(crate) enum PreparationState {
65    /// State after loading config file from disk.
66    #[default]
67    Unprepared,
68
69    /// [`Config::preliminary_prep`] has been called.
70    Preliminary,
71
72    /// [`Config`] is completely prepared after [`PrepareConfig::prepare`] has been called on it.
73    Complete,
74}
75
76/// Configuration for one server.  Derefs to `ServerSpecific`..
77#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
78#[serde(deny_unknown_fields)]
79pub struct ServerConfig<ServerSpecific> {
80    /// Port to bind this server to
81    #[serde(default)]
82    pub port: u16,
83
84    /// Ip addresses to bind to
85    #[serde(default = "default_ips")]
86    pub ips: Box<[std::net::IpAddr]>,
87
88    // Note: #[serde(skip)] does not consume the 'bind_to' key
89    /// Deprecated.
90    pub bind_to: serde_ext::Skip,
91
92    /// Random string used by this server to identify itself.  Randomly generated if not set.
93    /// May be set manually when multiple instances of the same server are used.
94    pub self_check_code: Option<String>,
95
96    /// Key used to sign JSON web tokens generated by this server.
97    /// If `None`, one is generated automatically (which is not suitable for production.)
98    ///
99    /// Generate using `cargo run tools generate signing-key`.
100    pub jwt_key: Option<api::SigningKey>,
101
102    /// Each server advertises an [`elgamal::PublicKey`] so that shared secrets may be established
103    /// with this server, and also encrypted messages may be sent to it.
104    ///
105    /// This key is also used to derive non-permanent secrets, like the the transcryptor's
106    /// encryption factor f_H for a hub H.
107    ///
108    /// Generate using `cargo run tools generate scalar`.
109    pub enc_key: Option<elgamal::PrivateKey>,
110
111    /// Key used by admin to sign requests for the admin endpoints.
112    /// If `None`, one is generated automatically and the private key is  printed to the log.
113    pub admin_key: Option<api::VerifyingKey>,
114
115    /// If the server needs an object store, use this one.
116    pub object_store: Option<ObjectStoreConfig>,
117
118    /// What version (if any) to claim this pubhubs binary is running.
119    /// Uses [`crate::servers::version()`] by default.  Should only be used for
120    /// troubleshooting/debugging.
121    #[serde(default = "default_version")]
122    pub version: Option<String>,
123
124    #[serde(flatten)]
125    /// Can be accessed via [`Deref`].
126    extra: ServerSpecific,
127}
128
129impl<X> Deref for ServerConfig<X> {
130    type Target = X;
131
132    fn deref(&self) -> &X {
133        &self.extra
134    }
135}
136
137impl<X> DerefMut for ServerConfig<X> {
138    fn deref_mut(&mut self) -> &mut X {
139        &mut self.extra
140    }
141}
142
143fn default_version() -> Option<String> {
144    crate::servers::version().map(str::to_string)
145}
146
147fn default_ips() -> Box<[std::net::IpAddr]> {
148    Box::new([
149        // Bind :: first, as this may already bind 0.0.0.0 too;
150        // if 0.0.0.0 is bound first a separate service will be started for ::.
151        std::net::Ipv6Addr::UNSPECIFIED.into(), // ::
152        std::net::Ipv4Addr::UNSPECIFIED.into(), // 0.0.0.0
153    ])
154}
155
156impl<'a, X> std::net::ToSocketAddrs for &'a ServerConfig<X> {
157    type Iter = Box<dyn Iterator<Item = std::net::SocketAddr> + 'a>;
158
159    fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
160        Ok(Box::new(self.ips.iter().map(|ip| (*ip, self.port).into())))
161    }
162}
163
164impl Config {
165    /// Loads [Config] from `path` and generates random values.
166    ///
167    /// Returns [None] if there's no file there.
168    pub fn load_from_path(path: &Path) -> Result<Option<Self>> {
169        // NOTE: the toml crate does not have a `from_reader` like `serde_json` does
170        let mut res: Self = toml::from_str(&match std::fs::read_to_string(path) {
171            Ok(contents) => contents,
172            Err(e) => match e.kind() {
173                std::io::ErrorKind::NotFound => return Ok(None),
174                _ => {
175                    return Err(e)
176                        .with_context(|| format!("could not open config file {}", path.display()));
177                }
178            },
179        })
180        .with_context(|| format!("could not parse config file {}", path.display()))?;
181
182        if res.wd.as_os_str().is_empty() {
183            res.wd = path
184                .canonicalize()
185                .with_context(|| format!("failed to canonicalize path {}", path.display()))?
186                .parent()
187                .expect("did not expect a configuration file without a parent directory")
188                .into();
189        }
190
191        if !res.wd.is_absolute() {
192            anyhow::bail!(
193                "if you specify a working directory (`wd` in {}) it must be absolute",
194                path.display()
195            );
196        }
197
198        res.preliminary_prep()?;
199
200        log::info!(
201            "loaded config file from {};  interpretting relative paths in {}",
202            path.display(),
203            res.wd.display()
204        );
205
206        Ok(Some(res))
207    }
208
209    pub fn preliminary_prep(&mut self) -> Result<()> {
210        anyhow::ensure!(
211            self.preparation_state == PreparationState::Unprepared,
212            "configuration already (partially) prepared: {:?}",
213            self.preparation_state
214        );
215
216        self.host_aliases.resolve_all()?;
217        self.host_aliases.dealias(&mut self.phc_url);
218
219        self.preparation_state = PreparationState::Preliminary;
220
221        Ok(())
222    }
223
224    /// Clones this configuration and strips out everything that's not needed to run
225    /// the specified server.  Also generated any random values not yet set.
226    pub async fn prepare_for(&self, server: crate::servers::Name) -> Result<Self> {
227        anyhow::ensure!(
228            self.preparation_state == PreparationState::Preliminary,
229            "configuration not in the correct preparation state"
230        );
231
232        // destruct to make sure we consider every field of Config
233        let Self {
234            log: _,
235            host_aliases,
236            phc_url,
237            wd,
238            preparation_state,
239            phc: _,
240            transcryptor: _,
241            auths: _,
242        } = self;
243
244        let mut config: Config = Config {
245            log: None, // <- servers do not read this
246            host_aliases: host_aliases.clone(),
247            phc_url: phc_url.clone(),
248            wd: wd.clone(),
249            preparation_state: *preparation_state,
250            phc: None,
251            transcryptor: None,
252            auths: None,
253        };
254
255        macro_rules! clone_only_server {
256            ($server:ident) => {
257                if crate::servers::$server::Server::NAME == server {
258                    assert!(self.$server.is_some());
259                    config.$server.clone_from(&self.$server);
260                }
261            };
262        }
263
264        for_all_servers!(clone_only_server);
265
266        config.prepare().await?;
267
268        Ok(config)
269    }
270
271    /// Prepares [`Config`] to be run; used by [`Config::prepare_for`].
272    pub async fn prepare(&mut self) -> anyhow::Result<()> {
273        let pcc = Pcc::new(actix_web::dev::Extensions::new());
274
275        PrepareConfig::prepare(self, pcc).await
276    }
277
278    /// Creates a new [Config] from the current one by updating a specific part
279    pub fn json_updated(&self, pointer: &str, new_value: serde_json::Value) -> Result<Self> {
280        let mut json_config: serde_json::Value =
281            serde_json::to_value(self).context("failed to serialize config")?;
282
283        let to_be_modified: &mut serde_json::Value =
284            json_config.pointer_mut(pointer).with_context(|| {
285                format!(
286                    "wanted to modify {pointer} of the configuration file, but that points nowhere"
287                )
288            })?;
289
290        to_be_modified.clone_from(&new_value);
291
292        let new_config: Config = serde_json::from_value(json_config).with_context(|| {
293            format!(
294                "wanted to change {pointer} of the configuration file to {new_value}, but the new configuration did not deserialize",
295            )
296        })?;
297
298        Ok(new_config)
299    }
300}
301
302#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
303pub struct ObjectStoreConfig {
304    /// E.g. "memory:///", or file:///some/path
305    ///
306    /// For a complete list, see:
307    ///
308    ///   <https://docs.rs/object_store/latest/object_store/enum.ObjectStoreScheme.html>
309    pub url: UrlPwa,
310
311    /// Additional options passed to the builder of the object store.
312    #[serde(default)]
313    pub options: std::collections::HashMap<String, String>,
314}
315
316impl Default for ObjectStoreConfig {
317    fn default() -> Self {
318        Self {
319            url: From::<Url>::from("memory:///".try_into().unwrap()),
320            options: Default::default(),
321        }
322    }
323}
324
325pub mod phc {
326    use super::*;
327
328    #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
329    #[serde(deny_unknown_fields)]
330    pub struct ExtraConfig {
331        /// Where can we reach the transcryptor?
332        pub transcryptor_url: UrlPwa,
333
334        /// Where can we reach the authentication server?
335        pub auths_url: UrlPwa,
336
337        /// The URL to pubhubs used by end-clients.
338        ///
339        /// Currently `https://app.pubhubs.net` for production, `https://main.pubhubs.ihub.ru.nl` for
340        /// acceptance, and `http://localhost:8080` for local development.  
341        pub global_client_url: UrlPwa,
342
343        /// The hubs that are known to us
344        pub hubs: Vec<hub::BasicInfo<UrlPwa>>,
345
346        /// `x_PHC` from the whitepaper; randomly generated if not set
347        ///
348        /// Generate using `cargo run tools generate scalar`.
349        pub master_enc_key_part: Option<elgamal::PrivateKey>,
350
351        /// Secret used to derive [`Attr::id`]s.
352        ///
353        /// Randomly generated if not set, which is not suitable for production.
354        ///
355        /// [`Attr::id`]: crate::attr::Attr::id
356        pub attr_id_secret: Option<B64UU>,
357
358        /// Authentication tokens issued to the global client are valid for this duration.
359        ///
360        /// Auth tokens are validated based on their own contents - there's no list of valid
361        /// authentication tokens in a database somewhere.  This means that when a user is banned,
362        /// the authentication tokens remain valid until they expire.  The validity duration of
363        /// auth tokens should thus not be too long.
364        #[serde(with = "time_ext::human_duration")]
365        #[serde(default = "default_auth_token_validity")]
366        pub auth_token_validity: core::time::Duration,
367
368        /// [`api::phc::user::PpNonce`]s issued to the global client are valid for this duration.
369        #[serde(with = "time_ext::human_duration")]
370        #[serde(default = "default_pp_nonce_validity")]
371        pub pp_nonce_validity: core::time::Duration,
372
373        /// Registration pseudonyms issued to the global client via [`api::phc::user::CardPseudEP`]
374        /// are valid for this duration.
375        #[serde(with = "time_ext::human_duration")]
376        #[serde(default = "default_card_pseud_validity")]
377        pub card_pseud_validity: core::time::Duration,
378
379        /// Secret used to derive `hmac`s for the retrieval of user objects.
380        ///
381        /// Randomly generated if not set.
382        pub user_object_hmac_secret: Option<B64UU>,
383
384        /// Quotas for a user
385        #[serde(default)]
386        pub user_quota: api::phc::user::Quota,
387
388        /// Deprecated.
389        pub card: serde_ext::Skip,
390
391        #[serde(default)]
392        pub hub_cache: crate::servers::phc::HubCacheConfig,
393    }
394
395    fn default_auth_token_validity() -> core::time::Duration {
396        core::time::Duration::from_secs(60 * 60) // 1 hour - the user might need to add attributes
397        // to their Yivi app
398    }
399
400    fn default_pp_nonce_validity() -> core::time::Duration {
401        core::time::Duration::from_secs(30)
402        // no user interaction required
403    }
404
405    fn default_card_pseud_validity() -> core::time::Duration {
406        core::time::Duration::from_secs(30)
407        // no user interaction required
408    }
409}
410
411pub mod transcryptor {
412    use super::*;
413
414    #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
415    #[serde(deny_unknown_fields)]
416    pub struct ExtraConfig {
417        /// `x_T` from the whitepaper; randomly generated if not set
418        ///
419        /// Generate using `cargo run tools generate scalar`.
420        pub master_enc_key_part: Option<elgamal::PrivateKey>,
421
422        /// Used to generate the *pseudonymisation factor secret* `g_H` given hub `H`'s identifier.
423        ///
424        /// Should **never be changed** in a production environment.
425        ///
426        /// Randomly generated when not set.t
427        pub pseud_factor_secret: Option<B64UU>,
428    }
429}
430
431pub mod auths {
432    use super::*;
433
434    #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
435    #[serde(deny_unknown_fields)]
436    pub struct ExtraConfig {
437        #[serde(default)]
438        pub attribute_types: Vec<attr::Type>,
439
440        /// Yivi configuration.  If `None`, yivi is not supported.
441        pub yivi: Option<YiviConfig>,
442
443        /// Authentication must be completed within this timeframe
444        /// formatted as string understood by [`humantime::parse_duration`] such as `1 week`.
445        #[serde(with = "time_ext::human_duration")]
446        #[serde(default = "default_auth_window")]
447        pub auth_window: core::time::Duration,
448
449        /// Used to derive attribute keys (see [`api::auths::AttrKeysEP`])
450        ///
451        /// Randomly generated when not set.  When changed, users loose access to all data
452        /// stored at PHC.
453        pub attr_key_secret: Option<B64UU>,
454    }
455
456    fn default_auth_window() -> core::time::Duration {
457        core::time::Duration::from_secs(60 * 60) // 1 hour - the user might need to add attributes
458        // to their Yivi app
459    }
460
461    impl ExtraConfig {
462        /// Removes the [`attr::SourceDetails`]s of unsupported sources from
463        /// [`attribute_types`].
464        ///
465        /// [`attribute_types`]: Self::attribute_types
466        pub(super) fn filter_attribute_types(&mut self) {
467            let mut supported_sources: std::collections::HashSet<attr::Source> = Default::default();
468
469            if self.yivi.is_some() {
470                assert!(supported_sources.insert(attr::Source::Yivi));
471            }
472
473            for attr_type in self.attribute_types.iter_mut() {
474                attr_type.filter_sources(|s| supported_sources.contains(&s))
475            }
476        }
477    }
478
479    #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
480    #[serde(deny_unknown_fields)]
481    pub struct YiviConfig {
482        /// Where can the Yivi server trusted by the authentication server be reached
483        /// by the hub client for starting disclosure requests?
484        pub requestor_url: UrlPwa,
485
486        pub requestor_creds: yivi::Credentials<yivi::SigningKey>,
487
488        /// What server name to expect in signed session results
489        pub server_name: String,
490
491        /// Verify signed session results using this key.  If not set the key is retrieved
492        /// from the yivi server.
493        pub server_key: Option<yivi::VerifyingKey>,
494
495        /// Fine-tune handling of chained sessions, see
496        /// [`api::auths::AuthStartReq::yivi_chained_session`].
497        #[serde(default)]
498        pub chained_sessions: crate::servers::auths::yivi::ChainedSessionsConfig,
499
500        /// Configuration of the pubhubs card issued by the authentication server
501        #[serde(default)]
502        pub card: crate::servers::auths::card::CardConfig,
503    }
504
505    impl YiviConfig {
506        pub fn server_creds(&self) -> yivi::Credentials<yivi::VerifyingKey> {
507            yivi::Credentials {
508                name: self.server_name.clone(),
509                key: self
510                    .server_key
511                    .clone()
512                    .expect("bug: YiviConfig was not properly prepared"),
513            }
514        }
515    }
516}
517
518/// Trait to prepare [`Config`] for use by initializing random values,
519/// and replacing aliases in [`UrlPwa`]s.
520trait PrepareConfig<C> {
521    async fn prepare(&mut self, context: C) -> anyhow::Result<()>;
522}
523
524type Pcc = std::rc::Rc<actix_web::dev::Extensions>;
525
526impl PrepareConfig<Pcc> for Config {
527    async fn prepare(&mut self, mut c: Pcc) -> anyhow::Result<()> {
528        anyhow::ensure!(
529            self.preparation_state == PreparationState::Preliminary,
530            "configuration not properly prepared"
531        );
532
533        // temporarily move `host_aliases` into Pcc
534        Pcc::get_mut(&mut c)
535            .unwrap()
536            .insert(std::mem::take(&mut self.host_aliases));
537
538        macro_rules! prep {
539            ($server:ident) => {
540                if let Some(ref mut server) = self.$server {
541                    server.prepare(c.clone()).await?;
542                }
543            };
544        }
545
546        for_all_servers!(prep);
547
548        // move `host_aliases` back to self, drop the substitute
549        drop(std::mem::replace(
550            &mut self.host_aliases,
551            Pcc::get_mut(&mut c)
552                .unwrap()
553                .remove::<HostAliases>()
554                .unwrap(),
555        ));
556
557        self.preparation_state = PreparationState::Complete;
558
559        Ok(())
560    }
561}
562
563impl<Extra: PrepareConfig<Pcc> + GetServerType> PrepareConfig<Pcc> for ServerConfig<Extra> {
564    async fn prepare(&mut self, c: Pcc) -> anyhow::Result<()> {
565        if self.port == 0 {
566            self.port = Extra::ServerT::default_port();
567        }
568
569        self.self_check_code
570            .get_or_insert_with(crate::misc::crypto::random_alphanumeric);
571
572        self.jwt_key.get_or_insert_with(api::SigningKey::generate);
573        self.enc_key.get_or_insert_with(elgamal::PrivateKey::random);
574
575        self.admin_key.get_or_insert_with(|| {
576            let sk = api::SigningKey::generate();
577
578            log::info!(
579                "{} admin key: {}",
580                Extra::ServerT::NAME,
581                serde_json::to_string(&sk)
582                    .expect("unexpected error during serialization of admin key")
583            );
584
585            sk.verifying_key().into()
586        });
587
588        if let &mut Some(&mut ref mut osc) = &mut self.object_store.as_mut() {
589            c.get::<HostAliases>()
590                .expect("host aliases were not passed along")
591                .dealias(&mut osc.url);
592        }
593
594        self.extra.prepare(c).await?;
595
596        Ok(())
597    }
598}
599
600impl PrepareConfig<Pcc> for transcryptor::ExtraConfig {
601    async fn prepare(&mut self, _c: Pcc) -> anyhow::Result<()> {
602        self.master_enc_key_part
603            .get_or_insert_with(elgamal::PrivateKey::random);
604
605        self.pseud_factor_secret.get_or_insert_with(|| {
606            serde_bytes::ByteBuf::from(crate::misc::crypto::random_32_bytes()).into()
607        });
608
609        Ok(())
610    }
611}
612
613impl PrepareConfig<Pcc> for phc::ExtraConfig {
614    async fn prepare(&mut self, c: Pcc) -> anyhow::Result<()> {
615        self.master_enc_key_part
616            .get_or_insert_with(elgamal::PrivateKey::random);
617
618        self.attr_id_secret.get_or_insert_with(|| {
619            serde_bytes::ByteBuf::from(crate::misc::crypto::random_32_bytes()).into()
620        });
621
622        self.user_object_hmac_secret.get_or_insert_with(|| {
623            serde_bytes::ByteBuf::from(crate::misc::crypto::random_32_bytes()).into()
624        });
625
626        let ha: &HostAliases = c.get::<HostAliases>().unwrap();
627
628        ha.dealias(&mut self.transcryptor_url);
629        ha.dealias(&mut self.auths_url);
630        ha.dealias(&mut self.global_client_url);
631
632        for hub in self.hubs.iter_mut() {
633            hub.prepare(c.clone()).await?;
634        }
635
636        Ok(())
637    }
638}
639
640impl PrepareConfig<Pcc> for hub::BasicInfo<UrlPwa> {
641    async fn prepare(&mut self, c: Pcc) -> anyhow::Result<()> {
642        let ha: &HostAliases = c.get::<HostAliases>().unwrap();
643
644        ha.dealias(&mut self.url);
645
646        Ok(())
647    }
648}
649
650impl PrepareConfig<Pcc> for auths::ExtraConfig {
651    async fn prepare(&mut self, c: Pcc) -> anyhow::Result<()> {
652        if let Some(ref mut yivi_cfg) = self.yivi {
653            yivi_cfg.prepare(c).await?;
654        }
655
656        self.filter_attribute_types();
657
658        self.attr_key_secret.get_or_insert_with(|| {
659            serde_bytes::ByteBuf::from(crate::misc::crypto::random_32_bytes()).into()
660        });
661
662        Ok(())
663    }
664}
665
666impl PrepareConfig<Pcc> for auths::YiviConfig {
667    async fn prepare(&mut self, c: Pcc) -> anyhow::Result<()> {
668        let ha: &HostAliases = c.get::<HostAliases>().unwrap();
669
670        ha.dealias(&mut self.requestor_url);
671
672        if self.server_key.is_none() {
673            let pk_url = self.requestor_url.as_ref().join("publickey")?.to_string();
674            log::debug!("yivi server key not set; retrieving from {pk_url}");
675
676            let client = awc::Client::default();
677            let payload: bytes::Bytes = crate::misc::task::retry(|| async {
678                match client.get(&pk_url).send().await {
679                    Ok(mut res) => match res.body().await {
680                        Ok(body) => Ok::<_, std::convert::Infallible>(Some(body)),
681                        Err(err) => {
682                            log::warn!(
683                                "error reading yivi server response at {}, retrying: {err}",
684                                self.requestor_url
685                            );
686                            Ok(None)
687                        }
688                    },
689                    Err(err) => {
690                        log::warn!(
691                            "could not reach yivi server at {}, retrying: {err}",
692                            self.requestor_url
693                        );
694                        Ok(None)
695                    }
696                }
697            })
698            .await
699            .expect("retry does not fail")
700            .ok_or_else(|| {
701                log::error!("could not reach yivi server at {}", self.requestor_url);
702                anyhow::anyhow!(
703                    "getting Yivi server's public key from {pk_url} failed after retries"
704                )
705            })?;
706
707            self.server_key = Some(yivi::VerifyingKey::RS256(
708                jwt::RS256Vk::from_public_key_pem(std::str::from_utf8(&payload)?)
709                    .context("decoding public key at {pk_url}")?,
710            ));
711        }
712
713        Ok(())
714    }
715}
716
717/// Used to implement the `server_config` method on `crate::servers::<SERVER>::Details`.
718pub trait GetServerConfig {
719    type Extra;
720
721    fn server_config(config: &Config) -> &ServerConfig<Self::Extra>;
722}
723
724macro_rules! implement_get_server_config {
725    ($server:ident) => {
726        impl GetServerConfig for crate::servers::$server::Details {
727            type Extra = crate::servers::config::$server::ExtraConfig;
728
729            fn server_config(config: &Config) -> &ServerConfig<Self::Extra> {
730                &config.$server.as_ref().unwrap()
731            }
732        }
733    };
734}
735
736for_all_servers!(implement_get_server_config);
737
738/// Used to implement the `ServerT` associated type of `<SERVER>::ExtraConfig`.
739trait GetServerType {
740    type ServerT: crate::servers::Server;
741}
742
743macro_rules! implement_server_type {
744    ($server:ident) => {
745        impl GetServerType for $server::ExtraConfig {
746            type ServerT = crate::servers::$server::Server;
747        }
748    };
749}
750
751for_all_servers!(implement_server_type);