Skip to main content

xmtp_mls/utils/
mod.rs

1use std::sync::Arc;
2
3use crate::groups::validated_commit::LibXMTPVersion;
4
5#[cfg(feature = "bench")]
6pub mod bench;
7pub mod cleanup_duplicate_updates;
8#[cfg(any(test, feature = "test-utils"))]
9pub mod test;
10
11#[cfg(any(test, feature = "test-utils"))]
12pub use self::test::*;
13
14pub mod hash {
15    pub use xmtp_cryptography::hash::sha256_bytes as sha256;
16}
17
18pub mod time {
19    /// Current hmac epoch. HMAC keys change every 30 days
20    pub fn hmac_epoch() -> i64 {
21        xmtp_push_types::hmac_epoch(xmtp_common::time::now_secs())
22    }
23}
24
25pub mod id {
26    use xmtp_db::group_intent::IntentKind;
27
28    use crate::groups::intents::{IntentError, SendMessageIntentData};
29    use prost::Message;
30    use xmtp_proto::xmtp::mls::message_contents::plaintext_envelope::Content;
31    use xmtp_proto::xmtp::mls::message_contents::{PlaintextEnvelope, plaintext_envelope::V1};
32
33    /// Relies on a client-created idempotency_key (which could be a timestamp)
34    pub fn calculate_message_id(
35        group_id: impl AsRef<[u8]>,
36        decrypted_message_bytes: &[u8],
37        idempotency_key: &str,
38    ) -> Vec<u8> {
39        let separator = b"\t";
40        let mut id_vec = Vec::new();
41        id_vec.extend_from_slice(group_id.as_ref());
42        id_vec.extend_from_slice(separator);
43        id_vec.extend_from_slice(idempotency_key.as_bytes());
44        id_vec.extend_from_slice(separator);
45        id_vec.extend_from_slice(decrypted_message_bytes);
46        super::hash::sha256(&id_vec)
47    }
48
49    /// Calculate the message id for this intent.
50    ///
51    /// # Note
52    /// This functions deserializes and decodes a [`PlaintextEnvelope`] from encoded bytes.
53    /// It would be costly to call this method while pulling extra data from a
54    /// [`PlaintextEnvelope`] elsewhere. The caller should consider combining implementations.
55    ///
56    /// # Returns
57    /// Returns [`Option::None`] if `StoredGroupIntent` is not [`IntentKind::SendMessage`] or if
58    /// an error occurs during decoding of intent data for [`IntentKind::SendMessage`].
59    pub fn calculate_message_id_for_intent(
60        intent: &xmtp_db::group_intent::StoredGroupIntent,
61    ) -> Result<Option<Vec<u8>>, IntentError> {
62        if intent.kind != IntentKind::SendMessage {
63            return Ok(None);
64        }
65
66        let data = SendMessageIntentData::from_bytes(&intent.data)?;
67        let envelope: PlaintextEnvelope = PlaintextEnvelope::decode(data.message.as_slice())?;
68
69        // optimistic message should always have a plaintext envelope
70        let PlaintextEnvelope {
71            content:
72                Some(Content::V1(V1 {
73                    content: message,
74                    idempotency_key: key,
75                })),
76        } = envelope
77        else {
78            return Ok(None);
79        };
80
81        Ok(Some(calculate_message_id(intent.group_id, &message, &key)))
82    }
83}
84
85#[derive(Clone, Debug, PartialEq)]
86pub struct VersionInfo {
87    pkg_version: Arc<str>,
88    /// `pkg_version` parsed once at construction. This is the client's own
89    /// build identity, not remote data, so it is always valid semver
90    /// (see [`VersionInfo::new`]) — receive-path guards can compare against
91    /// it without re-parsing the string on every message.
92    pkg_semver: LibXMTPVersion,
93}
94
95impl Default for VersionInfo {
96    fn default() -> Self {
97        Self::new(env!("CARGO_PKG_VERSION"))
98    }
99}
100
101impl VersionInfo {
102    /// Build a `VersionInfo`, parsing and caching the semver form.
103    ///
104    /// `version` is the client's own package version — in production always
105    /// the compile-time `CARGO_PKG_VERSION`. A non-semver value is a build
106    /// or configuration bug, not runtime data, so it panics here rather than
107    /// silently disabling the below-floor pause on every group later.
108    fn new(version: &str) -> Self {
109        let pkg_semver = LibXMTPVersion::parse(version)
110            .unwrap_or_else(|_| panic!("client pkg_version {version:?} is not valid semver"));
111        Self {
112            pkg_version: version.into(),
113            pkg_semver,
114        }
115    }
116
117    pub fn pkg_version(&self) -> &str {
118        &self.pkg_version
119    }
120
121    /// The client's own version, parsed once at construction. Prefer this
122    /// over re-parsing [`pkg_version`](Self::pkg_version) on hot paths.
123    pub fn pkg_semver(&self) -> &LibXMTPVersion {
124        &self.pkg_semver
125    }
126
127    // Test only function to update the version of the client
128    #[cfg(test)]
129    pub fn test_update_version(&mut self, version: &str) {
130        *self = Self::new(version);
131    }
132}