datex_core/values/core_values/
endpoint.rs

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