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