1#![warn(missing_docs)]
7
8use std::fmt;
11use std::num::NonZeroU8;
12
13use uuid::Uuid;
14
15#[derive(Clone, Copy, Hash, PartialEq, Eq, derive_more::TryFrom)]
17#[try_from(repr)]
18#[repr(u8)]
19pub enum ServiceIdKind {
20 Aci,
22 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub struct WrongKindOfServiceIdError {
51 pub expected: ServiceIdKind,
53 pub actual: ServiceIdKind,
55}
56
57#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
61pub struct SpecificServiceId<const RAW_KIND: u8>(Uuid);
62
63impl<const KIND: u8> SpecificServiceId<KIND> {
64 #[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
78impl<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 #[inline]
95 pub fn service_id_binary(&self) -> Vec<u8> {
96 ServiceId::from(*self).service_id_binary()
97 }
98
99 #[inline]
101 pub fn service_id_fixed_width_binary(&self) -> ServiceIdFixedWidthBinaryBytes {
102 ServiceId::from(*self).service_id_fixed_width_binary()
103 }
104
105 pub fn service_id_string(&self) -> String {
107 ServiceId::from(*self).service_id_string()
108 }
109
110 #[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 #[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 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
161pub type Aci = SpecificServiceId<{ ServiceIdKind::Aci as u8 }>;
165
166pub type Pni = SpecificServiceId<{ ServiceIdKind::Pni as u8 }>;
170
171pub type ServiceIdFixedWidthBinaryBytes = [u8; 17];
175
176#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, derive_more::From)]
181pub enum ServiceId {
182 Aci(Aci),
184 Pni(Pni),
186}
187
188impl ServiceId {
189 #[inline]
191 pub fn kind(&self) -> ServiceIdKind {
192 match self {
193 ServiceId::Aci(_) => ServiceIdKind::Aci,
194 ServiceId::Pni(_) => ServiceIdKind::Pni,
195 }
196 }
197
198 #[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 #[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 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 #[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 None
240 } else {
241 Some(result)
242 }
243 }
244 _ => None,
245 }
246 }
247
248 #[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 pub fn parse_from_service_id_string(input: &str) -> Option<Self> {
264 fn try_parse_hyphenated(input: &str) -> Option<Uuid> {
265 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 #[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 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]; 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 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 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#[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")]
682pub struct InvalidDeviceId;
684
685impl DeviceId {
686 #[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 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#[derive(Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
766pub struct ProtocolAddress {
767 name: String,
768 device_id: DeviceId,
769}
770
771impl ProtocolAddress {
772 pub fn new(name: String, device_id: DeviceId) -> Self {
792 ProtocolAddress { name, device_id }
793 }
794
795 #[inline]
797 pub fn name(&self) -> &str {
798 &self.name
799 }
800
801 #[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}