1use crate::{
2 prelude::*,
3 traits::structural_eq::StructuralEq,
4 utils::buffers::buffer_to_hex,
5 values::{
6 core_value::CoreValue,
7 value_container::{ValueContainer, ValueError},
8 },
9};
10
11use crate::crypto::CryptoImpl;
12use binrw::{BinRead, BinWrite, io::Cursor};
13use core::{
14 fmt::{Debug, Display, Formatter},
15 hash::Hash,
16 result::Result,
17 str,
18 str::FromStr,
19};
20use datex_crypto_facade::crypto::Crypto;
21use hex::decode;
22use serde::{Deserialize, Serialize};
23
24#[derive(
25 BinWrite, BinRead, Debug, Clone, Copy, Hash, PartialEq, Eq, Default,
26)]
27pub enum EndpointInstance {
28 #[default]
31 #[br(magic = 0u16)]
32 #[bw(magic = 0u16)]
33 Any,
34 #[br(magic = 65535u16)]
37 #[bw(magic = 65535u16)]
38 All,
39 Instance(u16),
42}
43
44impl EndpointInstance {
45 pub fn new(instance: u16) -> EndpointInstance {
46 match instance {
47 0 => EndpointInstance::Any,
48 65535 => EndpointInstance::All,
49 _ => EndpointInstance::Instance(instance),
50 }
51 }
52}
53
54#[derive(
56 Debug, Hash, PartialEq, Eq, Clone, Copy, Default, BinWrite, BinRead,
57)]
58#[brw(repr(u8))]
59pub enum EndpointType {
60 #[default]
61 Person = 0,
62 Institution = 1,
63 Anonymous = 2,
64}
65
66#[cfg_attr(feature = "wasm_runtime", derive(tsify::Tsify))]
67#[cfg_attr(feature = "wasm_runtime", tsify(type = "string"))]
68#[derive(BinWrite, BinRead, Debug, Clone, Hash, PartialEq, Eq)]
69#[brw(little)]
70pub struct Endpoint {
71 pub ty: EndpointType,
73 pub identifier: [u8; 18],
74 pub instance: EndpointInstance,
75}
76
77impl<T: Into<ValueContainer>> TryFrom<Option<T>> for Endpoint {
79 type Error = ValueError;
80 fn try_from(value: Option<T>) -> Result<Self, Self::Error> {
81 if let Some(value) = value {
82 let container: ValueContainer = value.into();
83 if let Some(endpoint) =
84 container.to_value().borrow().cast_to_endpoint()
85 {
86 return Ok(endpoint);
87 }
88 }
89 Err(ValueError::TypeConversionError)
90 }
91}
92
93impl StructuralEq for Endpoint {
108 fn structural_eq(&self, other: &Self) -> bool {
109 self == other
110 }
111}
112
113impl Default for Endpoint {
114 fn default() -> Self {
115 Endpoint::LOCAL
116 }
117}
118
119impl TryFrom<&str> for Endpoint {
120 type Error = InvalidEndpointError;
121 fn try_from(name: &str) -> Result<Self, Self::Error> {
122 Endpoint::from_string(name)
123 }
124}
125impl TryFrom<String> for Endpoint {
126 type Error = InvalidEndpointError;
127 fn try_from(name: String) -> Result<Self, Self::Error> {
128 Endpoint::from_string(&name)
129 }
130}
131
132impl TryFrom<CoreValue> for Endpoint {
133 type Error = ValueError;
134 fn try_from(value: CoreValue) -> Result<Self, Self::Error> {
135 if let Some(endpoint) = value.cast_to_endpoint() {
136 return Ok(endpoint);
137 }
138 Err(ValueError::TypeConversionError)
139 }
140}
141
142#[derive(PartialEq, Debug, Clone, Eq)]
143pub enum InvalidEndpointError {
144 InvalidCharacters,
145 MaxLengthExceeded,
146 MinLengthNotMet,
147 InvalidInstance,
148 ReservedName,
149}
150impl Display for InvalidEndpointError {
151 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
152 match self {
153 InvalidEndpointError::InvalidCharacters => {
154 core::write!(f, "Endpoint contains invalid characters")
155 }
156 InvalidEndpointError::MaxLengthExceeded => {
157 core::write!(
158 f,
159 "Endpoint name exceeds maximum length of 18 characters"
160 )
161 }
162 InvalidEndpointError::MinLengthNotMet => {
163 core::write!(
164 f,
165 "Endpoint name must be at least 3 characters long"
166 )
167 }
168 InvalidEndpointError::InvalidInstance => {
169 core::write!(f, "Endpoint instance must be between 1 and 65534")
170 }
171 InvalidEndpointError::ReservedName => {
172 core::write!(f, "Endpoint name is reserved")
173 }
174 }
175 }
176}
177
178#[derive(PartialEq, Debug)]
179pub struct EndpointParsingError;
180
181impl Endpoint {
182 const PREFIX_PERSON: &'static str = "@";
183 const PREFIX_INSTITUTION: &'static str = "@+";
184 const PREFIX_ANONYMOUS: &'static str = "@@";
185
186 const ALIAS_LOCAL: &'static str = "local";
187 const ALIAS_ANY: &'static str = "any";
188
189 pub const ANY: Endpoint = Endpoint {
192 ty: EndpointType::Anonymous,
193 identifier: [255; 18],
194 instance: EndpointInstance::Any,
195 };
196
197 pub const ANY_ALL_INSTANCES: Endpoint = Endpoint {
200 ty: EndpointType::Anonymous,
201 identifier: [255; 18],
202 instance: EndpointInstance::All,
203 };
204
205 pub const LOCAL: Endpoint = Endpoint {
208 ty: EndpointType::Anonymous,
209 identifier: [0; 18],
210 instance: EndpointInstance::Any,
211 };
212
213 pub const LOCAL_ALL_INSTANCES: Endpoint = Endpoint {
216 ty: EndpointType::Anonymous,
217 identifier: [0; 18],
218 instance: EndpointInstance::All,
219 };
220
221 pub fn random() -> Endpoint {
223 Self::anonymous(Self::random_anonymous_id(), EndpointInstance::Any)
224 .unwrap()
225 }
226
227 pub fn anonymous(
229 identifier: [u8; 18],
230 instance: EndpointInstance,
231 ) -> Result<Endpoint, InvalidEndpointError> {
232 if identifier == [255; 18] {
234 return if instance == EndpointInstance::Any {
235 Ok(Endpoint::ANY)
236 } else if instance == EndpointInstance::All {
237 Ok(Endpoint::ANY_ALL_INSTANCES)
238 } else {
239 Ok(Endpoint {
240 ty: EndpointType::Anonymous,
241 identifier,
242 instance,
243 })
244 };
245 }
246
247 if identifier == [0; 18] {
249 return if instance == EndpointInstance::Any {
250 Ok(Endpoint::LOCAL)
251 } else if instance == EndpointInstance::All {
252 Ok(Endpoint::LOCAL_ALL_INSTANCES)
253 } else {
254 Ok(Endpoint {
255 ty: EndpointType::Anonymous,
256 identifier,
257 instance,
258 })
259 };
260 }
261
262 Ok(Endpoint {
263 ty: EndpointType::Anonymous,
264 identifier,
265 instance,
266 })
267 }
268
269 pub fn new(name: &str) -> Endpoint {
272 Endpoint::from_string(name).unwrap_or_else(|_| {
273 core::panic!("Failed to convert str {name} to Endpoint")
274 })
275 }
276
277 pub fn person(
279 name: &str,
280 instance: EndpointInstance,
281 ) -> Result<Endpoint, InvalidEndpointError> {
282 Self::named(name, instance, EndpointType::Person)
283 }
284
285 pub fn institution(
287 name: &str,
288 instance: EndpointInstance,
289 ) -> Result<Endpoint, InvalidEndpointError> {
290 Self::named(name, instance, EndpointType::Institution)
291 }
292
293 fn from_string(name: &str) -> Result<Endpoint, InvalidEndpointError> {
295 let name = name.to_string();
296 if name
297 == format!("{}{}", Endpoint::PREFIX_ANONYMOUS, Endpoint::ALIAS_ANY)
298 {
299 return Ok(Endpoint::ANY);
300 } else if name
301 == format!(
302 "{}{}",
303 Endpoint::PREFIX_ANONYMOUS,
304 Endpoint::ALIAS_LOCAL
305 )
306 {
307 return Ok(Endpoint::LOCAL);
308 }
309
310 let mut name_part = name.clone();
311 let mut instance = EndpointInstance::Any;
312 if name.contains('/') {
314 let parts: Vec<&str> = name.split('/').collect();
315 if parts.len() != 2 {
316 return Err(InvalidEndpointError::InvalidCharacters);
317 }
318 name_part = parts[0].to_string();
319 let instance_str = parts[1];
320 if instance_str == "*" {
321 instance = EndpointInstance::All;
322 } else {
323 let instance_num = instance_str
324 .parse::<u16>()
325 .map_err(|_| InvalidEndpointError::InvalidInstance)?;
326 instance = EndpointInstance::new(instance_num);
327 }
328 }
329
330 match name_part {
331 s if s.starts_with(&format!(
332 "{}{}",
333 Endpoint::PREFIX_ANONYMOUS,
334 Endpoint::ALIAS_ANY
335 )) =>
336 {
337 Ok(Endpoint {
338 ty: EndpointType::Anonymous,
339 identifier: [255u8; 18],
340 instance,
341 })
342 }
343 s if s.starts_with(&format!(
344 "{}{}",
345 Endpoint::PREFIX_ANONYMOUS,
346 Endpoint::ALIAS_LOCAL
347 )) =>
348 {
349 Ok(Endpoint {
350 ty: EndpointType::Anonymous,
351 identifier: [0u8; 18],
352 instance,
353 })
354 }
355 s if s.starts_with(Endpoint::PREFIX_ANONYMOUS) => {
356 let s = s.trim_start_matches(Endpoint::PREFIX_ANONYMOUS);
357 if s.len() < 18 * 2 {
358 return Err(InvalidEndpointError::MinLengthNotMet);
359 } else if s.len() > 18 * 2 {
360 return Err(InvalidEndpointError::MaxLengthExceeded);
361 }
362 let bytes = decode(s)
363 .map_err(|_| InvalidEndpointError::InvalidCharacters)?;
364 let byte_slice: &[u8] = &bytes;
365 Endpoint::anonymous(byte_slice.try_into().unwrap(), instance)
366 }
367 s if s.starts_with(Endpoint::PREFIX_INSTITUTION) => {
368 Endpoint::named(&s[2..], instance, EndpointType::Institution)
369 }
370 s if s.starts_with(Endpoint::PREFIX_PERSON) => {
371 Endpoint::named(&s[1..], instance, EndpointType::Person)
372 }
373 _ => Err(InvalidEndpointError::InvalidCharacters),
374 }
375 }
376
377 pub fn from_slice(
379 binary: [u8; 21],
380 ) -> Result<Endpoint, EndpointParsingError> {
381 let mut reader = Cursor::new(binary);
382 let endpoint =
383 Endpoint::read(&mut reader).map_err(|_| EndpointParsingError)?;
384
385 if !Self::is_endpoint_valid(&endpoint) {
387 return Err(EndpointParsingError);
388 }
389 Ok(endpoint)
390 }
391
392 fn named(
393 name: &str,
394 instance: EndpointInstance,
395 type_: EndpointType,
396 ) -> Result<Endpoint, InvalidEndpointError> {
397 if !Self::is_instance_valid(&instance) {
399 return Err(InvalidEndpointError::InvalidInstance);
400 }
401
402 let name_bytes = Endpoint::name_to_bytes(name)?;
404
405 Ok(Endpoint {
406 ty: type_,
407 identifier: name_bytes,
408 instance,
409 })
410 }
411
412 fn name_to_bytes(name: &str) -> Result<[u8; 18], InvalidEndpointError> {
413 let mut identifier = String::into_bytes(
414 name.to_string().trim_end_matches('\0').to_string(),
415 );
416 if identifier.len() > 18 {
418 return Err(InvalidEndpointError::MaxLengthExceeded);
419 }
420 if identifier.len() < 3 {
422 return Err(InvalidEndpointError::MinLengthNotMet);
423 }
424
425 identifier.resize(18, 0);
426
427 if !Self::are_name_chars_valid(identifier.clone().try_into().unwrap()) {
429 return Err(InvalidEndpointError::InvalidCharacters);
430 };
431
432 Ok(identifier.try_into().unwrap())
433 }
434
435 fn random_anonymous_id() -> [u8; 18] {
436 let buffer = CryptoImpl::random_bytes(18);
437 if buffer.iter().any(|&b| b != 0) {
438 return buffer.try_into().unwrap();
439 }
440 panic!("Could not generate random anonymous id");
442 }
443
444 fn are_name_chars_valid(name: [u8; 18]) -> bool {
445 let mut is_null = false;
446 for c in name.iter() {
447 if is_null && *c != 0x00 {
449 return false;
450 }
451 if *c == 0x00 {
452 is_null = true;
453 continue;
454 }
455 if !(*c >= 0x30 && *c <= 0x39 || *c >= 0x61 && *c <= 0x7A) && *c != 0x2D && *c != 0x5F
459 {
460 return false;
462 }
463 }
464 true
465 }
466
467 fn is_endpoint_valid(endpoint: &Endpoint) -> bool {
468 if !Self::is_instance_valid(&endpoint.instance) {
470 return false;
471 }
472
473 match endpoint.ty {
474 EndpointType::Person | EndpointType::Institution => {
475 Self::are_name_chars_valid(endpoint.identifier)
477 }
478 _ => true,
479 }
480 }
481
482 fn is_instance_valid(endpoint_instance: &EndpointInstance) -> bool {
483 match endpoint_instance {
484 EndpointInstance::All => true,
485 EndpointInstance::Any => true,
486 EndpointInstance::Instance(instance) => {
487 *instance > 0 && *instance < 65535
489 }
490 }
491 }
492
493 pub fn to_slice(&self) -> [u8; 21] {
494 let mut writer = Cursor::new(Vec::new());
495 self.write(&mut writer).unwrap();
496 writer.into_inner().try_into().unwrap()
497 }
498
499 pub fn type_(&self) -> EndpointType {
501 self.ty
502 }
503
504 pub fn instance(&self) -> EndpointInstance {
506 self.instance
507 }
508
509 pub fn is_broadcast(&self) -> bool {
511 self.instance == EndpointInstance::All
512 }
513
514 pub fn is_local(&self) -> bool {
516 self == &Endpoint::LOCAL
517 }
518
519 pub fn is_any(&self) -> bool {
521 self == &Endpoint::ANY
522 }
523
524 pub fn is_any_instance(&self) -> bool {
526 self.instance == EndpointInstance::Any
527 }
528
529 pub fn any_instance_endpoint(&self) -> Endpoint {
531 Endpoint {
532 ty: self.ty,
533 identifier: self.identifier,
534 instance: EndpointInstance::Any,
535 }
536 }
537
538 pub fn broadcast(&self) -> Endpoint {
540 Endpoint {
541 ty: self.ty,
542 identifier: self.identifier,
543 instance: EndpointInstance::All,
544 }
545 }
546}
547
548impl Display for Endpoint {
549 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
550 match self.ty {
551 EndpointType::Anonymous => {
552 if self.identifier == [255; 18] {
554 core::write!(
555 f,
556 "{}{}",
557 Endpoint::PREFIX_ANONYMOUS,
558 Endpoint::ALIAS_ANY
559 )?;
560 }
561 else if self.identifier == [0; 18] {
563 core::write!(
564 f,
565 "{}{}",
566 Endpoint::PREFIX_ANONYMOUS,
567 Endpoint::ALIAS_LOCAL
568 )?;
569 }
570 else {
572 core::write!(
573 f,
574 "{}{}",
575 Endpoint::PREFIX_ANONYMOUS,
576 buffer_to_hex(self.identifier.to_vec())
577 )?
578 }
579 }
580 EndpointType::Person => core::write!(
581 f,
582 "{}{}",
583 Endpoint::PREFIX_PERSON,
584 str::from_utf8(&self.identifier)
585 .unwrap()
586 .trim_end_matches('\0')
587 )?,
588 EndpointType::Institution => core::write!(
589 f,
590 "{}{}",
591 Endpoint::PREFIX_INSTITUTION,
592 str::from_utf8(&self.identifier)
593 .unwrap()
594 .trim_end_matches('\0')
595 )?,
596 };
597
598 match self.instance {
599 EndpointInstance::Any => (),
600 EndpointInstance::All => f.write_str("/*")?,
601 EndpointInstance::Instance(instance) => {
602 core::write!(f, "/{instance}")?
603 }
604 };
605
606 Ok(())
607 }
608}
609
610impl FromStr for Endpoint {
611 type Err = InvalidEndpointError;
612
613 fn from_str(name: &str) -> Result<Self, Self::Err> {
614 Endpoint::from_string(name)
615 }
616}
617
618impl Serialize for Endpoint {
619 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
620 where
621 S: serde::Serializer,
622 {
623 serializer
624 .serialize_newtype_struct("datex::endpoint", &self.to_string())
625 }
626}
627
628impl<'a> Deserialize<'a> for Endpoint {
629 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
630 where
631 D: serde::Deserializer<'a>,
632 {
633 let s: String = Deserialize::deserialize(deserializer)?;
634 Endpoint::from_string(&s).map_err(serde::de::Error::custom)
635 }
636}
637
638#[cfg(test)]
639mod tests {
640 use super::*;
641
642 #[test]
643 fn utilities() {
644 let endpoint: Endpoint = Endpoint::from_string("@ben/42").unwrap();
645 assert!(!endpoint.is_any_instance());
646 assert!(!endpoint.is_broadcast());
647
648 let main_endpoint = endpoint.any_instance_endpoint();
649 assert!(main_endpoint.is_any_instance());
650 assert_eq!(main_endpoint.to_string(), "@ben");
651 assert_eq!(main_endpoint.instance, EndpointInstance::Any);
652
653 let broadcast_endpoint = endpoint.broadcast();
654 assert!(broadcast_endpoint.is_broadcast());
655 assert_eq!(broadcast_endpoint.to_string(), "@ben/*");
656 }
657
658 #[test]
659 fn parse_from_string() {
660 let endpoint = Endpoint::from_string("@jonas").unwrap();
662 assert_eq!(endpoint.ty, EndpointType::Person);
663 assert_eq!(endpoint.instance, EndpointInstance::Any);
664 assert_eq!(endpoint.to_string(), "@jonas");
665 assert_eq!(
666 endpoint.identifier,
667 [
668 106, 111, 110, 97, 115, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
669 ]
670 );
671
672 let endpoint = Endpoint::from_string("@+unyt").unwrap();
674 assert_eq!(endpoint.ty, EndpointType::Institution);
675 assert_eq!(endpoint.instance, EndpointInstance::Any);
676 assert_eq!(endpoint.to_string(), "@+unyt");
677
678 let endpoint = Endpoint::from_string(
680 &format!("@@{}", "A".repeat(18 * 2)).to_string(),
681 )
682 .unwrap();
683 assert_eq!(endpoint.ty, EndpointType::Anonymous);
684 assert_eq!(endpoint.instance, EndpointInstance::Any);
685 assert_eq!(endpoint.to_string(), format!("@@{}", "A".repeat(18 * 2)));
686
687 let valid_endpoint_names = vec![
688 "@jonas",
689 "@@any/*",
690 "@@local",
691 "@+unyt",
692 "@test/42",
693 "@test/*",
694 "@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
695 "@@BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
696 "@+institution/42",
697 "@+deno/9999",
698 "@+deno/65534",
699 ];
700 for name in valid_endpoint_names {
701 let endpoint = Endpoint::from_string(name).unwrap();
702 assert_eq!(endpoint.to_string(), name);
703 }
704 }
705
706 #[test]
707 fn too_long() {
708 let endpoint =
709 Endpoint::person("too-long-endpoint-name", EndpointInstance::Any);
710 assert_eq!(endpoint, Err(InvalidEndpointError::MaxLengthExceeded));
711
712 let endpoint = Endpoint::from_string("@too-long-endpoint-name");
713 assert_eq!(endpoint, Err(InvalidEndpointError::MaxLengthExceeded));
714
715 let to_long_endpoint_names = vec![
716 "@too-long-endpoint-name",
717 "@@FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA",
718 "@+too-long-endpoint-name",
719 "@@FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA/0001",
720 ];
721 for name in to_long_endpoint_names {
722 let endpoint = Endpoint::from_string(name);
723 assert_eq!(endpoint, Err(InvalidEndpointError::MaxLengthExceeded));
724 }
725 }
726
727 #[test]
728 fn too_short() {
729 let endpoint = Endpoint::person("ab", EndpointInstance::Any);
730 assert_eq!(endpoint, Err(InvalidEndpointError::MinLengthNotMet));
731
732 let endpoint =
733 Endpoint::person("ab\0\0\0\0\0\0\0\0", EndpointInstance::Any);
734 assert_eq!(endpoint, Err(InvalidEndpointError::MinLengthNotMet));
735
736 let endpoint = Endpoint::from_string("@ab");
737 assert_eq!(endpoint, Err(InvalidEndpointError::MinLengthNotMet));
738
739 let to_short_endpoint_names = vec![
740 "@ab",
741 "@@ff",
742 "@+ff",
743 "@@fffffff",
744 "@ab\0\0\0\0\0\0\0\0",
745 "@@FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA/0001",
746 ];
747 for name in to_short_endpoint_names {
748 let endpoint = Endpoint::from_string(name);
749 assert_eq!(endpoint, Err(InvalidEndpointError::MinLengthNotMet));
750 }
751 }
752
753 #[test]
754 fn invalid_characters() {
755 let endpoint = Endpoint::person("äüö", EndpointInstance::Any);
756 assert_eq!(endpoint, Err(InvalidEndpointError::InvalidCharacters));
757
758 let endpoint = Endpoint::person("__O", EndpointInstance::Any);
759 assert_eq!(endpoint, Err(InvalidEndpointError::InvalidCharacters));
760
761 let endpoint = Endpoint::person("#@!", EndpointInstance::Any);
762 assert_eq!(endpoint, Err(InvalidEndpointError::InvalidCharacters));
763
764 let endpoint = Endpoint::person("\0__", EndpointInstance::Any);
765 assert_eq!(endpoint, Err(InvalidEndpointError::InvalidCharacters));
766
767 let endpoint = Endpoint::from_string("@äüö");
768 assert_eq!(endpoint, Err(InvalidEndpointError::InvalidCharacters));
769
770 let endpoint = Endpoint::from_string("@Jonas");
771 assert_eq!(endpoint, Err(InvalidEndpointError::InvalidCharacters));
772
773 let endpoint =
774 Endpoint::from_string(&format!("@@{}X", "F".repeat(18 * 2 - 1)));
775 assert_eq!(endpoint, Err(InvalidEndpointError::InvalidCharacters));
776
777 let invalid_endpoint_names = vec![
778 "@äüö",
779 "@@FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFX",
780 "@+äüö",
781 "@@FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFX/0001",
782 "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFX/0001",
783 "test",
784 "@dff@",
785 "1",
786 "",
787 ];
788 for name in invalid_endpoint_names {
789 let endpoint = Endpoint::from_string(name);
790 assert_eq!(endpoint, Err(InvalidEndpointError::InvalidCharacters));
791 }
792 }
793
794 #[test]
795 fn invalid_instance() {
796 let endpoint = Endpoint::person("test", EndpointInstance::Instance(0));
797 assert_eq!(endpoint, Err(InvalidEndpointError::InvalidInstance));
798
799 let endpoint =
800 Endpoint::person("test", EndpointInstance::Instance(65535));
801 assert_eq!(endpoint, Err(InvalidEndpointError::InvalidInstance));
802 }
803
804 #[test]
805 fn special_instances() {
806 let endpoint = Endpoint::from_string("@+unyt/0");
807 assert_eq!(
808 endpoint,
809 Ok(Endpoint {
810 ty: EndpointType::Institution,
811 identifier: Endpoint::name_to_bytes("unyt").unwrap(),
812 instance: EndpointInstance::Any,
813 })
814 );
815
816 let endpoint = Endpoint::from_string("@+unyt/65535");
817 assert_eq!(
818 endpoint,
819 Ok(Endpoint {
820 ty: EndpointType::Institution,
821 identifier: Endpoint::name_to_bytes("unyt").unwrap(),
822 instance: EndpointInstance::All,
823 })
824 );
825
826 let endpoint = Endpoint::from_string("@+unyt/*");
827 assert_eq!(
828 endpoint,
829 Ok(Endpoint {
830 ty: EndpointType::Institution,
831 identifier: Endpoint::name_to_bytes("unyt").unwrap(),
832 instance: EndpointInstance::All,
833 })
834 );
835 }
836
837 #[test]
838 fn any_instance() {
839 let binary = [
841 EndpointType::Anonymous as u8,
842 0xFF,
843 0xFF,
844 0xFF,
845 0xFF,
846 0xFF,
847 0xFF,
848 0xFF,
849 0xFF,
850 0xFF,
851 0xFF,
852 0xFF,
853 0xFF,
854 0xFF,
855 0xFF,
856 0xFF,
857 0xFF,
858 0xFF,
859 0xFF,
860 0x00,
861 0x00,
862 ];
863 let endpoint = Endpoint::from_slice(binary);
864 assert_eq!(endpoint, Ok(Endpoint::ANY));
865 }
866
867 #[test]
868 fn special_endpoints() {
869 let endpoint = Endpoint::from_string("@@any").unwrap();
870 assert_eq!(endpoint.to_string(), "@@any");
871 assert_eq!(endpoint, Endpoint::ANY);
872
873 let endpoint = Endpoint::from_string("@@any/42").unwrap();
874 assert_eq!(endpoint.to_string(), "@@any/42");
875
876 let endpoint = Endpoint::from_string("@@local").unwrap();
877 assert_eq!(endpoint.to_string(), "@@local");
878 assert_eq!(endpoint, Endpoint::LOCAL);
879
880 let endpoint =
881 Endpoint::from_string("@@FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
882 .unwrap();
883 assert_eq!(endpoint, Endpoint::ANY);
884
885 let endpoint =
886 Endpoint::from_string("@@000000000000000000000000000000000000")
887 .unwrap();
888 assert_eq!(endpoint, Endpoint::LOCAL);
889 }
890
891 #[test]
892 fn format_named_endpoint() {
893 let endpoint = Endpoint::person("test", EndpointInstance::Any).unwrap();
894 assert_eq!(endpoint.to_string(), "@test");
895
896 let endpoint =
897 Endpoint::institution("test", EndpointInstance::Any).unwrap();
898 assert_eq!(endpoint.to_string(), "@+test");
899
900 let endpoint =
901 Endpoint::person("test", EndpointInstance::Instance(42)).unwrap();
902 assert_eq!(endpoint.to_string(), "@test/42");
903
904 let endpoint = Endpoint::person("test", EndpointInstance::All).unwrap();
905 assert_eq!(endpoint.to_string(), "@test/*");
906 }
907
908 #[test]
909 fn format_anonymous_endpoint() {
910 let endpoint =
911 Endpoint::anonymous([0xaa; 18], EndpointInstance::Any).unwrap();
912 assert_eq!(
913 endpoint.to_string(),
914 "@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
915 );
916
917 let endpoint =
918 Endpoint::from_string("@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/42")
919 .unwrap();
920 assert_eq!(
921 endpoint.to_string(),
922 "@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/42"
923 );
924 }
925
926 #[test]
927 fn random_anonymous_endpoint() {
928 let endpoint1 = Endpoint::random();
929 let endpoint2 = Endpoint::random();
930 assert_ne!(endpoint1, endpoint2);
931 assert_eq!(endpoint1.ty, EndpointType::Anonymous);
932 assert_eq!(endpoint2.ty, EndpointType::Anonymous);
933 }
934}