Skip to main content

xmtp_mls/utils/
mod.rs

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