1use std::{borrow::Borrow, fmt, ops::Deref, str::FromStr};
2
3#[cfg(feature = "diesel")]
4use diesel::{
5 backend::Backend,
6 deserialize::{self, FromSql, FromSqlRow},
7 expression::AsExpression,
8 serialize::{self, IsNull, Output, ToSql},
9 sql_types::Binary,
10 sqlite::Sqlite,
11};
12use serde::{Deserialize, Serialize};
13
14use crate::ConversionError;
15
16#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
18#[cfg_attr(feature = "diesel", derive(AsExpression, FromSqlRow))]
19#[cfg_attr(feature = "diesel", diesel(sql_type = Binary))]
20pub struct GroupId([u8; 16]);
21
22impl GroupId {
23 pub const ZERO: GroupId = GroupId([0u8; 16]);
25 pub const ONE: GroupId = GroupId([1u8; 16]);
27 pub const TWO: GroupId = GroupId([2u8; 16]);
29 pub const THREE: GroupId = GroupId([3u8; 16]);
31 pub const FOUR: GroupId = GroupId([4u8; 16]);
33
34 pub fn as_slice(&self) -> &[u8] {
36 &self.0
37 }
38
39 pub fn as_bytes(&self) -> &[u8; 16] {
41 &self.0
42 }
43
44 pub fn into_bytes(self) -> [u8; 16] {
46 self.0
47 }
48
49 pub fn to_vec(self) -> Vec<u8> {
51 self.0.to_vec()
52 }
53
54 pub fn to_openmls(self) -> openmls::group::GroupId {
56 openmls::group::GroupId::from_slice(&self.0)
57 }
58
59 pub fn random<R: openmls_traits::random::OpenMlsRand>(rand: &R) -> Self {
61 let mut bytes = [0u8; 16];
62 let v = rand
63 .random_vec(16)
64 .expect("OpenMlsRand failed to produce randomness for GroupId");
65 bytes.copy_from_slice(&v);
66 GroupId(bytes)
67 }
68}
69
70impl Deref for GroupId {
73 type Target = [u8; 16];
74
75 fn deref(&self) -> &Self::Target {
76 &self.0
77 }
78}
79
80impl<T> AsRef<T> for GroupId
81where
82 T: ?Sized,
83 <GroupId as Deref>::Target: AsRef<T>,
84{
85 fn as_ref(&self) -> &T {
86 self.deref().as_ref()
87 }
88}
89
90impl From<[u8; 16]> for GroupId {
93 fn from(v: [u8; 16]) -> Self {
94 GroupId(v)
95 }
96}
97
98impl From<&[u8; 16]> for GroupId {
99 fn from(v: &[u8; 16]) -> Self {
100 GroupId(*v)
101 }
102}
103
104impl From<GroupId> for [u8; 16] {
105 fn from(v: GroupId) -> Self {
106 v.0
107 }
108}
109
110impl TryFrom<Vec<u8>> for GroupId {
113 type Error = ConversionError;
114 fn try_from(v: Vec<u8>) -> Result<Self, Self::Error> {
115 Ok(GroupId(v.as_slice().try_into()?))
116 }
117}
118
119impl TryFrom<&[u8]> for GroupId {
120 type Error = ConversionError;
121 fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
122 let bytes: [u8; 16] = v.try_into()?;
123 Ok(GroupId(bytes))
124 }
125}
126
127impl TryFrom<&openmls::group::GroupId> for GroupId {
128 type Error = ConversionError;
129 fn try_from(id: &openmls::group::GroupId) -> Result<Self, Self::Error> {
130 GroupId::try_from(id.as_slice())
131 }
132}
133
134impl TryFrom<openmls::group::GroupId> for GroupId {
135 type Error = ConversionError;
136 fn try_from(id: openmls::group::GroupId) -> Result<Self, Self::Error> {
137 GroupId::try_from(id.as_slice())
138 }
139}
140
141impl From<GroupId> for Vec<u8> {
144 fn from(id: GroupId) -> Vec<u8> {
145 id.0.to_vec()
146 }
147}
148
149impl Borrow<[u8; 16]> for GroupId {
150 fn borrow(&self) -> &[u8; 16] {
151 &self.0
152 }
153}
154
155impl fmt::Display for GroupId {
158 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159 write!(f, "{}", hex::encode(self.0))
160 }
161}
162
163impl fmt::Debug for GroupId {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 f.debug_tuple("GroupId")
166 .field(&xmtp_common::fmt::debug_hex(self.0))
167 .finish()
168 }
169}
170
171#[derive(Debug, thiserror::Error)]
175pub enum GroupIdParseError {
176 #[error(transparent)]
178 Hex(#[from] hex::FromHexError),
179 #[error(transparent)]
181 Length(#[from] ConversionError),
182}
183
184impl FromStr for GroupId {
185 type Err = GroupIdParseError;
186 fn from_str(s: &str) -> Result<Self, Self::Err> {
187 let bytes = hex::decode(s)?;
188 Ok(GroupId::try_from(bytes)?)
189 }
190}
191
192impl PartialEq<Vec<u8>> for GroupId {
195 fn eq(&self, other: &Vec<u8>) -> bool {
196 self.0.eq(&other[..])
197 }
198}
199
200impl PartialEq<GroupId> for Vec<u8> {
201 fn eq(&self, other: &GroupId) -> bool {
202 other.0.eq(&self[..])
203 }
204}
205
206impl PartialEq<&Vec<u8>> for GroupId {
207 fn eq(&self, other: &&Vec<u8>) -> bool {
208 self.0.eq(&other[..])
209 }
210}
211
212impl PartialEq<GroupId> for &Vec<u8> {
213 fn eq(&self, other: &GroupId) -> bool {
214 other.0.eq(&self[..])
215 }
216}
217
218impl PartialEq<[u8]> for GroupId {
219 fn eq(&self, other: &[u8]) -> bool {
220 self.0.eq(other)
221 }
222}
223
224impl PartialEq<GroupId> for [u8] {
225 fn eq(&self, other: &GroupId) -> bool {
226 other.0.eq(self)
227 }
228}
229
230impl PartialEq<[u8; 16]> for GroupId {
231 fn eq(&self, other: &[u8; 16]) -> bool {
232 self.0.eq(other)
233 }
234}
235
236impl PartialEq<GroupId> for [u8; 16] {
237 fn eq(&self, other: &GroupId) -> bool {
238 other.0.eq(&self[..])
239 }
240}
241
242impl Serialize for GroupId {
245 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
246 self.0.as_ref().serialize(s)
247 }
248}
249
250impl<'de> Deserialize<'de> for GroupId {
251 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
252 let v = Vec::<u8>::deserialize(d)?;
253 GroupId::try_from(v).map_err(serde::de::Error::custom)
254 }
255}
256
257#[cfg(feature = "diesel")]
260impl ToSql<Binary, Sqlite> for GroupId
261where
262 [u8]: ToSql<Binary, Sqlite>,
263{
264 fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
265 out.set_value(self.0.to_vec());
266 Ok(IsNull::No)
267 }
268}
269
270#[cfg(feature = "diesel")]
271impl FromSql<Binary, Sqlite> for GroupId
272where
273 Vec<u8>: FromSql<Binary, Sqlite>,
274{
275 fn from_sql(bytes: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
276 let v = Vec::<u8>::from_sql(bytes)?;
277 GroupId::try_from(v).map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
278 }
279}
280
281xmtp_common::if_test! {
284 impl xmtp_common::Generate for GroupId {
285 fn generate() -> Self {
286 GroupId(xmtp_common::rand_array::<16>())
287 }
288 }
289}
290
291#[cfg(test)]
294mod test {
295 use rstest::rstest;
296
297 use super::*;
298
299 #[rstest]
300 #[case([0u8; 16])]
301 #[case([0xffu8; 16])]
302 #[case([1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])]
303 #[xmtp_common::test(unwrap_try = true)]
304 async fn test_group_id_from_array(#[case] input: [u8; 16]) {
305 let id = GroupId::from(input);
306 assert_eq!(id.as_slice(), &input);
307 assert_eq!(id.as_bytes(), &input);
308 assert_eq!(id.into_bytes(), input);
309 assert_eq!(GroupId::from(&input).as_slice(), &input);
310 assert_eq!(GroupId::from(input).to_vec(), input.to_vec());
311 }
312
313 #[rstest]
314 #[case(vec![1u8; 16], true)]
315 #[case(vec![1u8; 15], false)]
316 #[case(vec![1u8; 17], false)]
317 #[case(Vec::new(), false)]
318 #[xmtp_common::test(unwrap_try = true)]
319 async fn test_group_id_try_from_vec(#[case] input: Vec<u8>, #[case] ok: bool) {
320 assert_eq!(GroupId::try_from(input).is_ok(), ok);
321 }
322
323 #[rstest]
324 #[case(&[1u8; 16][..], true)]
325 #[case(&[1u8; 15][..], false)]
326 #[case(&[1u8; 17][..], false)]
327 #[case(&[][..], false)]
328 #[xmtp_common::test(unwrap_try = true)]
329 async fn test_group_id_try_from_slice(#[case] input: &[u8], #[case] ok: bool) {
330 assert_eq!(GroupId::try_from(input).is_ok(), ok);
331 }
332
333 #[xmtp_common::test(unwrap_try = true)]
334 fn test_openmls_try_from_valid() {
335 let bytes: [u8; 16] = xmtp_common::rand_array::<16>();
336 let ommls = openmls::group::GroupId::from_slice(&bytes);
337 let xmtp_id = GroupId::try_from(&ommls).unwrap();
338 assert_eq!(xmtp_id.as_slice(), &bytes);
339
340 let ommls_owned = openmls::group::GroupId::from_slice(&bytes);
341 let xmtp_id_owned = GroupId::try_from(ommls_owned).unwrap();
342 assert_eq!(xmtp_id_owned.as_slice(), &bytes);
343 }
344
345 #[xmtp_common::test(unwrap_try = true)]
346 fn test_openmls_try_from_wrong_length() {
347 let short = openmls::group::GroupId::from_slice(&[1u8; 8]);
348 assert!(GroupId::try_from(&short).is_err());
349
350 let long = openmls::group::GroupId::from_slice(&[1u8; 32]);
351 assert!(GroupId::try_from(long).is_err());
352 }
353
354 #[xmtp_common::test(unwrap_try = true)]
355 fn test_to_openmls_roundtrip() {
356 let bytes: [u8; 16] = xmtp_common::rand_array::<16>();
357 let xmtp_id = GroupId::from(bytes);
358 let ommls_id = xmtp_id.to_openmls();
359 assert_eq!(ommls_id.as_slice(), xmtp_id.as_slice());
360 }
361
362 #[xmtp_common::test(unwrap_try = true)]
363 fn test_fromstr_success() {
364 let bytes: [u8; 16] = xmtp_common::rand_array::<16>();
365 let hex = hex::encode(bytes);
366 let id: GroupId = hex.parse().unwrap();
367 assert_eq!(id.as_slice(), &bytes);
368 }
369
370 #[xmtp_common::test(unwrap_try = true)]
371 fn test_fromstr_bad_hex() {
372 let err = "zz".parse::<GroupId>().unwrap_err();
373 assert!(matches!(err, GroupIdParseError::Hex(_)));
374 }
375
376 #[xmtp_common::test(unwrap_try = true)]
377 fn test_fromstr_wrong_length() {
378 let err = "abcdef".parse::<GroupId>().unwrap_err();
380 assert!(matches!(err, GroupIdParseError::Length(_)));
381 }
382
383 #[rstest]
384 #[case(GroupId::from([1u8; 16]), vec![1u8; 16], true)]
385 #[case(GroupId::from([1u8; 16]), vec![2u8; 16], false)]
386 #[case(GroupId::from([1u8; 16]), vec![1u8; 15], false)] #[xmtp_common::test(unwrap_try = true)]
388 async fn test_group_id_eq_vec(#[case] id: GroupId, #[case] v: Vec<u8>, #[case] equal: bool) {
389 #[allow(clippy::op_ref)]
392 {
393 assert_eq!(id == v, equal);
394 assert_eq!(v == id, equal);
395 assert_eq!(id == &v, equal);
396 assert_eq!(&v == id, equal);
397 }
398 }
399
400 #[rstest]
401 #[case(GroupId::from([1u8; 16]), [1u8; 16], true)]
402 #[case(GroupId::from([1u8; 16]), [2u8; 16], false)]
403 #[xmtp_common::test(unwrap_try = true)]
404 async fn test_group_id_eq_array(#[case] id: GroupId, #[case] a: [u8; 16], #[case] equal: bool) {
405 assert_eq!(id == a, equal);
406 assert_eq!(a == id, equal);
407 }
408
409 #[xmtp_common::test(unwrap_try = true)]
410 async fn test_group_id_eq_slice() {
411 let id = GroupId::from([1u8; 16]);
412 let s: &[u8] = &[1u8; 16];
413 assert_eq!(id, *s);
414 assert_eq!(*s, id);
415
416 let wrong_len: &[u8] = &[1u8; 8];
417 assert_ne!(id, *wrong_len);
418 assert_ne!(*wrong_len, id);
419 }
420
421 #[xmtp_common::test(unwrap_try = true)]
422 fn test_serde_roundtrip() {
423 let id = GroupId::from([7u8; 16]);
424 let bytes = bincode::serialize(&id).unwrap();
425 let decoded: GroupId = bincode::deserialize(&bytes).unwrap();
426 assert_eq!(decoded, id);
427 }
428
429 #[xmtp_common::test(unwrap_try = true)]
430 fn test_serde_wrong_length_fails() {
431 let bad: Vec<u8> = vec![1, 2, 3, 4, 5, 6, 7, 8];
433 let bytes = bincode::serialize(&bad).unwrap();
434 assert!(bincode::deserialize::<GroupId>(&bytes).is_err());
435 }
436
437 #[xmtp_common::test(unwrap_try = true)]
438 fn test_generate_produces_16_bytes() {
439 use xmtp_common::Generate;
440 let id: GroupId = GroupId::generate();
441 assert_eq!(id.as_slice().len(), 16);
442 }
443
444 #[xmtp_common::test(unwrap_try = true)]
445 fn test_default_is_zero() {
446 let id = GroupId::default();
447 assert_eq!(id.as_slice(), &[0u8; 16][..]);
448 }
449
450 #[xmtp_common::test(unwrap_try = true)]
451 fn test_const_helpers() {
452 assert_eq!(GroupId::ZERO, GroupId::default());
453 assert_eq!(GroupId::ZERO.as_bytes(), &[0u8; 16]);
454 assert_eq!(GroupId::ONE.as_bytes(), &[1u8; 16]);
455 assert_eq!(GroupId::TWO.as_bytes(), &[2u8; 16]);
456 assert_eq!(GroupId::THREE.as_bytes(), &[3u8; 16]);
457 assert_eq!(GroupId::FOUR.as_bytes(), &[4u8; 16]);
458 }
459
460 #[xmtp_common::test(unwrap_try = true)]
461 fn test_display_debug() {
462 let id = GroupId::from([0x12, 0x34, 0xab, 0xcd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
463 let displayed = format!("{}", id);
464 assert!(displayed.starts_with("1234abcd"));
465 assert_eq!(displayed.len(), 32);
466 let debugged = format!("{:?}", id);
467 assert!(debugged.starts_with("GroupId("));
468 }
469
470 #[cfg(feature = "diesel")]
471 mod diesel_test {
472 use super::*;
473 use diesel::prelude::*;
474
475 diesel::table! {
476 test_group_ids (id) {
477 id -> Binary,
478 }
479 }
480
481 #[derive(Insertable, Queryable)]
482 #[diesel(table_name = test_group_ids)]
483 struct Row {
484 id: GroupId,
485 }
486
487 #[xmtp_common::test(unwrap_try = true)]
488 fn test_diesel_roundtrip() {
489 let mut conn = SqliteConnection::establish(":memory:").unwrap();
490 diesel::sql_query("CREATE TABLE test_group_ids (id BLOB NOT NULL PRIMARY KEY)")
491 .execute(&mut conn)
492 .unwrap();
493 let id = GroupId::from([0xabu8; 16]);
494 diesel::insert_into(test_group_ids::table)
495 .values(&Row { id })
496 .execute(&mut conn)
497 .unwrap();
498 let got: Row = test_group_ids::table.first(&mut conn).unwrap();
499 assert_eq!(got.id, id);
500 }
501
502 #[xmtp_common::test(unwrap_try = true)]
503 fn test_diesel_wrong_length_errors() {
504 let mut conn = SqliteConnection::establish(":memory:").unwrap();
505 diesel::sql_query("CREATE TABLE test_group_ids (id BLOB NOT NULL PRIMARY KEY)")
506 .execute(&mut conn)
507 .unwrap();
508 diesel::sql_query("INSERT INTO test_group_ids (id) VALUES (X'0102030405060708')")
510 .execute(&mut conn)
511 .unwrap();
512 let result: Result<Row, _> = test_group_ids::table.first(&mut conn);
513 assert!(result.is_err());
514 }
515 }
516}