Skip to main content

pubhubs/servers/
server.rs

1//! What's common between PubHubs servers
2use actix_web::web;
3use anyhow::{Context as _, Result};
4use futures_util::future::FutureExt as _;
5
6use core::convert::Infallible;
7use std::ops::{Deref, DerefMut};
8use std::rc::Rc;
9
10use crate::common::{elgamal, kem};
11
12use crate::client;
13
14use crate::api::OpenError;
15use crate::api::{
16    self, ApiResultExt as _, DiscoveryRunResp, EndpointDetails, NoPayload, ResultExt as _,
17};
18use crate::servers::{self, Config, Constellation, Handle};
19
20/// Enumerates the names of the different PubHubs servers
21#[derive(
22    serde::Serialize, serde::Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash, clap::ValueEnum,
23)]
24pub enum Name {
25    #[serde(rename = "phc")]
26    PubhubsCentral,
27
28    #[serde(rename = "transcryptor")]
29    Transcryptor,
30
31    #[serde(rename = "auths")]
32    AuthenticationServer,
33}
34
35impl std::fmt::Display for Name {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
37        write!(
38            f,
39            "{}",
40            match self {
41                Name::PubhubsCentral => "PubHubs Central",
42                Name::Transcryptor => "Transcryptor",
43                Name::AuthenticationServer => "Authentication Server",
44            }
45        )
46    }
47}
48
49/// Common API to the different PubHubs servers.
50///
51/// A single instance of the [`ServerImpl`] implementation of [`Server`] is created
52/// for each server that's being run, and it's mainly responsible for creating
53/// immutable [`App`] instances to be sent to the individual threads.
54///
55/// For efficiency's sake, only the [`App`] instances are available to each thread,
56/// and are mostly immutable. To change the server's state, generally all apps must be restarted.
57///
58/// An exception to this no-shared-mutable-state is the shared state in [`Handle`], for example the
59/// `crate::servers::run::DiscoveryLimiter` and the object store
60pub trait Server: DerefMut<Target = Self::AppCreatorT> + Sized + 'static {
61    type AppT: App<Self>;
62
63    const NAME: Name;
64
65    /// Returns the default TCP port this server binds to.
66    fn default_port() -> u16 {
67        match Self::NAME {
68            // we've changed phc's port to from 8080 to 5050
69            // so that the old and new phc can be run simultaneously.
70            Name::PubhubsCentral => 5050,
71            Name::Transcryptor => 7070,
72            Name::AuthenticationServer => 6060,
73        }
74    }
75
76    /// Is moved accross threads to create the [`App`]s.
77    type AppCreatorT: AppCreator<Self>;
78
79    type ExtraConfig;
80
81    /// Additional state when the server is running
82    type ExtraRunningState: Clone + core::fmt::Debug;
83
84    /// Data threaded out of [`App::discover`] into
85    /// [`create_running_state`](Self::create_running_state) that the latter needs but cannot
86    /// recompute.  `()` for servers other than PHC.
87    type RunningStateSeed: Send + 'static;
88
89    /// Additional shared state
90    type ExtraSharedState;
91
92    /// Additional process-local server state.
93    type ExtraServerState;
94
95    /// Type of this server's object store, usually an [`object_store::ObjectStore`], or `()`.
96    type ObjectStoreT: Sync;
97
98    fn new(config: &crate::servers::Config) -> Result<Self>;
99
100    fn config(&self) -> &crate::servers::Config;
101
102    fn server_config(&self) -> &servers::config::ServerConfig<Self::ExtraConfig> {
103        Self::server_config_from(self.config())
104    }
105
106    fn server_config_from(
107        config: &servers::Config,
108    ) -> &servers::config::ServerConfig<Self::ExtraConfig>;
109
110    fn create_running_state(
111        &self,
112        constellation: &Constellation,
113        seed: &Self::RunningStateSeed,
114    ) -> Result<Self::ExtraRunningState>;
115
116    fn create_extra_shared_state(config: &servers::Config) -> Result<Self::ExtraSharedState>;
117
118    fn create_extra_server_state(config: &servers::Config) -> Result<Self::ExtraServerState>;
119
120    fn extra(&self) -> &Self::ExtraServerState;
121
122    /// This function is called when the server is started to run discovery.
123    ///
124    /// It is only passed a shared (and thus immutable) reference to itself to prevent any modifications
125    /// going unnoticed by [`App`] instances.
126    ///
127    /// It can be ordered to stop via the `shutdown_receiver`, in which case
128    /// it should return Ok(None).
129    ///
130    /// If can also return on its own to modify itself via the returned [`BoxModifier`].
131    ///
132    /// If it returns an error, the whole binary crashes.
133    ///
134    /// Before this function's future finishes, it should relinquish all references to `self`.
135    /// Otherwise the modification following it will panic.
136    ///
137    /// It is given its own [`App`] instance.
138    ///
139    /// TODO: remove returning BoxModifier since that can be achieved via App instance?
140    #[expect(async_fn_in_trait)]
141    async fn run_until_modifier(
142        self: Rc<Self>,
143        shutdown_receiver: tokio::sync::oneshot::Receiver<Infallible>,
144        app: Rc<Self::AppT>,
145    ) -> Result<Option<BoxModifier<Self>>>;
146
147    /// Creates cross-origin resource sharing middleware for this server.
148    fn cors() -> actix_cors::Cors {
149        actix_cors::Cors::default()
150            .allow_any_origin()
151            .allowed_methods(["GET", "POST"])
152            .allowed_header(actix_web::http::header::CONTENT_TYPE)
153            .allowed_header(actix_web::http::header::AUTHORIZATION)
154    }
155}
156
157/// Basic implementation of [Server].
158pub struct ServerImpl<D: Details> {
159    config: servers::Config,
160    app_creator: D::AppCreatorT,
161    extra: D::ExtraServerState,
162}
163
164impl<D: Details> Deref for ServerImpl<D> {
165    type Target = D::AppCreatorT;
166
167    #[inline]
168    fn deref(&self) -> &Self::Target {
169        &self.app_creator
170    }
171}
172
173impl<D: Details> DerefMut for ServerImpl<D> {
174    #[inline]
175    fn deref_mut(&mut self) -> &mut Self::Target {
176        &mut self.app_creator
177    }
178}
179
180/// Details needed to create a [ServerImpl] type.
181pub trait Details: crate::servers::config::GetServerConfig + 'static + Sized {
182    const NAME: Name;
183    type AppCreatorT;
184    type AppT;
185    type ExtraRunningState: Clone + core::fmt::Debug;
186    type RunningStateSeed: Send + 'static;
187    type ExtraSharedState;
188    type ExtraServerState;
189    type ObjectStoreT;
190
191    fn create_running_state(
192        server: &ServerImpl<Self>,
193        constellation: &Constellation,
194        seed: &Self::RunningStateSeed,
195    ) -> Result<Self::ExtraRunningState>;
196
197    fn create_extra_shared_state(config: &servers::Config) -> Result<Self::ExtraSharedState>;
198
199    fn create_extra_server_state(config: &servers::Config) -> Result<Self::ExtraServerState>;
200}
201
202impl<D: Details> Server for ServerImpl<D>
203where
204    D::AppT: App<Self>,
205    D::AppCreatorT: AppCreator<Self>,
206    D::ObjectStoreT: Sync,
207{
208    const NAME: Name = D::NAME;
209
210    type AppCreatorT = D::AppCreatorT;
211    type AppT = D::AppT;
212
213    type ExtraConfig = D::Extra;
214    type ExtraRunningState = D::ExtraRunningState;
215    type RunningStateSeed = D::RunningStateSeed;
216    type ExtraSharedState = D::ExtraSharedState;
217    type ExtraServerState = D::ExtraServerState;
218
219    type ObjectStoreT = D::ObjectStoreT;
220
221    fn new(config: &servers::Config) -> Result<Self> {
222        Ok(Self {
223            app_creator: Self::AppCreatorT::new(config)?,
224            extra: D::create_extra_server_state(config)?,
225            config: config.clone(),
226        })
227    }
228
229    fn config(&self) -> &servers::Config {
230        &self.config
231    }
232
233    fn server_config_from(
234        config: &servers::Config,
235    ) -> &servers::config::ServerConfig<Self::ExtraConfig> {
236        D::server_config(config)
237    }
238
239    fn create_running_state(
240        &self,
241        constellation: &Constellation,
242        seed: &Self::RunningStateSeed,
243    ) -> Result<Self::ExtraRunningState> {
244        D::create_running_state(self, constellation, seed)
245    }
246
247    fn create_extra_shared_state(config: &servers::Config) -> Result<Self::ExtraSharedState> {
248        D::create_extra_shared_state(config)
249    }
250
251    fn create_extra_server_state(config: &servers::Config) -> Result<Self::ExtraServerState> {
252        D::create_extra_server_state(config)
253    }
254
255    fn extra(&self) -> &Self::ExtraServerState {
256        &self.extra
257    }
258
259    async fn run_until_modifier(
260        self: Rc<Self>,
261        shutdown_receiver: tokio::sync::oneshot::Receiver<Infallible>,
262        app: Rc<Self::AppT>,
263    ) -> Result<Option<crate::servers::server::BoxModifier<Self>>> {
264        tokio::select! {
265            res = shutdown_receiver => {
266               res.expect_err("got instance of Infallible");
267               #[expect(clippy::needless_return)] // It's more clear this way
268               return Ok(None);
269            }
270
271            res = self.run_discovery_and_then_wait_forever(app) => {
272               #[expect(clippy::needless_return)] // It's more clear this way
273                return Err(res.expect_err("got instance of Infallible"));
274            }
275        }
276    }
277}
278
279impl<D: Details> ServerImpl<D>
280where
281    D::AppT: App<Self>,
282    D::AppCreatorT: AppCreator<Self>,
283    D::ObjectStoreT: Sync,
284{
285    async fn run_discovery_and_then_wait_forever(&self, app: Rc<D::AppT>) -> Result<Infallible> {
286        self.run_discovery(app.clone()).await?;
287
288        D::AppT::global_task(app).await // waits forever
289    }
290
291    async fn run_discovery(&self, app: Rc<D::AppT>) -> Result<()> {
292        crate::misc::task::retry(|| async {
293            (match
294                AppBase::<Self>::handle_discovery_run(app.clone()).await.retryable()/* <- turns retryable error Err(err) into Ok(None) */?
295            {
296                Some(DiscoveryRunResp::Restarting | DiscoveryRunResp::UpToDate) => Ok(Some(())),
297                None => Ok(None),
298            }) as Result<Option<()>>
299        })
300        .await?
301        .ok_or_else(|| anyhow::anyhow!("timeout waiting for discovery of {server_name}", server_name = D::NAME))
302    }
303}
304
305/// What's cloned and moved accross threads by a [`Server`] to create its [`App`] instances.
306pub trait AppCreator<ServerT: Server>:
307    DerefMut<Target = AppCreatorBase<ServerT>> + Send + Clone + 'static
308{
309    /// When [`App`]s are created a new [`AppCreator::ContextT`]  is created,
310    /// and a reference to it is passed to [`AppCreator::into_app`].
311    type ContextT: Default + Send + Clone + 'static;
312
313    /// Creates a new instance of this [`AppCreator`] based on the given configuration.
314    fn new(config: &servers::Config) -> Result<Self>;
315
316    /// Create an [`App`] instance.
317    ///
318    /// The `handle` [`Handle`] can be used to restart the server.  It's
319    /// up to the implementor to clone it.
320    ///
321    /// `generation` indicates how many times the server has been restarted while running this
322    /// binary
323    fn into_app(
324        self,
325        handle: &Handle<ServerT>,
326        context: &Self::ContextT,
327        generation: usize,
328    ) -> ServerT::AppT;
329}
330
331/// What modifies a [Server] via [Command::Modify].
332///
333/// It is [Send] and `'static` because it's moved accross threads, from an [App] to the task
334/// running the [Server].
335///
336/// We do not use a trait like `(FnOnce(&mut ServerT)) + Send + 'static`,
337/// because it can not (yet) be implemented by users.
338pub trait Modifier<ServerT: Server>: Send + 'static {
339    /// Stops server, perform modification, and restarts server if true was returned.
340    fn modify(self: Box<Self>, server: &mut ServerT) -> bool;
341
342    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error>;
343}
344
345impl<S: Server, F: FnOnce(&mut S) -> bool + Send + 'static, D: std::fmt::Display + Send + 'static>
346    Modifier<S> for (F, D)
347{
348    fn modify(self: Box<Self>, server: &mut S) -> bool {
349        self.0(server)
350    }
351
352    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
353        self.1.fmt(f)
354    }
355}
356
357/// [Modifier] that stops the server
358pub struct Exiter;
359
360impl<S: Server> Modifier<S> for Exiter {
361    fn modify(self: Box<Self>, _server: &mut S) -> bool {
362        false
363    }
364
365    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
366        f.write_str("exiter")
367    }
368}
369
370/// Owned dynamically typed [Modifier].
371pub type BoxModifier<S> = Box<dyn Modifier<S>>;
372
373impl<S: Server> std::fmt::Display for BoxModifier<S> {
374    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
375        Modifier::fmt(&**self, f)
376    }
377}
378
379/// What inspects a server via [Command::Inspect].
380pub(crate) trait Inspector<ServerT: Server>: Send + 'static {
381    /// Calls this function with server as argument
382    fn inspect(self: Box<Self>, server: &ServerT);
383
384    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error>;
385}
386
387impl<S: Server, F: FnOnce(&S) + Send + 'static, D: std::fmt::Display + Send + 'static> Inspector<S>
388    for (F, D)
389{
390    fn inspect(self: Box<Self>, server: &S) {
391        self.0(server)
392    }
393
394    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
395        self.1.fmt(f)
396    }
397}
398
399/// Owned dynamically typed [Inspector].
400pub type BoxInspector<S> = Box<dyn Inspector<S>>;
401
402impl<S: Server> std::fmt::Display for BoxInspector<S> {
403    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
404        Inspector::fmt(&**self, f)
405    }
406}
407
408/// Commands an [App] can issue to its runner.
409pub(crate) enum Command<S: Server> {
410    /// Stop the server, apply the enclosed modification, and, depending on the result restart
411    /// the server.
412    ///
413    /// Server restarts should be performed sparingly, and may take seconds to minutes (because
414    /// actix waits for workers to shutdown gracefully.)
415    Modify(BoxModifier<S>),
416
417    /// Calls the enclosed function on the server
418    Inspect(BoxInspector<S>),
419
420    /// Stops the server
421    Exit,
422}
423impl<S: Server> std::fmt::Display for Command<S> {
424    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
425        match self {
426            Command::Inspect(inspector) => write!(f, "inspector {inspector}"),
427            Command::Modify(modifier) => write!(f, "modifier {modifier}"),
428            Command::Exit => write!(f, "exit"),
429        }
430    }
431}
432
433/// Result of [`App::discover`], generic over the server's
434/// [`RunningStateSeed`](Details::RunningStateSeed).
435pub enum DiscoverVerdict<RunningStateSeed> {
436    /// My and PHC's constellation seem up-to-date
437    Alright,
438
439    /// My constellation is out-of-date and must be replaced with this constellation.  `seed`
440    /// carries the data [`create_running_state`](Details::create_running_state) needs but cannot
441    /// recompute.
442    ConstellationOutdated {
443        new_constellation: Box<Constellation>,
444        seed: RunningStateSeed,
445    },
446
447    /// My constellation is unchanged, but my running state must be rebuilt from `seed`.  Used by
448    /// PHC once it has unsealed the transcryptor's master key part and can derive the master
449    /// encryption key (which lives in the running state, not the constellation): no new
450    /// constellation is published, so T and AS are not restarted.
451    RunningStateOutdated { seed: RunningStateSeed },
452
453    /// My binary is out-of-date.  Exit this binary, and hope the binary is updated.
454    BinaryOutdated,
455}
456
457/// What's common between the [`actix_web::App`]s used by the different PubHubs servers.
458///
459/// Each [`actix_web::App`] gets access to an instance of the appropriate implementation of [`App`]..
460#[allow(async_fn_in_trait)]
461pub trait App<S: Server>: Deref<Target = AppBase<S>> + 'static {
462    /// Allows [`App`] to add server-specific endpoints.  Non-server specific endpoints are added by
463    /// [`AppBase::configure_actix_app`].
464    fn configure_actix_app(self: &Rc<Self>, sc: &mut web::ServiceConfig);
465
466    /// Checks whether the given constellation properly reflects this server's configuration.
467    fn check_constellation(&self, constellation: &Constellation) -> bool;
468
469    /// Runs the discovery routine for this server given [`api::DiscoveryInfoResp`] already
470    /// obtained from Pubhubs Central.  If the server is not PHC itself, the [`Constellation`]
471    /// in this [`api::DiscoveryInfoResp`] must be set.
472    ///
473    /// If one of the other servers is not up-to-date according to this server, discovery of that
474    /// server is invoked and [`api::ErrorCode::PleaseRetry`] is returned.
475    ///
476    /// PHC implements this directly; the transcryptor and authentication server delegate to
477    /// [`discover_as_non_phc`](Self::discover_as_non_phc).
478    async fn discover(
479        self: &Rc<Self>,
480        phc_inf: api::DiscoveryInfoResp,
481    ) -> api::Result<DiscoverVerdict<S::RunningStateSeed>>;
482
483    /// Shared discovery routine for the non-PHC servers (transcryptor, authentication server),
484    /// whose running state needs no seed (`RunningStateSeed = ()`).
485    async fn discover_as_non_phc(
486        self: &Rc<Self>,
487        phc_inf: api::DiscoveryInfoResp,
488    ) -> api::Result<DiscoverVerdict<()>> {
489        log::debug!("{server_name}: running discovery", server_name = S::NAME);
490
491        if S::NAME == Name::PubhubsCentral {
492            log::error!(
493                "{} should implement discovery itself!",
494                Name::PubhubsCentral
495            );
496            return Err(api::ErrorCode::InternalError);
497        }
498
499        if let Some(ref phc_version) = phc_inf.version
500            && let Some(my_version) = &self.version
501        {
502            let phc_version = crate::servers::version::to_semver(phc_version).map_err(|err| {
503                log::error!(
504                    "could not parse semantic version returned by PHC: {phc_version}: {err}"
505                );
506                api::ErrorCode::InternalError
507            })?;
508
509            let my_version = crate::servers::version::to_semver(my_version).map_err(|err| {
510                log::error!("could not parse my semantic version {my_version}: {err}");
511                api::ErrorCode::InternalError
512            })?;
513
514            if my_version < phc_version {
515                log::warn!(
516                    "{server_name}: {phc}'s version ({phc_version}) > my version ({my_version})",
517                    server_name = S::NAME,
518                    phc = Name::PubhubsCentral
519                );
520                return Ok(DiscoverVerdict::BinaryOutdated);
521            }
522
523            if my_version > phc_version {
524                log::warn!(
525                    "{server_name}: {phc}'s version {phc_version} is out-of-date - requesting rediscovery",
526                    server_name = S::NAME,
527                    phc = Name::PubhubsCentral
528                );
529
530                let _drr = self
531                    .client
532                    .query::<api::DiscoveryRun>(&phc_inf.phc_url, NoPayload)
533                    .await
534                    .into_server_result()?;
535                return Err(api::ErrorCode::PleaseRetry);
536            }
537        } else {
538            log::warn!(
539                "not checking my version ({my_version}) against phc's version ({phc_version})",
540                my_version = crate::servers::version::VERSION,
541                phc_version = phc_inf.version.unwrap_or_else(|| "n/a".to_string())
542            );
543        }
544
545        assert!(
546            phc_inf.constellation_or_id.is_some(),
547            "this `discover` method should only be run when phc_inf.constellation is some"
548        );
549
550        let Some(phc_inf_constellation) = phc_inf.constellation_or_id.unwrap().into_constellation()
551        else {
552            log::warn!(
553                "{server_name}: {phc} returned only its contellation id",
554                phc = Name::PubhubsCentral,
555                server_name = S::NAME
556            );
557            return Err(api::ErrorCode::InternalError);
558        };
559
560        if !self.check_constellation(&phc_inf_constellation) {
561            log::warn!(
562                "{server_name}: {phc}'s constellation seems to be out-of-date - requesting rediscovery",
563                server_name = S::NAME,
564                phc = Name::PubhubsCentral
565            );
566
567            // PHC's discovery is out of date; invoke discovery and return
568            let _drr = self
569                .client
570                .query::<api::DiscoveryRun>(&phc_inf.phc_url, NoPayload)
571                .await
572                .into_server_result()?;
573
574            // We don't do anything with _drr: whether or not PHC has been updated in the
575            // meantime, we want to start discovery again from the start.
576
577            return Err(api::ErrorCode::PleaseRetry);
578        }
579
580        log::trace!(
581            "{server_name}: {phc}'s constellation looks alright! ",
582            server_name = S::NAME,
583            phc = Name::PubhubsCentral
584        );
585
586        if let Some(rs) = self.running_state.as_ref()
587            && phc_inf_constellation.id == rs.constellation.id
588        {
589            log::info!(
590                "{server_name}: my constellation is up-to-date!",
591                server_name = S::NAME,
592            );
593
594            return Ok(DiscoverVerdict::Alright);
595        }
596
597        log::info!(
598            "{}: my constellation is {}",
599            S::NAME,
600            if self.running_state.is_some() {
601                "out of date"
602            } else {
603                "not yet set"
604            }
605        );
606
607        // NOTE: phc_inf has already been (partially) checked
608        let url = phc_inf_constellation.url(S::NAME);
609
610        // obtain DiscoveryInfo from oneself
611        let di = self
612            .client
613            .query::<api::DiscoveryInfo>(url, NoPayload)
614            .await
615            .into_server_result()?;
616
617        let _di_again = client::discovery::DiscoveryInfoCheck {
618            name: S::NAME,
619            phc_url: &self.phc_url,
620            self_check_code: Some(&self.self_check_code),
621            constellation: None,
622            // NOTE: we're not checking whether our own constellation is up-to-date,
623            // because it likely is not - why would we run discovery otherwise?
624        }
625        .check(di, url)?;
626
627        Ok(DiscoverVerdict::ConstellationOutdated {
628            new_constellation: Box::new(phc_inf_constellation),
629            seed: (),
630        })
631    }
632
633    /// Should return the master encryption key part for PHC and the transcryption.
634    fn master_enc_key_part(&self) -> Option<&elgamal::PrivateKey> {
635        if matches!(S::NAME, Name::PubhubsCentral | Name::Transcryptor) {
636            panic!("this default impl should have been  overriden for PHC and T")
637        }
638        None
639    }
640
641    /// This server's published [`kem::EncapKeyBytes`], if any.  Overridden by T/AS.
642    fn encap_key(&self) -> Option<&kem::EncapKeyBytes> {
643        None
644    }
645
646    /// The sealing key (shared with PHC) under which this server seals its master encryption key
647    /// part in its discovery info, when available (it requires a running state, so it is `None`
648    /// before discovery completes).  Only invoked on, and overridden by, the transcryptor.
649    fn master_enc_key_part_sealing_key(&self) -> Option<&api::SealingKey> {
650        panic!(
651            "master_enc_key_part_sealing_key is only invoked on (and overridden by) the transcryptor"
652        )
653    }
654
655    /// Will be invoked for each instance of [`App`] that is created.
656    async fn local_task(_weak: std::rc::Weak<Self>) {}
657
658    /// Will be invoked once for each server, after discovery
659    async fn global_task(_app: std::rc::Rc<Self>) -> Result<Infallible> {
660        Ok(std::future::pending::<Infallible>().await)
661    }
662}
663
664/// What's internally common between PubHubs [`AppCreator`]s.
665pub struct AppCreatorBase<S: Server> {
666    pub running_state: Option<RunningState<S::ExtraRunningState>>,
667    pub phc_url: url::Url,
668    pub self_check_code: String,
669    pub enc_key: Box<[u8]>,
670    pub admin_key: crate::misc::jwt::HS256,
671    pub shared: SharedState<S>,
672    pub version: Option<String>,
673}
674
675// need to implement this manually, because we do not want `Server` to implement `Clone`
676impl<S: Server> Clone for AppCreatorBase<S> {
677    fn clone(&self) -> Self {
678        Self {
679            running_state: self.running_state.clone(),
680            phc_url: self.phc_url.clone(),
681            self_check_code: self.self_check_code.clone(),
682            enc_key: self.enc_key.clone(),
683            admin_key: self.admin_key.clone(),
684            shared: self.shared.clone(),
685            version: self.version.clone(),
686        }
687    }
688}
689
690impl<S: Server> AppCreatorBase<S>
691where
692    S::ObjectStoreT: for<'a> TryFrom<&'a Option<servers::config::ObjectStoreConfig>, Error = anyhow::Error>
693        + Sync,
694{
695    pub fn new(config: &crate::servers::Config) -> Result<Self> {
696        assert_eq!(
697            config.preparation_state,
698            crate::servers::config::PreparationState::Complete
699        );
700
701        let server_config = S::server_config_from(config);
702
703        // Decode the config-validated signing-key bytes into the live key once here, in the shared
704        // state, instead of per worker thread: a `PqdsaKeyPair` is expensive to construct, and the
705        // key is `Sync`, so all workers share this single decoded copy.  It also survives discovery
706        // restarts, which reuse the shared state rather than rebuilding it.
707        let signing_key = server_config
708            .signing_key
709            .as_ref()
710            .expect("signing_key was not set nor generated")
711            .decode()
712            .map_err(|_| anyhow::anyhow!("invalid signing_key in config"))?;
713        // Cache the verifying-key encoding so we don't re-encode it on every discovery poll /
714        // `check_constellation`.
715        let verifying_key_bytes = signing_key.verifying_key().encode();
716
717        let admin_key = crate::misc::jwt::HS256(
718            server_config
719                .admin_key
720                .as_ref()
721                .expect("admin_key was not set nor generated")
722                .clone()
723                .into_inner()
724                .into_vec(),
725        );
726
727        Ok(Self {
728            running_state: None,
729            self_check_code: server_config
730                .self_check_code
731                .clone()
732                .expect("self_check_code was not set nor generated"),
733            enc_key: <serde_bytes::ByteBuf as Clone>::clone(
734                server_config
735                    .enc_key
736                    .as_ref()
737                    .expect("enc_key was not set nor generated"),
738            )
739            .into_vec()
740            .into_boxed_slice(),
741            phc_url: config.phc_url.as_ref().clone(),
742            admin_key,
743            shared: SharedState::new(SharedStateInner {
744                object_store: TryFrom::try_from(&server_config.object_store)
745                    .with_context(|| format!("Creating object store for {}", S::NAME))?,
746                signing_key,
747                verifying_key_bytes,
748                extra: S::create_extra_shared_state(config)?,
749            }),
750            version: server_config.version.clone(),
751        })
752    }
753}
754
755/// What's internally common between PubHubs [`App`]s.
756///
757/// Should *NOT* be cloned.
758pub struct AppBase<S: Server> {
759    pub running_state: Option<RunningState<S::ExtraRunningState>>,
760    pub handle: Handle<S>,
761    pub self_check_code: String,
762    pub phc_url: url::Url,
763    pub admin_key: crate::misc::jwt::HS256,
764    pub shared: SharedState<S>,
765    pub client: client::Client,
766    pub version: Option<String>,
767    pub thread_id: std::thread::ThreadId,
768    pub generation: usize,
769}
770
771impl<S: Server> Drop for AppBase<S> {
772    fn drop(&mut self) {
773        log::trace!(
774            "{}: app for generation {} that started on {:?} dropped",
775            S::NAME,
776            self.generation,
777            self.thread_id
778        );
779    }
780}
781
782impl<S: Server> AppBase<S> {
783    pub fn new(creator_base: AppCreatorBase<S>, handle: &Handle<S>, generation: usize) -> Self {
784        let thread_id = std::thread::current().id();
785
786        log::trace!(
787            "{}: app for generation {} started on {:?}",
788            S::NAME,
789            generation,
790            thread_id
791        );
792
793        Self {
794            running_state: creator_base.running_state,
795            handle: handle.clone(),
796            phc_url: creator_base.phc_url,
797            self_check_code: creator_base.self_check_code,
798            admin_key: creator_base.admin_key,
799            shared: creator_base.shared,
800            client: client::Client::builder()
801                .agent(client::Agent::Server(S::NAME))
802                .finish(),
803            version: creator_base.version,
804            thread_id,
805            generation,
806        }
807    }
808
809    /// Returns the current [`RunningState`] of this server when available.
810    /// Otherwise returns [`api::ErrorCode::PleaseRetry`].
811    pub fn running_state_or_please_retry(
812        &self,
813    ) -> Result<&RunningState<S::ExtraRunningState>, api::ErrorCode> {
814        self.running_state
815            .as_ref()
816            .ok_or(api::ErrorCode::PleaseRetry)
817    }
818
819    /// Returns the current [`RunningState`] of this server when available.
820    /// Otherwise returns [`api::ErrorCode::InternalError`].
821    pub fn running_state_or_internal_error(
822        &self,
823    ) -> Result<&RunningState<S::ExtraRunningState>, api::ErrorCode> {
824        self.running_state.as_ref().ok_or_else(|| {
825            log::error!(
826                "{}: expected running state to be available, but it was not",
827                S::NAME
828            );
829            api::ErrorCode::InternalError
830        })
831    }
832
833    /// Configures common endpoints
834    pub fn configure_actix_app(app: &Rc<S::AppT>, sc: &mut web::ServiceConfig) {
835        api::DiscoveryRun::add_to(app, sc, Self::handle_discovery_run);
836        api::DiscoveryInfo::caching_add_to(app, sc, Self::cached_handle_discovery_info);
837
838        api::admin::UpdateConfigEP::add_to(app, sc, Self::handle_admin_post_config);
839        api::admin::InfoEP::add_to(app, sc, Self::handle_admin_info);
840    }
841
842    /// Shared body of [`api::server::HubPingEP`].  Each server has its own `handle_hub_ping`
843    /// method that delegates here.
844    pub async fn handle_hub_ping(
845        app: Rc<S::AppT>,
846        signed_req: web::Json<api::phc::hub::TicketSigned<api::server::PingReq>>,
847    ) -> api::Result<api::server::PingResp> {
848        let running_state = app.running_state_or_please_retry()?;
849
850        let ts_req = signed_req.into_inner();
851
852        let (req, hub_handle) = match ts_req.open(&running_state.phc_verifying_key) {
853            Ok(opened) => opened,
854            Err(toe) => return toe.default_verdict(api::server::PingResp::RetryWithNewTicket),
855        };
856
857        Ok(api::server::PingResp::Success {
858            hub_handle,
859            nonce: req.nonce,
860            served_by: S::NAME,
861        })
862    }
863
864    /// Changes server config, and restarts server
865    async fn handle_admin_post_config(
866        app: Rc<S::AppT>,
867        signed_req: web::Json<api::Signed<api::admin::UpdateConfigReq>>,
868    ) -> api::Result<api::admin::UpdateConfigResp> {
869        let signed_req = signed_req.into_inner();
870
871        let req = match signed_req.open(&app.admin_key, None) {
872            Ok(req) => req,
873            Err(OpenError::OtherConstellation(..)) | Err(OpenError::InternalError) => {
874                return Err(api::ErrorCode::InternalError);
875            }
876            Err(OpenError::OtherwiseInvalid) => return Err(api::ErrorCode::BadRequest),
877            Err(OpenError::Expired) => return Ok(api::admin::UpdateConfigResp::ResignRequest),
878            Err(OpenError::InvalidSignature) => {
879                return Ok(api::admin::UpdateConfigResp::InvalidAdminKey);
880            }
881        };
882
883        // Before restarting the server, check that the modification would work,
884        // so we can return an error to the requestor.  Once we issue a modification command
885        // the present connection is severed, and so no error can be returned.
886
887        let config = app
888            .handle
889            .inspect(
890                "admin's retrieval of current configuration",
891                |server: &S| -> Config { server.config().clone() },
892            )
893            .await
894            .into_ec(|_| {
895                log::warn!("{}: failed to retrieve configuration from server", S::NAME,);
896                api::ErrorCode::PleaseRetry // probably the server is restarting
897            })?;
898
899        let mut new_config: Config = config
900            .json_updated(&req.pointer, req.new_value.clone())
901            .into_ec(|err| {
902                log::warn!(
903                    "{}: failed to modify configuration at {} to {}: {err:#}",
904                    S::NAME,
905                    req.pointer,
906                    req.new_value
907                );
908                api::ErrorCode::BadRequest
909            })?;
910
911        drop(config);
912
913        // reprepare config...
914        new_config.preliminary_prep().into_ec(|err| {
915            log::warn!(
916                "{}: failed to reprepare (preliminary step) modified configuration: {err}",
917                S::NAME
918            );
919            api::ErrorCode::BadRequest
920        })?;
921        new_config.prepare().await.into_ec(|err| {
922            log::warn!(
923                "{}: failed to reprepare modified configuration: {err}",
924                S::NAME
925            );
926            api::ErrorCode::BadRequest
927        })?;
928
929        // All is well - let's restart the server with the new configuration
930        app
931        .handle
932        .modify(
933            "admin update of current in-memory configuration",
934            move |server: &mut S| {
935
936                let new_server_maybe = S::new(&new_config);
937
938                if let Err(err) = new_server_maybe {
939                    log::error!("Could not create new {} with changed configuration: {}. Restarting old server.", S::NAME, err);
940                    return true; // restart
941                }
942
943                *server = new_server_maybe.unwrap();
944
945                true // restart
946            }
947        )
948        .await
949        .into_ec(|_| {
950            log::warn!("{}: failed to enqueue modification", S::NAME);
951            api::ErrorCode::PleaseRetry
952        })?;
953
954        Ok(api::admin::UpdateConfigResp::Success)
955    }
956
957    /// Retrieve non-public information about the server
958    async fn handle_admin_info(
959        app: Rc<S::AppT>,
960        signed_req: web::Json<api::Signed<api::admin::InfoReq>>,
961    ) -> api::Result<api::admin::InfoResp> {
962        let signed_req = signed_req.into_inner();
963
964        let _req = match signed_req.open(&app.admin_key, None) {
965            Ok(req) => req,
966            Err(OpenError::OtherConstellation(..)) | Err(OpenError::InternalError) => {
967                return Err(api::ErrorCode::InternalError);
968            }
969            Err(OpenError::OtherwiseInvalid) => return Err(api::ErrorCode::BadRequest),
970            Err(OpenError::Expired) => return Ok(api::admin::InfoResp::ResignRequest),
971            Err(OpenError::InvalidSignature) => return Ok(api::admin::InfoResp::InvalidAdminKey),
972        };
973
974        let config = app
975            .handle
976            .inspect(
977                "admin's retrieval of current configuration",
978                |server: &S| -> Config { server.config().clone() },
979            )
980            .await
981            .into_ec(|_| {
982                log::warn!("{}: failed to retrieve configuration from server", S::NAME,);
983                api::ErrorCode::PleaseRetry // probably the server is restarting
984            })?;
985
986        Ok(api::admin::InfoResp::Success {
987            config: Box::new(config),
988        })
989    }
990
991    /// Run the discovery process, and restarts server if necessary.  Returns when
992    /// the discovery process is completed, but before a possible restart.
993    async fn handle_discovery_run(app: Rc<S::AppT>) -> api::Result<api::DiscoveryRunResp> {
994        app.handle.request_discovery(app.clone()).await
995    }
996
997    pub(super) async fn discover_phc(app: Rc<S::AppT>) -> api::Result<api::DiscoveryInfoResp> {
998        let pdi = app
999            .client
1000            .query::<api::DiscoveryInfo>(&app.phc_url, NoPayload)
1001            .await
1002            .into_server_result()?;
1003
1004        client::discovery::DiscoveryInfoCheck {
1005            phc_url: &app.phc_url,
1006            name: Name::PubhubsCentral,
1007            self_check_code: if S::NAME == Name::PubhubsCentral {
1008                Some(&app.self_check_code)
1009            } else {
1010                None
1011            },
1012            constellation: None,
1013            // NOTE: don't check whether our constellation coincides with PHC's constellation here,
1014            // because if they're not the same that will cause an error to be returned, while
1015            // we want to initiate a restart instead.
1016        }
1017        .check(pdi, &app.phc_url)
1018    }
1019
1020    fn cached_handle_discovery_info(app: &S::AppT) -> api::Result<api::DiscoveryInfoResp> {
1021        use super::constellation::ConstellationOrId;
1022
1023        // Return our constellation (if we have one), but only its id if we're not PHC.
1024        let constellation_or_id = app.running_state.as_ref().map(|rs| match S::NAME {
1025            Name::PubhubsCentral => ConstellationOrId::Constellation(
1026                AsRef::<Constellation>::as_ref(&rs.constellation)
1027                    .clone()
1028                    .into(),
1029            ),
1030            _ => ConstellationOrId::Id {
1031                id: rs.constellation.id,
1032            },
1033        });
1034
1035        // The transcryptor publishes a hash of its master encryption key part (so PHC can commit it
1036        // to the constellation before it is able to unseal the part) and, once it shares a secret
1037        // with PHC, the part itself sealed under that secret.  PHC and AS publish neither: PHC
1038        // commits its own part's hash to the constellation directly; AS has no master key part.
1039        let master_enc_key_part_hash = if matches!(S::NAME, Name::Transcryptor)
1040            && let Some(sk) = app.master_enc_key_part()
1041        {
1042            Some(crate::phcrypto::master_enc_key_part_hash(sk.public_key()))
1043        } else {
1044            None
1045        };
1046        let master_enc_key_part_sealed = if matches!(S::NAME, Name::Transcryptor)
1047            && let Some(sk) = app.master_enc_key_part()
1048            && let Some(key) = app.master_enc_key_part_sealing_key()
1049        {
1050            Some(api::Sealed::new(
1051                &api::MasterEncKeyPart(sk.public_key().clone()),
1052                key,
1053            )?)
1054        } else {
1055            None
1056        };
1057
1058        Ok(api::DiscoveryInfoResp {
1059            name: S::NAME,
1060            version: app.version.clone(),
1061            self_check_code: app.self_check_code.clone(),
1062            phc_url: app.phc_url.clone(),
1063            verifying_key: app.shared.verifying_key_bytes.clone(),
1064            master_enc_key_part_hash,
1065            master_enc_key_part_sealed,
1066            encap_key: app.encap_key().cloned(),
1067            constellation_or_id,
1068        })
1069    }
1070}
1071
1072/// An [`App`] together with a method on it.  Used to pass [`App`]s to [`actix_web::Handler`]s
1073/// as first argument. See [`api::EndpointDetails::add_to`].
1074pub struct AppMethod<App, F, EP: ?Sized> {
1075    app: Rc<App>,
1076    f: F,
1077    phantom: std::marker::PhantomData<EP>,
1078}
1079
1080/// Implement [`Clone`] manually so we don't have to require `EP` to implement
1081/// [`Clone`].
1082impl<App, F: Clone, EP> Clone for AppMethod<App, F, EP> {
1083    fn clone(&self) -> Self {
1084        Self {
1085            app: self.app.clone(),
1086            f: self.f.clone(),
1087            phantom: std::marker::PhantomData,
1088        }
1089    }
1090}
1091
1092impl<App, F, EP: ?Sized> AppMethod<App, F, EP> {
1093    /// Creates a new [`AppMethod`], cloning [`App`].
1094    pub fn new(app: &Rc<App>, f: F) -> Self {
1095        AppMethod {
1096            app: app.clone(),
1097            f,
1098            phantom: std::marker::PhantomData,
1099        }
1100    }
1101}
1102
1103/// Implements [`actix_web::Handler`] for an [`AppMethod`] with the given number of arguments.
1104///
1105/// Based on [`actix_web`]'s implementation of [`actix_web::Handler`] for [`Fn`]s.
1106macro_rules! factory_tuple ({ $($param:ident)* } => {
1107    impl<Func, Fut, App, EP, $($param,)*> actix_web::Handler<($($param,)*)> for AppMethod<App, Func, EP>
1108    where
1109        Func: Fn(Rc<App>, $($param),*) -> Fut + Clone + 'static,
1110        Fut: core::future::Future,
1111        App: 'static,
1112        EP : EndpointDetails + 'static,
1113        Fut::Output : Into<EP::ResponseType>,
1114    {
1115        type Output = api::Responder<EP>;
1116        type Future = futures::future::Map<Fut, fn(Fut::Output)->api::Responder<EP>>;
1117
1118        #[inline]
1119        // allow(non_snake_case), because the signature will be:  call(&self, A: A, B: B, ...)
1120        // not expect(...), because this macro definition does not fulfill this condition
1121        #[allow(non_snake_case)]
1122        fn call(&self, ($($param,)*): ($($param,)*)) -> Self::Future {
1123            (self.f)(self.app.clone(), $($param,)*).map(response_type_to_responder)
1124        }
1125    }
1126});
1127
1128/// Helper method for [`factory_tuple`] macro.
1129fn response_type_to_responder<EP: EndpointDetails, T: Into<EP::ResponseType>>(
1130    output: T,
1131) -> api::Responder<EP> {
1132    api::Responder(output.into())
1133}
1134
1135factory_tuple! {}
1136factory_tuple! { A }
1137factory_tuple! { A B }
1138factory_tuple! { A B C }
1139factory_tuple! { A B C D }
1140factory_tuple! { A B C D E }
1141factory_tuple! { A B C D E F }
1142factory_tuple! { A B C D E F G }
1143factory_tuple! { A B C D E F G H }
1144factory_tuple! { A B C D E F G H I }
1145factory_tuple! { A B C D E F G H I J }
1146factory_tuple! { A B C D E F G H I J K }
1147factory_tuple! { A B C D E F G H I J K L }
1148factory_tuple! { A B C D E F G H I J K L M }
1149factory_tuple! { A B C D E F G H I J K L M N }
1150factory_tuple! { A B C D E F G H I J K L M N O }
1151factory_tuple! { A B C D E F G H I J K L M N O P }
1152
1153/// Additional state when discovery has been completed.  Derefs to `Extra`.
1154#[derive(Clone, Debug)]
1155pub struct RunningState<Extra: Clone + core::fmt::Debug> {
1156    pub constellation: Box<Constellation>,
1157
1158    /// PHC's hybrid verifying key, decoded once here from `constellation.phc_verifying_key` rather
1159    /// than re-parsing the ML-DSA key on every request that opens a PHC-signed ticket or package.
1160    pub phc_verifying_key: api::VerifyingKey,
1161
1162    /// Accessible via [`Deref`].
1163    extra: Extra,
1164}
1165
1166impl<Extra: Clone + core::fmt::Debug> RunningState<Extra> {
1167    pub(crate) fn new(constellation: Constellation, extra: Extra) -> Result<Self> {
1168        let phc_verifying_key = constellation
1169            .phc_verifying_key
1170            .decode()
1171            .map_err(|_| anyhow::anyhow!("constellation's phc_verifying_key does not decode"))?;
1172
1173        Ok(RunningState {
1174            constellation: Box::new(constellation),
1175            phc_verifying_key,
1176            extra,
1177        })
1178    }
1179}
1180
1181impl<Extra: Clone + core::fmt::Debug> Deref for RunningState<Extra> {
1182    type Target = Extra;
1183
1184    #[inline]
1185    fn deref(&self) -> &Extra {
1186        &self.extra
1187    }
1188}
1189
1190/// Shared state between [`App`]s.  Use sparingly!
1191pub struct SharedState<S: Server> {
1192    inner: std::sync::Arc<SharedStateInner<S>>,
1193}
1194
1195impl<S: Server> Clone for SharedState<S> {
1196    fn clone(&self) -> Self {
1197        Self {
1198            inner: self.inner.clone(),
1199        }
1200    }
1201}
1202
1203impl<S: Server> std::ops::Deref for SharedState<S> {
1204    type Target = SharedStateInner<S>;
1205
1206    #[inline]
1207    fn deref(&self) -> &Self::Target {
1208        &self.inner
1209    }
1210}
1211
1212impl<S: Server> SharedState<S> {
1213    fn new(inner: SharedStateInner<S>) -> Self {
1214        Self {
1215            inner: std::sync::Arc::new(inner),
1216        }
1217    }
1218}
1219
1220pub struct SharedStateInner<S: Server> {
1221    pub object_store: S::ObjectStoreT,
1222
1223    /// Decoded once and shared by all apps: a `PqdsaKeyPair` is expensive to construct, and it's
1224    /// `Sync`.
1225    pub signing_key: api::SigningKey,
1226
1227    /// Cached verifying-key encoding, so we don't re-encode it on every discovery poll /
1228    /// `check_constellation`.
1229    pub verifying_key_bytes: api::VerifyingKeyBytes,
1230
1231    pub extra: S::ExtraSharedState,
1232}
1233
1234impl<S: Server> std::ops::Deref for SharedStateInner<S> {
1235    type Target = S::ExtraSharedState;
1236
1237    #[inline]
1238    fn deref(&self) -> &Self::Target {
1239        &self.extra
1240    }
1241}