Skip to main content

xmtp_db/encrypted_store/
server_configuration.rs

1//! The one row that binds this database to one backend deployment.
2//!
3//! Spec 006 ยง6.2. The row holds the deployment identifier, the URL the copy
4//! came from, the serialized response, and when it was fetched. A conflicting
5//! identifier is recorded once, by the conflict path only, and is never
6//! cleared (CFG-053).
7
8use crate::encrypted_store::schema::server_configuration;
9use crate::schema::server_configuration::dsl;
10use crate::{ConnectionExt, DbConnection, StorageError};
11use diesel::prelude::*;
12use serde::{Deserialize, Serialize};
13
14/// The single stored row. `id` is always zero; the table's check constraint
15/// enforces it.
16#[derive(Insertable, Queryable, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17#[diesel(table_name = server_configuration)]
18pub struct StoredServerConfiguration {
19    pub id: i32,
20    pub identifier: String,
21    pub backend_url: String,
22    /// The serialized `GetConfigurationResponse`, stored whole.
23    pub response: Vec<u8>,
24    pub fetched_at_ns: i64,
25    /// An identifier that did not match `identifier`. Set once, never cleared.
26    pub conflicting_identifier: Option<String>,
27}
28
29pub trait QueryServerConfiguration {
30    /// The stored copy, or `None` when this database has never held one.
31    fn server_configuration(&self) -> Result<Option<StoredServerConfiguration>, StorageError>;
32
33    /// Write the copy whole: identifier, URL, response, and fetch time. Never
34    /// touches `conflicting_identifier`, so a matching refresh cannot erase a
35    /// recorded conflict (CFG-053).
36    fn store_server_configuration(
37        &self,
38        identifier: &str,
39        backend_url: &str,
40        response: &[u8],
41        fetched_at_ns: i64,
42    ) -> Result<(), StorageError>;
43
44    /// Record that the deployment answered with a different identifier
45    /// (CFG-051). Does nothing when no row exists yet.
46    fn record_server_configuration_conflict(
47        &self,
48        conflicting_identifier: &str,
49    ) -> Result<(), StorageError>;
50}
51
52impl<T> QueryServerConfiguration for &T
53where
54    T: QueryServerConfiguration,
55{
56    fn server_configuration(&self) -> Result<Option<StoredServerConfiguration>, StorageError> {
57        (**self).server_configuration()
58    }
59
60    fn store_server_configuration(
61        &self,
62        identifier: &str,
63        backend_url: &str,
64        response: &[u8],
65        fetched_at_ns: i64,
66    ) -> Result<(), StorageError> {
67        (**self).store_server_configuration(identifier, backend_url, response, fetched_at_ns)
68    }
69
70    fn record_server_configuration_conflict(
71        &self,
72        conflicting_identifier: &str,
73    ) -> Result<(), StorageError> {
74        (**self).record_server_configuration_conflict(conflicting_identifier)
75    }
76}
77
78impl<C: ConnectionExt> QueryServerConfiguration for DbConnection<C> {
79    fn server_configuration(&self) -> Result<Option<StoredServerConfiguration>, StorageError> {
80        Ok(self.raw_query(|conn| {
81            dsl::server_configuration
82                .first::<StoredServerConfiguration>(conn)
83                .optional()
84        })?)
85    }
86
87    fn store_server_configuration(
88        &self,
89        identifier: &str,
90        backend_url: &str,
91        response: &[u8],
92        fetched_at_ns: i64,
93    ) -> Result<(), StorageError> {
94        self.raw_query(|conn| {
95            diesel::insert_into(dsl::server_configuration)
96                .values((
97                    dsl::id.eq(0),
98                    dsl::identifier.eq(identifier),
99                    dsl::backend_url.eq(backend_url),
100                    dsl::response.eq(response),
101                    dsl::fetched_at_ns.eq(fetched_at_ns),
102                ))
103                .on_conflict(dsl::id)
104                .do_update()
105                .set((
106                    dsl::identifier.eq(identifier),
107                    dsl::backend_url.eq(backend_url),
108                    dsl::response.eq(response),
109                    dsl::fetched_at_ns.eq(fetched_at_ns),
110                ))
111                .execute(conn)
112        })?;
113        Ok(())
114    }
115
116    fn record_server_configuration_conflict(
117        &self,
118        conflicting_identifier: &str,
119    ) -> Result<(), StorageError> {
120        self.raw_query(|conn| {
121            diesel::update(dsl::server_configuration)
122                .set(dsl::conflicting_identifier.eq(conflicting_identifier))
123                .execute(conn)
124        })?;
125        Ok(())
126    }
127}
128
129#[cfg(test)]
130mod tests;