Skip to main content

datex_core/values/core_values/
endpoint.rs

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