xmtp_mls_common/app_data/
protocol_floor.rs1use openmls::group::MlsGroup as OpenMlsGroup;
4
5use super::component_id::ComponentId;
6use crate::libxmtp_version::LibXMTPVersion;
7
8pub fn committed_floor_exceeding(mls_group: &OpenMlsGroup, own: &LibXMTPVersion) -> Option<String> {
29 committed_floor_exceeding_in_extensions(mls_group.extensions(), own)
30}
31
32pub fn committed_floor_exceeding_in_extensions(
36 extensions: &openmls::extensions::Extensions<openmls::group::GroupContext>,
37 own: &LibXMTPVersion,
38) -> Option<String> {
39 let bytes = extensions
40 .app_data_dictionary()?
41 .dictionary()
42 .get(&ComponentId::MIN_SUPPORTED_PROTOCOL_VERSION.as_u16())?
43 .to_vec();
44 let floor = String::from_utf8(bytes).ok()?;
45 let floor_version = LibXMTPVersion::parse(&floor).ok()?;
46 (floor_version > *own).then_some(floor)
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52 use openmls::extensions::{
53 AppDataDictionary, AppDataDictionaryExtension, Extension, Extensions,
54 };
55
56 fn extensions_with_dict(
57 entries: &[(u16, Vec<u8>)],
58 ) -> Extensions<openmls::group::GroupContext> {
59 let mut dict = AppDataDictionary::new();
60 for (id, bytes) in entries {
61 let _ = dict.insert(*id, bytes.clone());
62 }
63 Extensions::from_vec(vec![Extension::AppDataDictionary(
64 AppDataDictionaryExtension::new(dict),
65 )])
66 .expect("AppDataDictionary is a valid GroupContext extension")
67 }
68
69 fn empty_extensions() -> Extensions<openmls::group::GroupContext> {
70 Extensions::from_vec(vec![]).expect("empty extensions are always valid")
71 }
72
73 fn ver(s: &str) -> LibXMTPVersion {
77 LibXMTPVersion::parse(s).unwrap()
78 }
79
80 #[xmtp_common::test(unwrap_try = true)]
91 fn floor_above_own_version_fires() {
92 let exts = extensions_with_dict(&[(
93 ComponentId::MIN_SUPPORTED_PROTOCOL_VERSION.as_u16(),
94 b"2.0.0".to_vec(),
95 )]);
96 assert_eq!(
97 committed_floor_exceeding_in_extensions(&exts, &ver("1.11.0")),
98 Some("2.0.0".to_string())
99 );
100 let exts = extensions_with_dict(&[(
103 ComponentId::MIN_SUPPORTED_PROTOCOL_VERSION.as_u16(),
104 b"1.11.0-dev".to_vec(),
105 )]);
106 assert_eq!(
107 committed_floor_exceeding_in_extensions(&exts, &ver("1.10.0")),
108 Some("1.11.0-dev".to_string())
109 );
110 assert_eq!(
111 committed_floor_exceeding_in_extensions(&exts, &ver("1.11.0")),
112 None
113 );
114 }
115
116 #[xmtp_common::test(unwrap_try = true)]
117 fn floor_at_or_below_own_version_does_not_fire() {
118 let exts = extensions_with_dict(&[(
119 ComponentId::MIN_SUPPORTED_PROTOCOL_VERSION.as_u16(),
120 b"1.11.0".to_vec(),
121 )]);
122 assert_eq!(
124 committed_floor_exceeding_in_extensions(&exts, &ver("1.11.0")),
125 None
126 );
127 assert_eq!(
129 committed_floor_exceeding_in_extensions(&exts, &ver("1.12.0")),
130 None
131 );
132 }
133
134 #[xmtp_common::test(unwrap_try = true)]
135 fn missing_floor_or_dict_does_not_fire() {
136 assert_eq!(
137 committed_floor_exceeding_in_extensions(&empty_extensions(), &ver("1.11.0")),
138 None
139 );
140 assert_eq!(
141 committed_floor_exceeding_in_extensions(&extensions_with_dict(&[]), &ver("1.11.0")),
142 None
143 );
144 let exts = extensions_with_dict(&[(ComponentId::GROUP_NAME.as_u16(), b"name".to_vec())]);
146 assert_eq!(
147 committed_floor_exceeding_in_extensions(&exts, &ver("1.11.0")),
148 None
149 );
150 }
151
152 #[xmtp_common::test(unwrap_try = true)]
153 fn malformed_floor_is_lenient() {
154 let exts = extensions_with_dict(&[(
156 ComponentId::MIN_SUPPORTED_PROTOCOL_VERSION.as_u16(),
157 vec![0xFF, 0xFE],
158 )]);
159 assert_eq!(
160 committed_floor_exceeding_in_extensions(&exts, &ver("1.11.0")),
161 None
162 );
163 let exts = extensions_with_dict(&[(
165 ComponentId::MIN_SUPPORTED_PROTOCOL_VERSION.as_u16(),
166 b"not-a-version".to_vec(),
167 )]);
168 assert_eq!(
169 committed_floor_exceeding_in_extensions(&exts, &ver("1.11.0")),
170 None
171 );
172 }
176}