datex_core/values/core_values/
endpoint.rs

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