libsignal_core_syft/
address.rs

1//
2// Copyright 2020-2022 Signal Messenger, LLC.
3// SPDX-License-Identifier: AGPL-3.0-only
4//
5
6#![warn(missing_docs)]
7
8//! Types for identifying an individual Signal client instance.
9
10use std::fmt;
11use std::num::NonZeroU8;
12
13use uuid::Uuid;
14
15/// Known types of [ServiceId].
16#[derive(Clone, Copy, Hash, PartialEq, Eq, derive_more::TryFrom)]
17#[try_from(repr)]
18#[repr(u8)]
19pub enum ServiceIdKind {
20    /// An [Aci].
21    Aci,
22    /// A [Pni].
23    Pni,
24}
25
26impl From<ServiceIdKind> for u8 {
27    fn from(value: ServiceIdKind) -> Self {
28        value as u8
29    }
30}
31
32impl fmt::Display for ServiceIdKind {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        match self {
35            ServiceIdKind::Aci => f.write_str("ACI"),
36            ServiceIdKind::Pni => f.write_str("PNI"),
37        }
38    }
39}
40
41impl fmt::Debug for ServiceIdKind {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        write!(f, "{self}")
44    }
45}
46
47/// The error returned for a failed "downcast" conversion from a [`ServiceId`] to a specific kind of
48/// service ID (e.g. [`Pni`]).
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub struct WrongKindOfServiceIdError {
51    /// The kind of service ID being converted to.
52    pub expected: ServiceIdKind,
53    /// The actual kind of the service ID being converted.
54    pub actual: ServiceIdKind,
55}
56
57/// A service ID with a known type.
58///
59/// `RAW_KIND` is a raw [ServiceIdKind] (eventually Rust will allow enums as generic parameters).
60#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
61pub struct SpecificServiceId<const RAW_KIND: u8>(Uuid);
62
63impl<const KIND: u8> SpecificServiceId<KIND> {
64    /// Convenience function to go directly from bytes to a specific kind of service ID.
65    ///
66    /// Prefer `from(Uuid)` / `Uuid::into` if you already have a strongly-typed UUID.
67    #[inline]
68    pub const fn from_uuid_bytes(bytes: [u8; 16]) -> Self {
69        Self::from_uuid(uuid::Uuid::from_bytes(bytes))
70    }
71
72    #[inline]
73    const fn from_uuid(uuid: Uuid) -> Self {
74        Self(uuid)
75    }
76}
77
78// We can go back to derive(Hash) if the uuid crate makes a similar change:
79// https://github.com/uuid-rs/uuid/issues/775
80impl<const KIND: u8> std::hash::Hash for SpecificServiceId<KIND> {
81    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
82        state.write(self.0.as_bytes());
83    }
84}
85
86impl<const KIND: u8> SpecificServiceId<KIND>
87where
88    ServiceId: From<Self>,
89    Self: TryFrom<ServiceId>,
90{
91    /// The standard variable-width binary representation for a Signal service ID.
92    ///
93    /// This format is not self-delimiting; the length is needed to decode it.
94    #[inline]
95    pub fn service_id_binary(&self) -> Vec<u8> {
96        ServiceId::from(*self).service_id_binary()
97    }
98
99    /// The standard fixed-width binary representation for a Signal service ID.
100    #[inline]
101    pub fn service_id_fixed_width_binary(&self) -> ServiceIdFixedWidthBinaryBytes {
102        ServiceId::from(*self).service_id_fixed_width_binary()
103    }
104
105    /// The standard string representation for a Signal service ID.
106    pub fn service_id_string(&self) -> String {
107        ServiceId::from(*self).service_id_string()
108    }
109
110    /// Parses from the standard binary representation, returning `None` if invalid.
111    #[inline]
112    pub fn parse_from_service_id_binary(bytes: &[u8]) -> Option<Self> {
113        ServiceId::parse_from_service_id_binary(bytes)?
114            .try_into()
115            .ok()
116    }
117
118    /// Parses from the standard binary representation, returning `None` if invalid.
119    #[inline]
120    pub fn parse_from_service_id_fixed_width_binary(
121        bytes: &ServiceIdFixedWidthBinaryBytes,
122    ) -> Option<Self> {
123        ServiceId::parse_from_service_id_fixed_width_binary(bytes)?
124            .try_into()
125            .ok()
126    }
127
128    /// Parses from the standard String representation, returning `None` if invalid.
129    ///
130    /// The UUID parsing is case-insensitive.
131    pub fn parse_from_service_id_string(input: &str) -> Option<Self> {
132        ServiceId::parse_from_service_id_string(input)?
133            .try_into()
134            .ok()
135    }
136}
137
138impl<const KIND: u8> From<Uuid> for SpecificServiceId<KIND> {
139    #[inline]
140    fn from(value: Uuid) -> Self {
141        Self::from_uuid(value)
142    }
143}
144
145impl<const KIND: u8> From<SpecificServiceId<KIND>> for Uuid {
146    #[inline]
147    fn from(value: SpecificServiceId<KIND>) -> Self {
148        value.0
149    }
150}
151
152impl<const KIND: u8> fmt::Debug for SpecificServiceId<KIND>
153where
154    ServiceId: From<Self>,
155{
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        ServiceId::from(*self).fmt(f)
158    }
159}
160
161/// A service ID representing an ACI ("ACcount Identifier").
162///
163/// See also [ServiceId].
164pub type Aci = SpecificServiceId<{ ServiceIdKind::Aci as u8 }>;
165
166/// A service ID representing a PNI ("Phone Number Identifier").
167///
168/// See also [ServiceId].
169pub type Pni = SpecificServiceId<{ ServiceIdKind::Pni as u8 }>;
170
171/// The fixed-width binary representation of a ServiceId.
172///
173/// Rarely used. The variable-width format that privileges ACIs is preferred.
174pub type ServiceIdFixedWidthBinaryBytes = [u8; 17];
175
176/// A Signal service ID, which can be one of various types.
177///
178/// Conceptually this is a UUID in a particular "namespace" representing a particular way to reach a
179/// user on the Signal service.
180#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, derive_more::From)]
181pub enum ServiceId {
182    /// An ACI
183    Aci(Aci),
184    /// A PNI
185    Pni(Pni),
186}
187
188impl ServiceId {
189    /// The kind of service ID `self` is.
190    #[inline]
191    pub fn kind(&self) -> ServiceIdKind {
192        match self {
193            ServiceId::Aci(_) => ServiceIdKind::Aci,
194            ServiceId::Pni(_) => ServiceIdKind::Pni,
195        }
196    }
197
198    /// The standard variable-width binary representation for a Signal service ID.
199    ///
200    /// This format is not self-delimiting; the length is needed to decode it.
201    #[inline]
202    pub fn service_id_binary(&self) -> Vec<u8> {
203        if let Self::Aci(aci) = self {
204            aci.0.as_bytes().to_vec()
205        } else {
206            self.service_id_fixed_width_binary().to_vec()
207        }
208    }
209
210    /// The standard fixed-width binary representation for a Signal service ID.
211    #[inline]
212    pub fn service_id_fixed_width_binary(&self) -> ServiceIdFixedWidthBinaryBytes {
213        let mut result = [0; 17];
214        result[0] = self.kind().into();
215        result[1..].copy_from_slice(self.raw_uuid().as_bytes());
216        result
217    }
218
219    /// The standard string representation for a Signal service ID.
220    pub fn service_id_string(&self) -> String {
221        if let Self::Aci(aci) = self {
222            aci.0.to_string()
223        } else {
224            format!("{}:{}", self.kind(), self.raw_uuid())
225        }
226    }
227
228    /// Parses from the standard binary representation, returning `None` if invalid.
229    #[inline]
230    pub fn parse_from_service_id_binary(bytes: &[u8]) -> Option<Self> {
231        match bytes.len() {
232            16 => Some(Self::Aci(Uuid::from_slice(bytes).ok()?.into())),
233            17 => {
234                let result = Self::parse_from_service_id_fixed_width_binary(
235                    bytes.try_into().expect("already measured"),
236                )?;
237                if result.kind() == ServiceIdKind::Aci {
238                    // The ACI is unmarked in the standard binary format, so this is an error.
239                    None
240                } else {
241                    Some(result)
242                }
243            }
244            _ => None,
245        }
246    }
247
248    /// Parses from the standard binary representation, returning `None` if invalid.
249    #[inline]
250    pub fn parse_from_service_id_fixed_width_binary(
251        bytes: &ServiceIdFixedWidthBinaryBytes,
252    ) -> Option<Self> {
253        let uuid = Uuid::from_slice(&bytes[1..]).ok()?;
254        match ServiceIdKind::try_from(bytes[0]).ok()? {
255            ServiceIdKind::Aci => Some(Self::Aci(uuid.into())),
256            ServiceIdKind::Pni => Some(Self::Pni(uuid.into())),
257        }
258    }
259
260    /// Parses from the standard String representation, returning `None` if invalid.
261    ///
262    /// The UUID parsing is case-insensitive.
263    pub fn parse_from_service_id_string(input: &str) -> Option<Self> {
264        fn try_parse_hyphenated(input: &str) -> Option<Uuid> {
265            // uuid::Uuid supports multiple UUID formats; we only want to support the "hyphenated"
266            // form.
267            if input.len() != uuid::fmt::Hyphenated::LENGTH {
268                return None;
269            }
270            Uuid::try_parse(input).ok()
271        }
272
273        if let Some(uuid_string) = input.strip_prefix("PNI:") {
274            let uuid = try_parse_hyphenated(uuid_string)?;
275            Some(Self::Pni(uuid.into()))
276        } else {
277            let uuid = try_parse_hyphenated(input)?;
278            Some(Self::Aci(uuid.into()))
279        }
280    }
281
282    /// Returns the UUID inside this service ID, discarding the type.
283    #[inline]
284    pub fn raw_uuid(self) -> Uuid {
285        match self {
286            ServiceId::Aci(aci) => aci.into(),
287            ServiceId::Pni(pni) => pni.into(),
288        }
289    }
290
291    /// Constructs a [ProtocolAddress] from this service ID and a device ID.
292    pub fn to_protocol_address(&self, device_id: DeviceId) -> ProtocolAddress {
293        ProtocolAddress::new(self.service_id_string(), device_id)
294    }
295}
296
297impl fmt::Debug for ServiceId {
298    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299        write!(f, "<{}:{}>", self.kind(), self.raw_uuid())
300    }
301}
302
303impl<const KIND: u8> TryFrom<ServiceId> for SpecificServiceId<KIND> {
304    type Error = WrongKindOfServiceIdError;
305
306    #[inline]
307    fn try_from(value: ServiceId) -> Result<Self, Self::Error> {
308        if u8::from(value.kind()) == KIND {
309            Ok(value.raw_uuid().into())
310        } else {
311            Err(WrongKindOfServiceIdError {
312                expected: KIND
313                    .try_into()
314                    .expect("invalid kind, not covered in ServiceIdKind"),
315                actual: value.kind(),
316            })
317        }
318    }
319}
320
321impl<const KIND: u8> PartialEq<ServiceId> for SpecificServiceId<KIND>
322where
323    ServiceId: From<SpecificServiceId<KIND>>,
324{
325    fn eq(&self, other: &ServiceId) -> bool {
326        ServiceId::from(*self) == *other
327    }
328}
329
330impl<const KIND: u8> PartialEq<SpecificServiceId<KIND>> for ServiceId
331where
332    ServiceId: From<SpecificServiceId<KIND>>,
333{
334    fn eq(&self, other: &SpecificServiceId<KIND>) -> bool {
335        *self == ServiceId::from(*other)
336    }
337}
338
339#[cfg(test)]
340mod service_id_tests {
341    use std::borrow::Borrow;
342
343    use proptest::prelude::*;
344    use rand::rng;
345    use rand::seq::SliceRandom;
346
347    use super::*;
348
349    #[test]
350    fn conversions() {
351        let uuid = uuid::uuid!("8c78cd2a-16ff-427d-83dc-1a5e36ce713d");
352
353        let aci = Aci::from(uuid);
354        assert_eq!(uuid, Uuid::from(aci));
355        let aci_service_id = ServiceId::from(aci);
356        assert_eq!(aci, aci_service_id);
357        assert_eq!(aci_service_id, aci);
358        assert_eq!(Ok(aci), Aci::try_from(aci_service_id));
359        assert_eq!(
360            Err(WrongKindOfServiceIdError {
361                expected: ServiceIdKind::Pni,
362                actual: ServiceIdKind::Aci
363            }),
364            Pni::try_from(aci_service_id)
365        );
366        assert_eq!(ServiceIdKind::Aci, aci_service_id.kind());
367
368        let pni = Pni::from(uuid);
369        assert_eq!(uuid, Uuid::from(pni));
370        let pni_service_id = ServiceId::from(pni);
371        assert_eq!(pni, pni_service_id);
372        assert_eq!(pni_service_id, pni);
373        assert_eq!(Ok(pni), Pni::try_from(pni_service_id));
374        assert_eq!(
375            Err(WrongKindOfServiceIdError {
376                expected: ServiceIdKind::Aci,
377                actual: ServiceIdKind::Pni
378            }),
379            Aci::try_from(pni_service_id)
380        );
381        assert_eq!(ServiceIdKind::Pni, pni_service_id.kind());
382
383        assert_ne!(aci_service_id, pni_service_id);
384    }
385
386    #[allow(clippy::too_many_arguments)]
387    fn round_trip_test<SerializedOwned, SerializedBorrowed>(
388        uuid: Uuid,
389        serialize: fn(&ServiceId) -> SerializedOwned,
390        serialize_aci: fn(&Aci) -> SerializedOwned,
391        serialize_pni: fn(&Pni) -> SerializedOwned,
392        deserialize: fn(&SerializedBorrowed) -> Option<ServiceId>,
393        deserialize_aci: fn(&SerializedBorrowed) -> Option<Aci>,
394        deserialize_pni: fn(&SerializedBorrowed) -> Option<Pni>,
395        expected_aci: &SerializedBorrowed,
396        expected_pni: &SerializedBorrowed,
397    ) where
398        SerializedOwned: Borrow<SerializedBorrowed>,
399        SerializedBorrowed: Eq + fmt::Debug + ?Sized,
400    {
401        {
402            let aci = Aci::from(uuid);
403            let serialized = serialize_aci(&aci);
404            assert_eq!(expected_aci, serialized.borrow());
405            assert_eq!(
406                serialized.borrow(),
407                serialize(&ServiceId::from(aci)).borrow()
408            );
409            let deserialized = deserialize(serialized.borrow()).expect("just serialized");
410            assert_eq!(ServiceIdKind::Aci, deserialized.kind());
411            assert_eq!(uuid, deserialized.raw_uuid());
412            assert_eq!(aci, Aci::try_from(deserialized).expect("type matches"));
413            assert_eq!(Some(aci), deserialize_aci(serialized.borrow()));
414            assert_eq!(None, deserialize_pni(serialized.borrow()));
415        }
416        {
417            let pni = Pni::from(uuid);
418            let serialized = serialize_pni(&pni);
419            assert_eq!(expected_pni, serialized.borrow());
420            assert_eq!(
421                serialized.borrow(),
422                serialize(&ServiceId::from(pni)).borrow()
423            );
424            let deserialized = deserialize(serialized.borrow()).expect("just serialized");
425            assert_eq!(ServiceIdKind::Pni, deserialized.kind());
426            assert_eq!(uuid, deserialized.raw_uuid());
427            assert_eq!(pni, Pni::try_from(deserialized).expect("type matches"));
428            assert_eq!(Some(pni), deserialize_pni(serialized.borrow()));
429            assert_eq!(None, deserialize_aci(serialized.borrow()));
430        }
431    }
432
433    fn array_prepend(tag: u8, uuid_bytes: &[u8; 16]) -> [u8; 17] {
434        let mut result = [tag; 17];
435        result[1..].copy_from_slice(uuid_bytes);
436        result
437    }
438
439    #[test]
440    fn round_trip_service_id_binary() {
441        proptest!(|(uuid_bytes: [u8; 16])| {
442            let uuid = Uuid::from_bytes(uuid_bytes);
443            round_trip_test(
444                uuid,
445                ServiceId::service_id_binary,
446                Aci::service_id_binary,
447                Pni::service_id_binary,
448                ServiceId::parse_from_service_id_binary,
449                Aci::parse_from_service_id_binary,
450                Pni::parse_from_service_id_binary,
451                uuid.as_bytes(),
452                &array_prepend(0x01, uuid.as_bytes()),
453            );
454        });
455    }
456
457    #[test]
458    fn round_trip_service_id_fixed_width_binary() {
459        proptest!(|(uuid_bytes: [u8; 16])| {
460            let uuid = Uuid::from_bytes(uuid_bytes);
461            round_trip_test(
462                uuid,
463                ServiceId::service_id_fixed_width_binary,
464                Aci::service_id_fixed_width_binary,
465                Pni::service_id_fixed_width_binary,
466                ServiceId::parse_from_service_id_fixed_width_binary,
467                Aci::parse_from_service_id_fixed_width_binary,
468                Pni::parse_from_service_id_fixed_width_binary,
469                &array_prepend(0x00, uuid.as_bytes()),
470                &array_prepend(0x01, uuid.as_bytes()),
471            );
472        });
473    }
474
475    #[test]
476    fn round_trip_service_id_string() {
477        proptest!(|(uuid_bytes: [u8; 16])| {
478            let uuid = Uuid::from_bytes(uuid_bytes);
479            round_trip_test(
480                uuid,
481                ServiceId::service_id_string,
482                Aci::service_id_string,
483                Pni::service_id_string,
484                ServiceId::parse_from_service_id_string,
485                Aci::parse_from_service_id_string,
486                Pni::parse_from_service_id_string,
487                &uuid.hyphenated().to_string(),
488                &format!("PNI:{}", uuid.hyphenated()),
489            );
490        });
491    }
492
493    #[test]
494    fn logging() {
495        let uuid = uuid::uuid!("8c78cd2a-16ff-427d-83dc-1a5e36ce713d");
496        let aci = Aci::from(uuid);
497        assert_eq!(
498            "<ACI:8c78cd2a-16ff-427d-83dc-1a5e36ce713d>",
499            format!("{aci:?}")
500        );
501        assert_eq!(
502            "<ACI:8c78cd2a-16ff-427d-83dc-1a5e36ce713d>",
503            format!("{:?}", ServiceId::from(aci))
504        );
505        let pni = Pni::from(uuid);
506        assert_eq!(
507            "<PNI:8c78cd2a-16ff-427d-83dc-1a5e36ce713d>",
508            format!("{pni:?}")
509        );
510        assert_eq!(
511            "<PNI:8c78cd2a-16ff-427d-83dc-1a5e36ce713d>",
512            format!("{:?}", ServiceId::from(pni))
513        );
514    }
515
516    #[test]
517    fn case_insensitive() {
518        let uuid = uuid::uuid!("8c78cd2a-16ff-427d-83dc-1a5e36ce713d");
519        let mut buffer = [0u8; 40]; // exactly fits "PNI:{uuid}"
520
521        let service_id =
522            ServiceId::parse_from_service_id_string(uuid.hyphenated().encode_upper(&mut buffer))
523                .expect("can decode uppercase");
524        assert_eq!(uuid, service_id.raw_uuid());
525
526        let service_id =
527            ServiceId::parse_from_service_id_string(uuid.hyphenated().encode_lower(&mut buffer))
528                .expect("can decode lowercase");
529        assert_eq!(uuid, service_id.raw_uuid());
530
531        buffer[..4].copy_from_slice(b"PNI:");
532        uuid.hyphenated().encode_upper(&mut buffer[4..]);
533        let service_id = ServiceId::parse_from_service_id_string(
534            std::str::from_utf8(&buffer).expect("valid UTF-8"),
535        )
536        .expect("can decode uppercase PNI");
537        assert_eq!(uuid, service_id.raw_uuid());
538
539        uuid.hyphenated().encode_lower(&mut buffer[4..]);
540        let service_id = ServiceId::parse_from_service_id_string(
541            std::str::from_utf8(&buffer).expect("valid UTF-8"),
542        )
543        .expect("can decode lowercase PNI");
544        assert_eq!(uuid, service_id.raw_uuid());
545    }
546
547    #[test]
548    fn accepts_ios_system_story_aci() {
549        // This is not technically a valid UUID, but we need to handle it anyway, at least on iOS.
550        let service_id =
551            ServiceId::parse_from_service_id_string("00000000-0000-0000-0000-000000000001")
552                .expect("can decode");
553        assert_eq!(
554            &const_str::hex!("00000000 0000 0000 0000 000000000001"),
555            service_id.raw_uuid().as_bytes(),
556        );
557        assert_eq!(ServiceIdKind::Aci, service_id.kind());
558    }
559
560    #[test]
561    fn rejects_invalid_binary_lengths() {
562        let uuid = uuid::uuid!("8c78cd2a-16ff-427d-83dc-1a5e36ce713d");
563        assert!(ServiceId::parse_from_service_id_binary(&[]).is_none());
564        assert!(ServiceId::parse_from_service_id_binary(&[1]).is_none());
565        assert!(ServiceId::parse_from_service_id_binary(&uuid.as_bytes()[1..]).is_none());
566        assert!(ServiceId::parse_from_service_id_binary(&[1; 18]).is_none());
567    }
568
569    #[test]
570    fn rejects_invalid_uuid_strings() {
571        assert!(ServiceId::parse_from_service_id_string("").is_none());
572        assert!(ServiceId::parse_from_service_id_string("11").is_none());
573        assert!(
574            ServiceId::parse_from_service_id_string("8c78cd2a16ff427d83dc1a5e36ce713d").is_none()
575        );
576        assert!(
577            ServiceId::parse_from_service_id_string("{8c78cd2a-16ff-427d-83dc-1a5e36ce713d}")
578                .is_none()
579        );
580
581        assert!(ServiceId::parse_from_service_id_string("PNI:").is_none());
582        assert!(ServiceId::parse_from_service_id_string("PNI:11").is_none());
583        assert!(
584            ServiceId::parse_from_service_id_string("PNI:8c78cd2a16ff427d83dc1a5e36ce713d")
585                .is_none()
586        );
587        assert!(
588            ServiceId::parse_from_service_id_string("PNI:{8c78cd2a-16ff-427d-83dc-1a5e36ce713d}")
589                .is_none()
590        );
591    }
592
593    #[test]
594    fn rejects_invalid_types() {
595        let uuid = uuid::uuid!("8c78cd2a-16ff-427d-83dc-1a5e36ce713d");
596        assert!(
597            ServiceId::parse_from_service_id_binary(&array_prepend(0xFF, uuid.as_bytes()))
598                .is_none()
599        );
600        assert!(
601            ServiceId::parse_from_service_id_fixed_width_binary(&array_prepend(
602                0xFF,
603                uuid.as_bytes()
604            ))
605            .is_none()
606        );
607        assert!(ServiceId::parse_from_service_id_string("BAD:{uuid}").is_none());
608        assert!(ServiceId::parse_from_service_id_string("PNI{uuid}").is_none());
609        assert!(ServiceId::parse_from_service_id_string("PNI {uuid}").is_none());
610        assert!(ServiceId::parse_from_service_id_string("PNI{uuid} ").is_none());
611
612        // ACIs are only prefixed in the fixed-width format.
613        assert!(
614            ServiceId::parse_from_service_id_binary(&array_prepend(0x00, uuid.as_bytes()))
615                .is_none()
616        );
617        assert!(ServiceId::parse_from_service_id_string("ACI:{uuid}").is_none());
618    }
619
620    #[test]
621    fn ordering() {
622        let test_uuid = uuid::uuid!("8c78cd2a-16ff-427d-83dc-1a5e36ce713d");
623
624        let mut ids: [ServiceId; 4] = [
625            Aci::from_uuid(Uuid::nil()).into(),
626            Aci::from_uuid(test_uuid).into(),
627            Pni::from_uuid(Uuid::nil()).into(),
628            Pni::from_uuid(test_uuid).into(),
629        ];
630        let original = ids;
631        ids.shuffle(&mut rng());
632        ids.sort();
633        assert_eq!(original, ids);
634    }
635
636    #[test]
637    fn ordering_consistency() {
638        proptest!(|(
639            left_uuid_bytes: [u8; 16],
640            left_raw_kind in 0..=1,
641            right_uuid_bytes: [u8; 16],
642            right_raw_kind in 0..=1
643        )| {
644            let service_id_constructor = |raw_type| match raw_type {
645                0 => |uuid: Uuid| ServiceId::Aci(uuid.into()),
646                1 => |uuid: Uuid| ServiceId::Pni(uuid.into()),
647                _ => unreachable!("unexpected raw type {raw_type}"),
648            };
649
650            let left_uuid = Uuid::from_bytes(left_uuid_bytes);
651            let left_service_id = service_id_constructor(left_raw_kind)(left_uuid);
652            let right_uuid = Uuid::from_bytes(right_uuid_bytes);
653            let right_service_id = service_id_constructor(right_raw_kind)(right_uuid);
654
655            assert_eq!(
656                left_service_id.cmp(&right_service_id),
657                left_service_id.service_id_fixed_width_binary()
658                    .cmp(&right_service_id.service_id_fixed_width_binary()),
659                "didn't match Service-Id-FixedWidthBinary ordering ({left_service_id:?} vs {right_service_id:?})",
660            );
661
662            if left_raw_kind == right_raw_kind {
663                assert_eq!(
664                    left_service_id.cmp(&right_service_id),
665                    left_service_id.service_id_string().cmp(&right_service_id.service_id_string()),
666                    "same-kind ServiceIds didn't match Service-Id-String ordering ({left_service_id:?} vs {right_service_id:?})",
667                );
668            }
669        })
670    }
671}
672
673/// The type used in memory to represent a *device*, i.e. a particular Signal client instance which
674/// represents some user.
675///
676/// Used in [ProtocolAddress].
677#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
678pub struct DeviceId(NonZeroU8);
679
680#[derive(Copy, Clone, Debug, Eq, PartialEq, thiserror::Error)]
681#[error("device ID is out of range")]
682/// Error for trying to construct a [`DeviceId`] with an invalid value.
683pub struct InvalidDeviceId;
684
685impl DeviceId {
686    /// Creates a new `DeviceId` if the value is in range.
687    ///
688    /// If the value is not in the range `1..=127`, an `InvalidDeviceId` error is
689    /// returned instead.
690    #[inline]
691    pub const fn new(id: u8) -> Result<Self, InvalidDeviceId> {
692        let Some(id) = NonZeroU8::new(id) else {
693            return Err(InvalidDeviceId);
694        };
695        Self::new_nonzero(id)
696    }
697
698    /// Creates a new `DeviceId` if the value is in range.
699    ///
700    /// If the value is not in the range `1..=127`, an `InvalidDeviceId` error is
701    /// returned instead.
702    pub const fn new_nonzero(id: NonZeroU8) -> Result<Self, InvalidDeviceId> {
703        if id.get() <= MAX_VALID_DEVICE_ID {
704            Ok(Self(id))
705        } else {
706            Err(InvalidDeviceId)
707        }
708    }
709}
710
711const MAX_VALID_DEVICE_ID: u8 = 127;
712
713impl From<DeviceId> for u32 {
714    fn from(value: DeviceId) -> Self {
715        value.0.get().into()
716    }
717}
718
719impl From<DeviceId> for u8 {
720    fn from(value: DeviceId) -> Self {
721        value.0.get()
722    }
723}
724
725impl From<DeviceId> for NonZeroU8 {
726    fn from(value: DeviceId) -> Self {
727        value.0
728    }
729}
730
731impl TryFrom<u8> for DeviceId {
732    type Error = InvalidDeviceId;
733    fn try_from(value: u8) -> Result<Self, Self::Error> {
734        Self::new(value)
735    }
736}
737
738impl TryFrom<i32> for DeviceId {
739    type Error = InvalidDeviceId;
740    fn try_from(value: i32) -> Result<Self, Self::Error> {
741        Self::new(value.try_into().map_err(|_| InvalidDeviceId)?)
742    }
743}
744
745impl TryFrom<u32> for DeviceId {
746    type Error = InvalidDeviceId;
747    fn try_from(value: u32) -> Result<Self, Self::Error> {
748        Self::new(value.try_into().map_err(|_| InvalidDeviceId)?)
749    }
750}
751
752impl fmt::Display for DeviceId {
753    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
754        write!(f, "{}", self.0)
755    }
756}
757
758impl rand::distr::Distribution<DeviceId> for rand::distr::StandardUniform {
759    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> DeviceId {
760        DeviceId(NonZeroU8::new(rng.random_range(1..=MAX_VALID_DEVICE_ID)).unwrap())
761    }
762}
763
764/// Represents a unique Signal client instance as `(<user ID>, <device ID>)` pair.
765#[derive(Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
766pub struct ProtocolAddress {
767    name: String,
768    device_id: DeviceId,
769}
770
771impl ProtocolAddress {
772    /// Create a new address.
773    ///
774    /// - `name` defines a user's public identity, and therefore must be globally unique to that
775    ///   user.
776    /// - Each Signal client instance then has its own `device_id`, which must be unique among
777    ///   all clients for that user.
778    ///
779    ///```
780    /// use libsignal_core::{DeviceId, ProtocolAddress};
781    ///
782    /// // This is a unique id for some user, typically a UUID.
783    /// let user_id: String = "04899A85-4C9E-44CC-8428-A02AB69335F1".to_string();
784    /// // Each client instance representing that user has a unique device id.
785    /// let device_id: DeviceId = 2_u32.try_into().unwrap();
786    /// let address = ProtocolAddress::new(user_id.clone(), device_id);
787    ///
788    /// assert!(address.name() == &user_id);
789    /// assert!(address.device_id() == device_id);
790    ///```
791    pub fn new(name: String, device_id: DeviceId) -> Self {
792        ProtocolAddress { name, device_id }
793    }
794
795    /// A unique identifier for the target user. This is usually a UUID.
796    #[inline]
797    pub fn name(&self) -> &str {
798        &self.name
799    }
800
801    /// An identifier representing a particular Signal client instance to send to.
802    ///
803    /// For example, if a user has set up Signal on both their phone and laptop, a particular
804    /// message sent to the user will still only go to a single device. So when a user sends a
805    /// message to another user at all, they're actually sending a message to *every* device.
806    #[inline]
807    pub fn device_id(&self) -> DeviceId {
808        self.device_id
809    }
810}
811
812impl fmt::Display for ProtocolAddress {
813    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
814        write!(f, "{}.{}", self.name, self.device_id)
815    }
816}