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;
15use xmtp_configuration::{GROUP_ID_BYTES, LEGACY_GROUP_ID_BYTES};
16
17#[derive(Copy, Clone)]
23#[cfg_attr(feature = "diesel", derive(AsExpression, FromSqlRow))]
24#[cfg_attr(feature = "diesel", diesel(sql_type = Binary))]
25pub struct GroupId {
26 len: u8,
28 bytes: [u8; GROUP_ID_BYTES],
30}
31
32const fn legacy(byte: u8) -> GroupId {
33 let mut bytes = [0u8; GROUP_ID_BYTES];
34 let mut i = 0;
35 while i < LEGACY_GROUP_ID_BYTES {
36 bytes[i] = byte;
37 i += 1;
38 }
39 GroupId {
40 len: LEGACY_GROUP_ID_BYTES as u8,
41 bytes,
42 }
43}
44
45impl GroupId {
46 pub const ZERO: GroupId = legacy(0);
48 pub const ONE: GroupId = legacy(1);
50 pub const TWO: GroupId = legacy(2);
52 pub const THREE: GroupId = legacy(3);
54 pub const FOUR: GroupId = legacy(4);
56
57 fn from_slice(v: &[u8]) -> Result<Self, ConversionError> {
58 if v.len() != GROUP_ID_BYTES && v.len() != LEGACY_GROUP_ID_BYTES {
59 return Err(ConversionError::InvalidValue {
60 item: "GroupId",
61 expected: "a 32-byte or 16-byte group id",
62 got: format!("{} bytes", v.len()),
63 });
64 }
65 let mut bytes = [0u8; GROUP_ID_BYTES];
66 bytes[..v.len()].copy_from_slice(v);
67 Ok(GroupId {
68 len: v.len() as u8,
69 bytes,
70 })
71 }
72
73 pub fn as_slice(&self) -> &[u8] {
75 &self.bytes[..self.len as usize]
76 }
77
78 #[allow(clippy::len_without_is_empty)]
80 pub fn len(&self) -> usize {
81 self.len as usize
82 }
83
84 pub fn is_legacy(&self) -> bool {
86 self.len() == LEGACY_GROUP_ID_BYTES
87 }
88
89 pub fn to_vec(self) -> Vec<u8> {
91 self.as_slice().to_vec()
92 }
93
94 pub fn to_openmls(self) -> openmls::group::GroupId {
96 openmls::group::GroupId::from_slice(self.as_slice())
97 }
98
99 pub fn random<R: openmls_traits::random::OpenMlsRand>(rand: &R) -> Self {
101 let v = rand
102 .random_vec(LEGACY_GROUP_ID_BYTES)
103 .expect("OpenMlsRand failed to produce randomness for GroupId");
104 GroupId::from_slice(&v).expect("random_vec returns the requested length")
105 }
106}
107
108impl Default for GroupId {
109 fn default() -> Self {
110 GroupId::ZERO
111 }
112}
113
114impl PartialEq for GroupId {
115 fn eq(&self, other: &Self) -> bool {
116 self.as_slice() == other.as_slice()
117 }
118}
119
120impl Eq for GroupId {}
121
122impl PartialOrd for GroupId {
123 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
124 Some(self.cmp(other))
125 }
126}
127
128impl Ord for GroupId {
129 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
130 self.as_slice().cmp(other.as_slice())
131 }
132}
133
134impl std::hash::Hash for GroupId {
136 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
137 self.as_slice().hash(state)
138 }
139}
140
141impl Deref for GroupId {
142 type Target = [u8];
143
144 fn deref(&self) -> &Self::Target {
145 self.as_slice()
146 }
147}
148
149impl AsRef<[u8]> for GroupId {
150 fn as_ref(&self) -> &[u8] {
151 self.as_slice()
152 }
153}
154
155impl From<[u8; LEGACY_GROUP_ID_BYTES]> for GroupId {
158 fn from(v: [u8; LEGACY_GROUP_ID_BYTES]) -> Self {
159 GroupId::from(&v)
160 }
161}
162
163impl From<&[u8; LEGACY_GROUP_ID_BYTES]> for GroupId {
164 fn from(v: &[u8; LEGACY_GROUP_ID_BYTES]) -> Self {
165 let mut bytes = [0u8; GROUP_ID_BYTES];
166 bytes[..LEGACY_GROUP_ID_BYTES].copy_from_slice(v);
167 GroupId {
168 len: LEGACY_GROUP_ID_BYTES as u8,
169 bytes,
170 }
171 }
172}
173
174impl From<[u8; GROUP_ID_BYTES]> for GroupId {
175 fn from(bytes: [u8; GROUP_ID_BYTES]) -> Self {
176 GroupId {
177 len: GROUP_ID_BYTES as u8,
178 bytes,
179 }
180 }
181}
182
183impl From<&[u8; GROUP_ID_BYTES]> for GroupId {
184 fn from(v: &[u8; GROUP_ID_BYTES]) -> Self {
185 GroupId::from(*v)
186 }
187}
188
189impl TryFrom<Vec<u8>> for GroupId {
192 type Error = ConversionError;
193 fn try_from(v: Vec<u8>) -> Result<Self, Self::Error> {
194 GroupId::from_slice(&v)
195 }
196}
197
198impl TryFrom<&[u8]> for GroupId {
199 type Error = ConversionError;
200 fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
201 GroupId::from_slice(v)
202 }
203}
204
205impl TryFrom<&openmls::group::GroupId> for GroupId {
206 type Error = ConversionError;
207 fn try_from(id: &openmls::group::GroupId) -> Result<Self, Self::Error> {
208 GroupId::from_slice(id.as_slice())
209 }
210}
211
212impl TryFrom<openmls::group::GroupId> for GroupId {
213 type Error = ConversionError;
214 fn try_from(id: openmls::group::GroupId) -> Result<Self, Self::Error> {
215 GroupId::from_slice(id.as_slice())
216 }
217}
218
219impl From<GroupId> for Vec<u8> {
222 fn from(id: GroupId) -> Vec<u8> {
223 id.to_vec()
224 }
225}
226
227impl Borrow<[u8]> for GroupId {
228 fn borrow(&self) -> &[u8] {
229 self.as_slice()
230 }
231}
232
233impl fmt::Display for GroupId {
236 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237 write!(f, "{}", hex::encode(self.as_slice()))
238 }
239}
240
241impl fmt::Debug for GroupId {
242 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243 f.debug_tuple("GroupId")
244 .field(&xmtp_common::fmt::debug_hex(self.as_slice()))
245 .finish()
246 }
247}
248
249#[derive(Debug, thiserror::Error)]
253pub enum GroupIdParseError {
254 #[error(transparent)]
256 Hex(#[from] hex::FromHexError),
257 #[error(transparent)]
259 Length(#[from] ConversionError),
260}
261
262impl FromStr for GroupId {
263 type Err = GroupIdParseError;
264 fn from_str(s: &str) -> Result<Self, Self::Err> {
265 if s.len() != 2 * GROUP_ID_BYTES && s.len() != 2 * LEGACY_GROUP_ID_BYTES {
266 return Err(GroupIdParseError::Length(ConversionError::InvalidValue {
267 item: "GroupId",
268 expected: "32 or 64 hexadecimal characters",
269 got: format!("{} characters", s.len()),
270 }));
271 }
272 let len = s.len() / 2;
273 let mut bytes = [0u8; GROUP_ID_BYTES];
274 hex::decode_to_slice(s, &mut bytes[..len])?;
275 Ok(GroupId::from_slice(&bytes[..len])?)
276 }
277}
278
279impl PartialEq<Vec<u8>> for GroupId {
282 fn eq(&self, other: &Vec<u8>) -> bool {
283 self.as_slice() == &other[..]
284 }
285}
286
287impl PartialEq<GroupId> for Vec<u8> {
288 fn eq(&self, other: &GroupId) -> bool {
289 other.as_slice() == &self[..]
290 }
291}
292
293impl PartialEq<&Vec<u8>> for GroupId {
294 fn eq(&self, other: &&Vec<u8>) -> bool {
295 self.as_slice() == &other[..]
296 }
297}
298
299impl PartialEq<GroupId> for &Vec<u8> {
300 fn eq(&self, other: &GroupId) -> bool {
301 other.as_slice() == &self[..]
302 }
303}
304
305impl PartialEq<[u8]> for GroupId {
306 fn eq(&self, other: &[u8]) -> bool {
307 self.as_slice() == other
308 }
309}
310
311impl PartialEq<GroupId> for [u8] {
312 fn eq(&self, other: &GroupId) -> bool {
313 other.as_slice() == self
314 }
315}
316
317impl PartialEq<[u8; LEGACY_GROUP_ID_BYTES]> for GroupId {
318 fn eq(&self, other: &[u8; LEGACY_GROUP_ID_BYTES]) -> bool {
319 self.as_slice() == &other[..]
320 }
321}
322
323impl PartialEq<GroupId> for [u8; LEGACY_GROUP_ID_BYTES] {
324 fn eq(&self, other: &GroupId) -> bool {
325 other.as_slice() == &self[..]
326 }
327}
328
329impl PartialEq<[u8; GROUP_ID_BYTES]> for GroupId {
330 fn eq(&self, other: &[u8; GROUP_ID_BYTES]) -> bool {
331 self.as_slice() == &other[..]
332 }
333}
334
335impl PartialEq<GroupId> for [u8; GROUP_ID_BYTES] {
336 fn eq(&self, other: &GroupId) -> bool {
337 other.as_slice() == &self[..]
338 }
339}
340
341impl Serialize for GroupId {
344 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
345 self.as_slice().serialize(s)
346 }
347}
348
349impl<'de> Deserialize<'de> for GroupId {
350 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
351 let v = Vec::<u8>::deserialize(d)?;
352 GroupId::try_from(v).map_err(serde::de::Error::custom)
353 }
354}
355
356#[cfg(feature = "diesel")]
359impl ToSql<Binary, Sqlite> for GroupId
360where
361 [u8]: ToSql<Binary, Sqlite>,
362{
363 fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
364 out.set_value(self.to_vec());
365 Ok(IsNull::No)
366 }
367}
368
369#[cfg(feature = "diesel")]
370impl FromSql<Binary, Sqlite> for GroupId
371where
372 Vec<u8>: FromSql<Binary, Sqlite>,
373{
374 fn from_sql(bytes: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
375 let v = Vec::<u8>::from_sql(bytes)?;
376 GroupId::try_from(v).map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
377 }
378}
379
380xmtp_common::if_test! {
383 impl xmtp_common::Generate for GroupId {
384 fn generate() -> Self {
386 GroupId::from(xmtp_common::rand_array::<{ GROUP_ID_BYTES }>())
387 }
388 }
389}
390
391#[cfg(test)]
394mod test {
395 use rstest::rstest;
396
397 use super::*;
398
399 #[rstest]
400 #[case([0u8; 16])]
401 #[case([0xffu8; 16])]
402 #[case([1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])]
403 #[xmtp_common::test(unwrap_try = true)]
404 async fn test_group_id_from_legacy_array(#[case] input: [u8; 16]) {
405 let id = GroupId::from(input);
406 assert_eq!(id.as_slice(), &input);
407 assert_eq!(id.len(), 16);
408 assert!(id.is_legacy());
409 assert_eq!(GroupId::from(&input).as_slice(), &input);
410 assert_eq!(GroupId::from(input).to_vec(), input.to_vec());
411 }
412
413 #[rstest]
414 #[case([0u8; 32])]
415 #[case([0xffu8; 32])]
416 #[xmtp_common::test(unwrap_try = true)]
417 async fn test_group_id_from_keyed_array(#[case] input: [u8; 32]) {
418 let id = GroupId::from(input);
419 assert_eq!(id.as_slice(), &input);
420 assert_eq!(id.len(), 32);
421 assert!(!id.is_legacy());
422 assert_eq!(GroupId::from(&input).as_slice(), &input);
423 assert_eq!(Vec::from(id), input.to_vec());
424 }
425
426 #[rstest]
428 #[case(vec![1u8; 16], true)]
429 #[case(vec![1u8; 32], true)]
430 #[case(vec![1u8; 8], false)]
431 #[case(vec![1u8; 15], false)]
432 #[case(vec![1u8; 17], false)]
433 #[case(vec![1u8; 31], false)]
434 #[case(vec![1u8; 33], false)]
435 #[case(Vec::new(), false)]
436 #[xmtp_common::test(unwrap_try = true)]
437 async fn test_group_id_try_from_vec(#[case] input: Vec<u8>, #[case] ok: bool) {
438 assert_eq!(GroupId::try_from(input.clone()).is_ok(), ok);
439 assert_eq!(GroupId::try_from(input.as_slice()).is_ok(), ok);
440 if ok {
441 assert_eq!(
442 GroupId::try_from(input.clone()).unwrap().as_slice(),
443 input.as_slice()
444 );
445 }
446 }
447
448 #[rstest]
449 #[case(16)]
450 #[case(32)]
451 #[xmtp_common::test(unwrap_try = true)]
452 fn test_openmls_roundtrip(#[case] len: usize) {
453 let bytes = xmtp_common::rand_vec::<64>()[..len].to_vec();
454 let ommls = openmls::group::GroupId::from_slice(&bytes);
455 let xmtp_id = GroupId::try_from(&ommls).unwrap();
456 assert_eq!(xmtp_id.as_slice(), &bytes[..]);
457 assert_eq!(GroupId::try_from(ommls).unwrap().as_slice(), &bytes[..]);
458 assert_eq!(xmtp_id.to_openmls().as_slice(), &bytes[..]);
459 }
460
461 #[rstest]
462 #[case(8)]
463 #[case(33)]
464 #[xmtp_common::test(unwrap_try = true)]
465 fn test_openmls_try_from_wrong_length(#[case] len: usize) {
466 let ommls = openmls::group::GroupId::from_slice(&vec![1u8; len]);
467 assert!(GroupId::try_from(&ommls).is_err());
468 assert!(GroupId::try_from(ommls).is_err());
469 }
470
471 #[rstest]
472 #[case(16)]
473 #[case(32)]
474 #[xmtp_common::test(unwrap_try = true)]
475 fn test_hex_roundtrip(#[case] len: usize) {
476 let bytes = xmtp_common::rand_vec::<64>()[..len].to_vec();
477 let hex = hex::encode(&bytes);
478 let id: GroupId = hex.parse().unwrap();
479 assert_eq!(id.as_slice(), &bytes[..]);
480 assert_eq!(id.to_string(), hex);
481 }
482
483 #[xmtp_common::test(unwrap_try = true)]
484 fn test_fromstr_bad_hex() {
485 let err = "zz".repeat(32).parse::<GroupId>().unwrap_err();
486 assert!(matches!(err, GroupIdParseError::Hex(_)));
487 }
488
489 #[rstest]
490 #[case(0)]
491 #[case(16)]
492 #[case(31)]
493 #[case(33)]
494 #[case(65)]
495 #[case(66)]
496 #[xmtp_common::test(unwrap_try = true)]
497 fn test_fromstr_wrong_length(#[case] len: usize) {
498 let err = "a".repeat(len).parse::<GroupId>().unwrap_err();
499 assert!(matches!(err, GroupIdParseError::Length(_)));
500 }
501
502 #[rstest]
503 #[case(GroupId::from([1u8; 16]), vec![1u8; 16], true)]
504 #[case(GroupId::from([1u8; 16]), vec![2u8; 16], false)]
505 #[case(GroupId::from([1u8; 16]), vec![1u8; 15], false)] #[case(GroupId::from([1u8; 16]), vec![1u8; 32], false)] #[case(GroupId::from([1u8; 32]), vec![1u8; 32], true)]
508 #[xmtp_common::test(unwrap_try = true)]
509 async fn test_group_id_eq_vec(#[case] id: GroupId, #[case] v: Vec<u8>, #[case] equal: bool) {
510 #[allow(clippy::op_ref)]
513 {
514 assert_eq!(id == v, equal);
515 assert_eq!(v == id, equal);
516 assert_eq!(id == &v, equal);
517 assert_eq!(&v == id, equal);
518 }
519 }
520
521 #[rstest]
522 #[case(GroupId::from([1u8; 16]), [1u8; 16], true)]
523 #[case(GroupId::from([1u8; 16]), [2u8; 16], false)]
524 #[xmtp_common::test(unwrap_try = true)]
525 async fn test_group_id_eq_array(#[case] id: GroupId, #[case] a: [u8; 16], #[case] equal: bool) {
526 assert_eq!(id == a, equal);
527 assert_eq!(a == id, equal);
528 }
529
530 #[xmtp_common::test(unwrap_try = true)]
531 async fn test_group_id_eq_slice() {
532 let id = GroupId::from([1u8; 16]);
533 let s: &[u8] = &[1u8; 16];
534 assert_eq!(id, *s);
535 assert_eq!(*s, id);
536
537 let wrong_len: &[u8] = &[1u8; 8];
538 assert_ne!(id, *wrong_len);
539 assert_ne!(*wrong_len, id);
540 }
541
542 #[xmtp_common::test(unwrap_try = true)]
546 fn test_eq_ord_hash_follow_bytes() {
547 use std::collections::{BTreeSet, HashMap};
548 let legacy = GroupId::from([0u8; 16]);
549 let keyed = GroupId::from([0u8; 32]);
550 assert_ne!(legacy, keyed);
551 assert!(legacy < keyed);
552 assert!(GroupId::from([1u8; 16]) > keyed);
553 let set: BTreeSet<_> = [keyed, legacy, GroupId::from([1u8; 16])].into();
554 assert_eq!(set.len(), 3);
555 let map = HashMap::from([(legacy, 1), (keyed, 2)]);
556 assert_eq!(map.get(&[0u8; 16][..]), Some(&1));
557 assert_eq!(map.get(&[0u8; 32][..]), Some(&2));
558 }
559
560 #[rstest]
562 #[case(GroupId::from([7u8; 16]))]
563 #[case(GroupId::from([7u8; 32]))]
564 #[xmtp_common::test(unwrap_try = true)]
565 fn test_serde_roundtrip(#[case] id: GroupId) {
566 let bytes = cbor4ii::serde::to_vec(Vec::new(), &id).unwrap();
567 let decoded: GroupId = cbor4ii::serde::from_slice(&bytes).unwrap();
568 assert_eq!(decoded, id);
569 assert_eq!(decoded.len(), id.len());
570 }
571
572 #[rstest]
573 #[case(8)]
574 #[case(33)]
575 #[xmtp_common::test(unwrap_try = true)]
576 fn test_serde_wrong_length_fails(#[case] len: usize) {
577 let bad: Vec<u8> = vec![1; len];
578 let bytes = cbor4ii::serde::to_vec(Vec::new(), &bad).unwrap();
579 assert!(cbor4ii::serde::from_slice::<GroupId>(&bytes).is_err());
580 }
581
582 #[xmtp_common::test(unwrap_try = true)]
583 fn test_generate_produces_32_bytes() {
584 use xmtp_common::Generate;
585 let id: GroupId = GroupId::generate();
586 assert_eq!(id.as_slice().len(), 32);
587 }
588
589 #[xmtp_common::test(unwrap_try = true)]
590 fn test_default_is_zero() {
591 let id = GroupId::default();
592 assert_eq!(id.as_slice(), &[0u8; 16][..]);
593 }
594
595 #[xmtp_common::test(unwrap_try = true)]
596 fn test_const_helpers() {
597 assert_eq!(GroupId::ZERO, GroupId::default());
598 assert_eq!(GroupId::ZERO.as_slice(), &[0u8; 16]);
599 assert_eq!(GroupId::ONE.as_slice(), &[1u8; 16]);
600 assert_eq!(GroupId::TWO.as_slice(), &[2u8; 16]);
601 assert_eq!(GroupId::THREE.as_slice(), &[3u8; 16]);
602 assert_eq!(GroupId::FOUR.as_slice(), &[4u8; 16]);
603 }
604
605 #[xmtp_common::test(unwrap_try = true)]
606 fn test_display_debug() {
607 let id = GroupId::from([0x12, 0x34, 0xab, 0xcd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
608 let displayed = format!("{}", id);
609 assert!(displayed.starts_with("1234abcd"));
610 assert_eq!(displayed.len(), 32);
611 assert_eq!(GroupId::from([0xab; 32]).to_string(), "ab".repeat(32));
612 let debugged = format!("{:?}", id);
613 assert!(debugged.starts_with("GroupId("));
614 }
615
616 #[cfg(feature = "diesel")]
617 mod diesel_test {
618 use super::*;
619 use diesel::prelude::*;
620
621 diesel::table! {
622 test_group_ids (id) {
623 id -> Binary,
624 }
625 }
626
627 #[derive(Insertable, Queryable)]
628 #[diesel(table_name = test_group_ids)]
629 struct Row {
630 id: GroupId,
631 }
632
633 fn connection() -> SqliteConnection {
634 let mut conn = SqliteConnection::establish(":memory:").unwrap();
635 diesel::sql_query("CREATE TABLE test_group_ids (id BLOB NOT NULL PRIMARY KEY)")
636 .execute(&mut conn)
637 .unwrap();
638 conn
639 }
640
641 #[rstest]
642 #[case(GroupId::from([0xabu8; 16]))]
643 #[case(GroupId::from([0xabu8; 32]))]
644 #[xmtp_common::test(unwrap_try = true)]
645 fn test_diesel_roundtrip(#[case] id: GroupId) {
646 let mut conn = connection();
647 diesel::insert_into(test_group_ids::table)
648 .values(&Row { id })
649 .execute(&mut conn)
650 .unwrap();
651 let got: Row = test_group_ids::table.first(&mut conn).unwrap();
652 assert_eq!(got.id, id);
653 let stored: Vec<u8> = test_group_ids::table
655 .select(diesel::dsl::sql::<diesel::sql_types::Binary>("id"))
656 .first(&mut conn)
657 .unwrap();
658 assert_eq!(stored, id.to_vec());
659 }
660
661 #[rstest]
662 #[case("X'0102030405060708'")]
663 #[case("zeroblob(33)")]
664 #[xmtp_common::test(unwrap_try = true)]
665 fn test_diesel_wrong_length_errors(#[case] blob: &str) {
666 let mut conn = connection();
667 diesel::sql_query(format!("INSERT INTO test_group_ids (id) VALUES ({blob})"))
669 .execute(&mut conn)
670 .unwrap();
671 let result: Result<Row, _> = test_group_ids::table.first(&mut conn);
672 assert!(result.is_err());
673 }
674 }
675}