up_rust/
uri.rs

1/********************************************************************************
2 * Copyright (c) 2023 Contributors to the Eclipse Foundation
3 *
4 * See the NOTICE file(s) distributed with this work for additional
5 * information regarding copyright ownership.
6 *
7 * This program and the accompanying materials are made available under the
8 * terms of the Apache License Version 2.0 which is available at
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * SPDX-License-Identifier: Apache-2.0
12 ********************************************************************************/
13
14// [impl->dsn~uri-data-model-naming~1]
15// [impl->req~uri-data-model-proto~1]
16
17use std::hash::{Hash, Hasher};
18use std::str::FromStr;
19use std::sync::LazyLock;
20
21use uriparse::{Authority, URIReference};
22
23pub use crate::up_core_api::uri::UUri;
24
25pub(crate) const WILDCARD_AUTHORITY: &str = "*";
26pub(crate) const WILDCARD_ENTITY_INSTANCE: u32 = 0xFFFF_0000;
27pub(crate) const WILDCARD_ENTITY_TYPE: u32 = 0x0000_FFFF;
28pub(crate) const WILDCARD_ENTITY_VERSION: u32 = 0x0000_00FF;
29pub(crate) const WILDCARD_RESOURCE_ID: u32 = 0x0000_FFFF;
30
31pub(crate) const RESOURCE_ID_RESPONSE: u32 = 0;
32pub(crate) const RESOURCE_ID_MIN_EVENT: u32 = 0x8000;
33
34static AUTHORITY_NAME_PATTERN: LazyLock<regex::Regex> =
35    LazyLock::new(|| regex::Regex::new(r"^[a-z0-9\-._~]{0,128}$").unwrap());
36
37#[derive(Debug)]
38pub enum UUriError {
39    SerializationError(String),
40    ValidationError(String),
41}
42
43impl UUriError {
44    pub fn serialization_error<T>(message: T) -> UUriError
45    where
46        T: Into<String>,
47    {
48        Self::SerializationError(message.into())
49    }
50
51    pub fn validation_error<T>(message: T) -> UUriError
52    where
53        T: Into<String>,
54    {
55        Self::ValidationError(message.into())
56    }
57}
58
59impl std::fmt::Display for UUriError {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        match self {
62            Self::SerializationError(e) => f.write_fmt(format_args!("Serialization error: {e}")),
63            Self::ValidationError(e) => f.write_fmt(format_args!("Validation error: {e}")),
64        }
65    }
66}
67
68impl std::error::Error for UUriError {}
69
70// [impl->req~uri-serialization~1]
71impl From<&UUri> for String {
72    /// Serializes a uProtocol URI to a URI string.
73    ///
74    /// # Arguments
75    ///
76    /// * `uri` - The URI to serialize. Note that the given URI is **not** validated before serialization.
77    ///   In particular, the URI's version and resource ID length are not checked to be within limits.
78    ///
79    /// # Returns
80    ///
81    /// The output of [`UUri::to_uri`] without including the uProtocol scheme.
82    ///
83    /// # Examples
84    ///
85    /// ```rust
86    /// use up_rust::UUri;
87    ///
88    /// let uuri = UUri {
89    ///     authority_name: String::from("vin.vehicles"),
90    ///     ue_id: 0x0000_800A,
91    ///     ue_version_major: 0x02,
92    ///     resource_id: 0x0000_1a50,
93    ///     ..Default::default()
94    /// };
95    ///
96    /// let uri_string = String::from(&uuri);
97    /// assert_eq!(uri_string, "//vin.vehicles/800A/2/1A50");
98    /// ````
99    fn from(uri: &UUri) -> Self {
100        UUri::to_uri(uri, false)
101    }
102}
103
104impl FromStr for UUri {
105    type Err = UUriError;
106
107    /// Attempts to parse a `String` into a `UUri`.
108    ///
109    /// As part of the parsing, the _authority_ of the URI is getting normalized. This means that all characters
110    /// are converted to lowercase, no bytes that are in the unreserved character set remain percent-encoded,
111    /// and all alphabetical characters in percent-encodings are converted to uppercase.
112    ///
113    /// # Arguments
114    ///
115    /// * `uri` - The `String` to be converted into a `UUri`.
116    ///
117    /// # Returns
118    ///
119    /// A `Result` containing either the `UUri` representation of the URI or a `SerializationError`.
120    ///
121    /// # Examples
122    ///
123    /// ```rust
124    /// use std::str::FromStr;
125    /// use up_rust::UUri;
126    ///
127    /// let uri = UUri {
128    ///     authority_name: "vin.vehicles".to_string(),
129    ///     ue_id: 0x000A_8000,
130    ///     ue_version_major: 0x02,
131    ///     resource_id: 0x0000_1a50,
132    ///     ..Default::default()
133    /// };
134    ///
135    /// let uri_from = UUri::from_str("//vin.vehicles/A8000/2/1A50").unwrap();
136    /// assert_eq!(uri, uri_from);
137    /// ````
138    // [impl->dsn~uri-authority-name-length~1]
139    // [impl->dsn~uri-scheme~1]
140    // [impl->dsn~uri-host-only~2]
141    // [impl->dsn~uri-authority-mapping~1]
142    // [impl->dsn~uri-path-mapping~2]
143    // [impl->req~uri-serialization~1]
144    fn from_str(uri: &str) -> Result<Self, Self::Err> {
145        if uri.is_empty() {
146            return Err(UUriError::serialization_error("URI is empty"));
147        }
148        let parsed_uri = URIReference::try_from(uri)
149            .map_err(|e| UUriError::serialization_error(e.to_string()))?;
150
151        if let Some(scheme) = parsed_uri.scheme() {
152            if scheme.ne("up") {
153                return Err(UUriError::serialization_error(
154                    "uProtocol URI must use 'up' scheme",
155                ));
156            }
157        }
158        if parsed_uri.has_query() {
159            return Err(UUriError::serialization_error(
160                "uProtocol URI must not contain query",
161            ));
162        }
163        if parsed_uri.has_fragment() {
164            return Err(UUriError::serialization_error(
165                "uProtocol URI must not contain fragment",
166            ));
167        }
168        let authority_name = parsed_uri
169            .authority()
170            .map_or(Ok(String::default()), Self::verify_parsed_authority)?;
171
172        let path_segments = parsed_uri.path().segments();
173        if path_segments.len() != 3 {
174            return Err(UUriError::serialization_error(
175                "uProtocol URI must contain entity ID, entity version and resource ID",
176            ));
177        }
178        let entity = path_segments[0].as_str();
179        if entity.is_empty() {
180            return Err(UUriError::serialization_error(
181                "URI must contain non-empty entity ID",
182            ));
183        }
184        let ue_id = u32::from_str_radix(entity, 16)
185            .map_err(|e| UUriError::serialization_error(format!("Cannot parse entity ID: {e}")))?;
186        let version = path_segments[1].as_str();
187        if version.is_empty() {
188            return Err(UUriError::serialization_error(
189                "URI must contain non-empty entity version",
190            ));
191        }
192        let ue_version_major = u8::from_str_radix(version, 16).map_err(|e| {
193            UUriError::serialization_error(format!("Cannot parse entity version: {e}"))
194        })?;
195        let resource = path_segments[2].as_str();
196        if resource.is_empty() {
197            return Err(UUriError::serialization_error(
198                "URI must contain non-empty resource ID",
199            ));
200        }
201        let resource_id = u16::from_str_radix(resource, 16).map_err(|e| {
202            UUriError::serialization_error(format!("Cannot parse resource ID: {e}"))
203        })?;
204
205        Ok(UUri {
206            authority_name,
207            ue_id,
208            ue_version_major: ue_version_major as u32,
209            resource_id: resource_id as u32,
210            ..Default::default()
211        })
212    }
213}
214
215// [impl->req~uri-serialization~1]
216impl TryFrom<String> for UUri {
217    type Error = UUriError;
218
219    /// Attempts to serialize a `String` into a `UUri`.
220    ///
221    /// # Arguments
222    ///
223    /// * `uri` - The `String` to be converted into a `UUri`.
224    ///
225    /// # Returns
226    ///
227    /// A `Result` containing either the `UUri` representation of the URI or a `SerializationError`.
228    ///
229    /// # Examples
230    ///
231    /// ```rust
232    /// use up_rust::UUri;
233    ///
234    /// let uri = UUri {
235    ///     authority_name: "".to_string(),
236    ///     ue_id: 0x001A_8000,
237    ///     ue_version_major: 0x02,
238    ///     resource_id: 0x0000_1a50,
239    ///     ..Default::default()
240    /// };
241    ///
242    /// let uri_from = UUri::try_from("/1A8000/2/1A50".to_string()).unwrap();
243    /// assert_eq!(uri, uri_from);
244    /// ````
245    fn try_from(uri: String) -> Result<Self, Self::Error> {
246        UUri::from_str(uri.as_str())
247    }
248}
249
250// [impl->req~uri-serialization~1]
251impl TryFrom<&str> for UUri {
252    type Error = UUriError;
253
254    /// Attempts to serialize a `String` into a `UUri`.
255    ///
256    /// # Arguments
257    ///
258    /// * `uri` - The `String` to be converted into a `UUri`.
259    ///
260    /// # Returns
261    ///
262    /// A `Result` containing either the `UUri` representation of the URI or a `SerializationError`.
263    ///
264    /// # Examples
265    ///
266    /// ```rust
267    /// use up_rust::UUri;
268    ///
269    /// let uri = UUri {
270    ///     authority_name: "".to_string(),
271    ///     ue_id: 0x001A_8000,
272    ///     ue_version_major: 0x02,
273    ///     resource_id: 0x0000_1a50,
274    ///     ..Default::default()
275    /// };
276    ///
277    /// let uri_from = UUri::try_from("/1A8000/2/1A50").unwrap();
278    /// assert_eq!(uri, uri_from);
279    /// ````
280    fn try_from(uri: &str) -> Result<Self, Self::Error> {
281        UUri::from_str(uri)
282    }
283}
284
285impl Hash for UUri {
286    fn hash<H: Hasher>(&self, state: &mut H) {
287        self.authority_name.hash(state);
288        self.ue_id.hash(state);
289        self.ue_version_major.hash(state);
290        self.resource_id.hash(state);
291    }
292}
293
294impl Eq for UUri {}
295
296impl UUri {
297    /// Serializes this UUri to a URI string.
298    ///
299    /// # Arguments
300    ///
301    /// * `include_scheme` - Indicates whether to include the uProtocol scheme (`up`) in the URI.
302    ///
303    /// # Returns
304    ///
305    /// The URI as defined by the [uProtocol Specification](https://github.com/eclipse-uprotocol/up-spec).
306    ///
307    /// # Examples
308    ///
309    /// ```rust
310    /// use up_rust::UUri;
311    ///
312    /// let uuri = UUri {
313    ///     authority_name: String::from("vin.vehicles"),
314    ///     ue_id: 0x0000_800A,
315    ///     ue_version_major: 0x02,
316    ///     resource_id: 0x0000_1a50,
317    ///     ..Default::default()
318    /// };
319    ///
320    /// let uri_string = uuri.to_uri(true);
321    /// assert_eq!(uri_string, "up://vin.vehicles/800A/2/1A50");
322    /// ````
323    // [impl->dsn~uri-authority-mapping~1]
324    // [impl->dsn~uri-path-mapping~2]
325    // [impl->req~uri-serialization~1]
326    pub fn to_uri(&self, include_scheme: bool) -> String {
327        let mut output = String::default();
328        if include_scheme {
329            output.push_str("up:");
330        }
331        if !self.authority_name.is_empty() {
332            output.push_str("//");
333            output.push_str(&self.authority_name);
334        }
335        let uri = format!(
336            "/{:X}/{:X}/{:X}",
337            self.ue_id, self.ue_version_major, self.resource_id
338        );
339        output.push_str(&uri);
340        output
341    }
342
343    /// Creates a new UUri from its parts.
344    ///
345    /// # Errors
346    ///
347    /// Returns a [`UUriError::ValidationError`] if the authority does not comply with the UUri specification.
348    ///
349    /// # Examples
350    ///
351    /// ```rust
352    /// use up_rust::UUri;
353    ///
354    /// assert!(UUri::try_from_parts("vin", 0x0000_5a6b, 0x01, 0x0001).is_ok());
355    /// ```
356    // [impl->dsn~uri-authority-name-length~1]
357    // [impl->dsn~uri-host-only~2]
358    pub fn try_from_parts(
359        authority: &str,
360        entity_id: u32,
361        entity_version: u8,
362        resource_id: u16,
363    ) -> Result<Self, UUriError> {
364        let authority_name = Self::verify_authority(authority)?;
365        Ok(UUri {
366            authority_name,
367            ue_id: entity_id,
368            ue_version_major: entity_version as u32,
369            resource_id: resource_id as u32,
370            ..Default::default()
371        })
372    }
373
374    /// Gets a URI that consists of wildcards only and therefore matches any URI.
375    pub fn any() -> Self {
376        Self::any_with_resource_id(WILDCARD_RESOURCE_ID)
377    }
378
379    /// Gets a URI that consists of wildcards and the specific resource ID.
380    pub fn any_with_resource_id(resource_id: u32) -> Self {
381        UUri {
382            authority_name: WILDCARD_AUTHORITY.to_string(),
383            ue_id: WILDCARD_ENTITY_INSTANCE | WILDCARD_ENTITY_TYPE,
384            ue_version_major: WILDCARD_ENTITY_VERSION,
385            resource_id,
386            ..Default::default()
387        }
388    }
389
390    /// Gets the authority name part from this uProtocol URI.
391    ///
392    /// # Examples
393    ///
394    /// ```rust
395    /// use up_rust::UUri;
396    ///
397    /// let uri = UUri::try_from_parts("my-vehicle", 0x10101234, 0x01, 0x9a10).unwrap();
398    /// assert_eq!(uri.authority_name(), *"my-vehicle");
399    /// ```
400    pub fn authority_name(&self) -> String {
401        self.authority_name.to_owned()
402    }
403
404    // Gets the uEntity type identifier part from this uProtocol URI.
405    ///
406    /// # Examples
407    ///
408    /// ```rust
409    /// use up_rust::UUri;
410    ///
411    /// let uri = UUri::try_from_parts("my-vehicle", 0x10101234, 0x01, 0x9a10).unwrap();
412    /// assert_eq!(uri.uentity_type_id(), 0x1234);
413    /// ```
414    pub fn uentity_type_id(&self) -> u16 {
415        (self.ue_id & WILDCARD_ENTITY_TYPE) as u16
416    }
417
418    // Gets the uEntity instance identifier part from this uProtocol URI.
419    ///
420    /// # Examples
421    ///
422    /// ```rust
423    /// use up_rust::UUri;
424    ///
425    /// let uri = UUri::try_from_parts("my-vehicle", 0x10101234, 0x01, 0x9a10).unwrap();
426    /// assert_eq!(uri.uentity_instance_id(), 0x1010);
427    /// ```
428    pub fn uentity_instance_id(&self) -> u16 {
429        ((self.ue_id & WILDCARD_ENTITY_INSTANCE) >> 16) as u16
430    }
431
432    // Gets the major version part from this uProtocol URI.
433    ///
434    /// # Examples
435    ///
436    /// ```rust
437    /// use up_rust::UUri;
438    ///
439    /// let uri = UUri::try_from_parts("my-vehicle", 0x10101234, 0x01, 0x9a10).unwrap();
440    /// assert_eq!(uri.uentity_major_version(), 0x01);
441    /// ```
442    pub fn uentity_major_version(&self) -> u8 {
443        (self.ue_version_major & WILDCARD_ENTITY_VERSION) as u8
444    }
445
446    // Gets the resource identifier part from this uProtocol URI.
447    ///
448    /// # Examples
449    ///
450    /// ```rust
451    /// use up_rust::UUri;
452    ///
453    /// let uri = UUri::try_from_parts("my-vehicle", 0x10101234, 0x01, 0x9a10).unwrap();
454    /// assert_eq!(uri.resource_id(), 0x9a10);
455    /// ```
456    pub fn resource_id(&self) -> u16 {
457        (self.resource_id & WILDCARD_RESOURCE_ID) as u16
458    }
459
460    // [impl->dsn~uri-authority-name-length~1]
461    // [impl->dsn~uri-host-only~2]
462    pub(crate) fn verify_authority(authority: &str) -> Result<String, UUriError> {
463        Authority::try_from(authority)
464            .map_err(|e| UUriError::validation_error(format!("invalid authority: {e}")))
465            .and_then(|auth| Self::verify_parsed_authority(&auth))
466    }
467
468    // [impl->dsn~uri-authority-name-length~1]
469    // [impl->dsn~uri-host-only~2]
470    pub(crate) fn verify_parsed_authority(auth: &Authority) -> Result<String, UUriError> {
471        if auth.has_port() {
472            Err(UUriError::validation_error(
473                "uProtocol URI's authority must not contain port",
474            ))
475        } else if auth.has_username() || auth.has_password() {
476            Err(UUriError::validation_error(
477                "uProtocol URI's authority must not contain userinfo",
478            ))
479        } else {
480            match auth.host() {
481                uriparse::Host::IPv4Address(_) | uriparse::Host::IPv6Address(_) => {
482                    Ok(auth.host().to_string())
483                }
484                uriparse::Host::RegisteredName(name) => {
485                    if !WILDCARD_AUTHORITY.eq(name.as_str())
486                        && !AUTHORITY_NAME_PATTERN.is_match(name.as_str())
487                    {
488                        return Err(UUriError::validation_error(
489                            "uProtocol URI's authority contains invalid characters",
490                        ));
491                    }
492                    Ok(name.to_string())
493                }
494            }
495        }
496    }
497
498    fn verify_major_version(major_version: u32) -> Result<u8, UUriError> {
499        u8::try_from(major_version).map_err(|_e| {
500            UUriError::ValidationError(
501                "uProtocol URI's major version must be an 8 bit unsigned integer".to_string(),
502            )
503        })
504    }
505
506    fn verify_resource_id(resource_id: u32) -> Result<u16, UUriError> {
507        u16::try_from(resource_id).map_err(|_e| {
508            UUriError::ValidationError(
509                "uProtocol URI's resource ID must be a 16 bit unsigned integer".to_string(),
510            )
511        })
512    }
513
514    /// Verifies that this UUri is indeed a valid uProtocol URI.
515    ///
516    /// This check is not necessary, if any of UUri's constructors functions has been used
517    /// to create the URI. However, if the origin of a UUri is unknown, e.g. when it has
518    /// been deserialized from a protobuf, then this function can be used to check if all
519    /// properties are compliant with the uProtocol specification.
520    ///
521    /// # Errors
522    ///
523    /// Returns an error if this UUri is not a valid uProtocol URI. The returned error may
524    /// contain details regarding the cause of the validation to have failed.
525    ///
526    /// # Examples
527    ///
528    /// ```rust
529    /// use up_rust::UUri;
530    ///
531    /// let uuri = UUri {
532    ///   authority_name: "valid_name".into(),
533    ///   ue_id: 0x1000,
534    ///   ue_version_major: 0x01,
535    ///   resource_id: 0x8100,
536    ///   ..Default::default()
537    /// };
538    /// assert!(uuri.check_validity().is_ok());
539    /// ```
540    pub fn check_validity(&self) -> Result<(), UUriError> {
541        Self::verify_authority(self.authority_name.as_str())?;
542        Self::verify_major_version(self.ue_version_major)?;
543        Self::verify_resource_id(self.resource_id)?;
544        Ok(())
545    }
546
547    /// Checks if this URI is empty.
548    ///
549    /// # Returns
550    ///
551    /// 'true' if this URI is equal to `UUri::default()`, `false` otherwise.
552    ///
553    /// # Examples
554    ///
555    /// ```rust
556    /// use up_rust::UUri;
557    ///
558    /// let uuri = UUri::try_from_parts("myvin", 0xa13b, 0x01, 0x7f4e).unwrap();
559    /// assert!(!uuri.is_empty());
560    /// assert!(UUri::default().is_empty());
561    /// ```
562    pub fn is_empty(&self) -> bool {
563        self.eq(&UUri::default())
564    }
565
566    /// Check if an `UUri` is remote, by comparing authority fields.
567    /// UUris with empty authority are considered to be local.
568    ///
569    /// # Returns
570    ///
571    /// 'true' if other_uri has a different authority than `Self`, `false` otherwise.
572    ///
573    /// # Examples
574    ///
575    /// ```rust
576    /// use std::str::FromStr;
577    /// use up_rust::UUri;
578    ///
579    /// let authority_a = UUri::from_str("up://authority.a/100A/1/0").unwrap();
580    /// let authority_b = UUri::from_str("up://authority.b/200B/2/20").unwrap();
581    /// assert!(authority_a.is_remote(&authority_b));
582    ///
583    /// let authority_local = UUri::from_str("up:///100A/1/0").unwrap();
584    /// assert!(!authority_local.is_remote(&authority_a));
585    ///
586    /// let authority_wildcard = UUri::from_str("up://*/100A/1/0").unwrap();
587    /// assert!(!authority_wildcard.is_remote(&authority_a));
588    /// assert!(!authority_a.is_remote(&authority_wildcard));
589    /// assert!(!authority_wildcard.is_remote(&authority_wildcard));
590    /// ````
591    pub fn is_remote(&self, other_uri: &UUri) -> bool {
592        self.is_remote_authority(&other_uri.authority_name)
593    }
594
595    /// Check if an authority is remote compared to the authority field of the UUri.
596    /// Empty authorities are considered to be local.
597    ///
598    /// # Returns
599    ///
600    /// 'true' if authority is a different than `Self.authority_name`, `false` otherwise.
601    ///
602    /// # Examples
603    ///
604    /// ```rust
605    /// use std::str::FromStr;
606    /// use up_rust::UUri;
607    ///
608    /// let authority_a = UUri::from_str("up://authority.a/100A/1/0").unwrap();
609    /// let authority_b = "authority.b".to_string();
610    /// assert!(authority_a.is_remote_authority(&authority_b));
611    ///
612    /// let authority_local = "".to_string();
613    /// assert!(!authority_a.is_remote_authority(&authority_local));
614    ///
615    /// let authority_wildcard = "*".to_string();
616    /// assert!(!authority_a.is_remote_authority(&authority_wildcard));
617    /// ```
618    pub fn is_remote_authority(&self, authority: &String) -> bool {
619        !authority.is_empty()
620            && !self.authority_name.is_empty()
621            && !self.has_wildcard_authority()
622            && authority != WILDCARD_AUTHORITY
623            && self.authority_name != *authority
624    }
625
626    /// Checks if this UUri has an empty authority name.
627    ///
628    /// # Examples
629    ///
630    /// ```rust
631    /// use up_rust::UUri;
632    ///
633    /// let uuri = UUri::try_from_parts("", 0x9b3a, 0x01, 0x145b).unwrap();
634    /// assert!(uuri.has_empty_authority());
635    /// ```
636    pub fn has_empty_authority(&self) -> bool {
637        self.authority_name.is_empty()
638    }
639
640    /// Checks if this UUri has a wildcard authority name.
641    ///
642    /// # Examples
643    ///
644    /// ```rust
645    /// use up_rust::UUri;
646    ///
647    /// let uuri = UUri::try_from_parts("*", 0x9b3a, 0x01, 0x145b).unwrap();
648    /// assert!(uuri.has_wildcard_authority());
649    /// ```
650    pub fn has_wildcard_authority(&self) -> bool {
651        self.authority_name == WILDCARD_AUTHORITY
652    }
653
654    /// Checks if this UUri has an entity identifier matching any instance.
655    ///
656    /// # Examples
657    ///
658    /// ```rust
659    /// use up_rust::UUri;
660    ///
661    /// let uuri = UUri::try_from_parts("vin", 0xFFFF_0123, 0x01, 0x145b).unwrap();
662    /// assert!(uuri.has_wildcard_entity_instance());
663    /// ```
664    pub fn has_wildcard_entity_instance(&self) -> bool {
665        self.ue_id & WILDCARD_ENTITY_INSTANCE == WILDCARD_ENTITY_INSTANCE
666    }
667
668    /// Checks if this UUri has an entity identifier matching any type.
669    ///
670    /// # Examples
671    ///
672    /// ```rust
673    /// use up_rust::UUri;
674    ///
675    /// let uuri = UUri::try_from_parts("vin", 0x00C0_FFFF, 0x01, 0x145b).unwrap();
676    /// assert!(uuri.has_wildcard_entity_type());
677    /// ```
678    pub fn has_wildcard_entity_type(&self) -> bool {
679        self.ue_id & WILDCARD_ENTITY_TYPE == WILDCARD_ENTITY_TYPE
680    }
681
682    /// Checks if this UUri has a wildcard major version.
683    ///
684    /// # Examples
685    ///
686    /// ```rust
687    /// use up_rust::UUri;
688    ///
689    /// let uuri = UUri::try_from_parts("vin", 0x9b3a, 0xFF, 0x145b).unwrap();
690    /// assert!(uuri.has_wildcard_version());
691    /// ```
692    pub fn has_wildcard_version(&self) -> bool {
693        self.ue_version_major == WILDCARD_ENTITY_VERSION
694    }
695
696    /// Checks if this UUri has a wildcard entity identifier.
697    ///
698    /// # Examples
699    ///
700    /// ```rust
701    /// use up_rust::UUri;
702    ///
703    /// let uuri = UUri::try_from_parts("vin", 0x9b3a, 0x01, 0xFFFF).unwrap();
704    /// assert!(uuri.has_wildcard_resource_id());
705    /// ```
706    pub fn has_wildcard_resource_id(&self) -> bool {
707        self.resource_id == WILDCARD_RESOURCE_ID
708    }
709
710    /// Verifies that this UUri does not contain any wildcards.
711    ///
712    /// # Errors
713    ///
714    /// Returns an error if any of this UUri's properties contain a wildcard value.
715    ///
716    /// # Examples
717    ///
718    /// ```rust
719    /// use up_rust::UUri;
720    ///
721    /// let uri = UUri {
722    ///     authority_name: String::from("VIN.vehicles"),
723    ///     ue_id: 0x0000_2310,
724    ///     ue_version_major: 0x03,
725    ///     resource_id: 0xa000,
726    ///     ..Default::default()
727    /// };
728    /// assert!(uri.verify_no_wildcards().is_ok());
729    /// ```
730    pub fn verify_no_wildcards(&self) -> Result<(), UUriError> {
731        if self.has_wildcard_authority() {
732            Err(UUriError::validation_error(format!(
733                "Authority must not contain wildcard character [{WILDCARD_AUTHORITY}]"
734            )))
735        } else if self.has_wildcard_entity_instance() {
736            Err(UUriError::validation_error(format!(
737                "Entity instance ID must not be set to wildcard value [{WILDCARD_ENTITY_INSTANCE:#X}]")))
738        } else if self.has_wildcard_entity_type() {
739            Err(UUriError::validation_error(format!(
740                "Entity type ID must not be set to wildcard value [{WILDCARD_ENTITY_TYPE:#X}]"
741            )))
742        } else if self.has_wildcard_version() {
743            Err(UUriError::validation_error(format!(
744                "Entity version must not be set to wildcard value [{WILDCARD_ENTITY_VERSION:#X}]"
745            )))
746        } else if self.has_wildcard_resource_id() {
747            Err(UUriError::validation_error(format!(
748                "Resource ID must not be set to wildcard value [{WILDCARD_RESOURCE_ID:#X}]"
749            )))
750        } else {
751            Ok(())
752        }
753    }
754
755    /// Checks if this UUri refers to a service method.
756    ///
757    /// Returns `true` if 0 < resource ID < 0x8000.
758    ///
759    /// # Examples
760    ///
761    /// ```rust
762    /// use up_rust::UUri;
763    ///
764    /// let uri = UUri {
765    ///     resource_id: 0x7FFF,
766    ///     ..Default::default()
767    /// };
768    /// assert!(uri.is_rpc_method());
769    /// ```
770    pub fn is_rpc_method(&self) -> bool {
771        self.resource_id > RESOURCE_ID_RESPONSE && self.resource_id < RESOURCE_ID_MIN_EVENT
772    }
773
774    /// Verifies that this UUri refers to a service method.
775    ///
776    /// # Errors
777    ///
778    /// Returns an error if [`Self::is_rpc_method`] fails or
779    /// the UUri [contains any wildcards](Self::verify_no_wildcards).
780    ///
781    /// # Examples
782    ///
783    /// ```rust
784    /// use up_rust::UUri;
785    ///
786    /// let uri = UUri {
787    ///     resource_id: 0x8000,
788    ///     ..Default::default()
789    /// };
790    /// assert!(uri.verify_rpc_method().is_err());
791    ///
792    /// let uri = UUri {
793    ///     resource_id: 0x0,
794    ///     ..Default::default()
795    /// };
796    /// assert!(uri.verify_rpc_method().is_err());
797    /// ```
798    pub fn verify_rpc_method(&self) -> Result<(), UUriError> {
799        if !self.is_rpc_method() {
800            Err(UUriError::validation_error(format!(
801                "Resource ID must be a value from ]{RESOURCE_ID_RESPONSE:#X}, {RESOURCE_ID_MIN_EVENT:#X}[")))
802        } else {
803            self.verify_no_wildcards()
804        }
805    }
806
807    /// Checks if this UUri represents a destination for a Notification.
808    ///
809    /// Returns `true` if resource ID is 0.
810    ///
811    /// # Examples
812    ///
813    /// ```rust
814    /// use up_rust::UUri;
815    ///
816    /// let uri = UUri {
817    ///     resource_id: 0,
818    ///     ..Default::default()
819    /// };
820    /// assert!(uri.is_notification_destination());
821    /// ```
822    pub fn is_notification_destination(&self) -> bool {
823        self.resource_id == RESOURCE_ID_RESPONSE
824    }
825
826    /// Checks if this UUri represents an RPC response address.
827    ///
828    /// Returns `true` if resource ID is 0.
829    ///
830    /// # Examples
831    ///
832    /// ```rust
833    /// use up_rust::UUri;
834    ///
835    /// let uri = UUri {
836    ///     resource_id: 0,
837    ///     ..Default::default()
838    /// };
839    /// assert!(uri.is_rpc_response());
840    /// ```
841    pub fn is_rpc_response(&self) -> bool {
842        self.resource_id == RESOURCE_ID_RESPONSE
843    }
844
845    /// Verifies that this UUri represents an RPC response address.
846    ///
847    /// # Errors
848    ///
849    /// Returns an error if [`Self::is_rpc_response`] fails or
850    /// the UUri [contains any wildcards](Self::verify_no_wildcards).
851    ///
852    /// # Examples
853    ///
854    /// ```rust
855    /// use up_rust::UUri;
856    ///
857    /// let uri = UUri {
858    ///     resource_id: 0x4001,
859    ///     ..Default::default()
860    /// };
861    /// assert!(uri.verify_rpc_response().is_err());
862    /// ```
863    pub fn verify_rpc_response(&self) -> Result<(), UUriError> {
864        if !self.is_rpc_response() {
865            Err(UUriError::validation_error(format!(
866                "Resource ID must be {RESOURCE_ID_RESPONSE:#X}"
867            )))
868        } else {
869            self.verify_no_wildcards()
870        }
871    }
872
873    /// Checks if this UUri can be used as the source of an event.
874    ///
875    /// Returns `true` if resource ID >= 0x8000.
876    ///
877    /// # Examples
878    ///
879    /// ```rust
880    /// use up_rust::UUri;
881    ///
882    /// let uri = UUri {
883    ///     resource_id: 0x8000,
884    ///     ..Default::default()
885    /// };
886    /// assert!(uri.is_event());
887    /// ```
888    pub fn is_event(&self) -> bool {
889        self.resource_id >= RESOURCE_ID_MIN_EVENT
890    }
891
892    /// Verifies that this UUri can be used as the source of an event.
893    ///
894    /// # Errors
895    ///
896    /// Returns an error if [`Self::is_event`] fails or
897    /// the UUri [contains any wildcards](Self::verify_no_wildcards).
898    ///
899    /// # Examples
900    ///
901    /// ```rust
902    /// use up_rust::UUri;
903    ///
904    /// let uri = UUri {
905    ///     resource_id: 0x7FFF,
906    ///     ..Default::default()
907    /// };
908    /// assert!(uri.verify_event().is_err());
909    /// ```
910    pub fn verify_event(&self) -> Result<(), UUriError> {
911        if !self.is_event() {
912            Err(UUriError::validation_error(format!(
913                "Resource ID must be >= {RESOURCE_ID_MIN_EVENT:#X}"
914            )))
915        } else {
916            self.verify_no_wildcards()
917        }
918    }
919
920    fn matches_authority(&self, candidate: &UUri) -> bool {
921        self.has_wildcard_authority() || self.authority_name == candidate.authority_name
922    }
923
924    fn matches_entity_type(&self, candidate: &UUri) -> bool {
925        self.has_wildcard_entity_type() || self.uentity_type_id() == candidate.uentity_type_id()
926    }
927
928    fn matches_entity_instance(&self, candidate: &UUri) -> bool {
929        self.has_wildcard_entity_instance()
930            || self.uentity_instance_id() == candidate.uentity_instance_id()
931    }
932
933    fn matches_entity_version(&self, candidate: &UUri) -> bool {
934        self.has_wildcard_version()
935            || self.uentity_major_version() == candidate.uentity_major_version()
936    }
937
938    fn matches_entity(&self, candidate: &UUri) -> bool {
939        self.matches_entity_type(candidate)
940            && self.matches_entity_instance(candidate)
941            && self.matches_entity_version(candidate)
942    }
943
944    fn matches_resource(&self, candidate: &UUri) -> bool {
945        self.has_wildcard_resource_id() || self.resource_id == candidate.resource_id
946    }
947
948    /// Checks if a given candidate URI matches a pattern.
949    ///
950    /// # Returns
951    ///
952    /// `true` if the candiadate matches the pattern represented by this UUri.
953    ///
954    /// # Examples
955    ///
956    /// ```rust
957    /// use up_rust::UUri;
958    ///
959    /// let pattern = UUri::try_from("//vin/A14F/3/FFFF").unwrap();
960    /// let candidate = UUri::try_from("//vin/A14F/3/B1D4").unwrap();
961    /// assert!(pattern.matches(&candidate));
962    /// ```
963    // [impl->dsn~uri-pattern-matching~2]
964    pub fn matches(&self, candidate: &UUri) -> bool {
965        self.matches_authority(candidate)
966            && self.matches_entity(candidate)
967            && self.matches_resource(candidate)
968    }
969}
970
971#[cfg(test)]
972mod tests {
973    use super::*;
974    use test_case::test_case;
975
976    // [utest->dsn~uri-authority-name-length~1]
977    // [utest->dsn~uri-host-only~2]
978    #[test_case(UUri {
979            authority_name: "invalid:5671".into(),
980            ue_id: 0x0000_8000,
981            ue_version_major: 0x01,
982            resource_id: 0x0002,
983            ..Default::default()
984        };
985        "for authority including port")]
986    #[test_case(UUri {
987            authority_name: ['a'; 129].iter().collect::<String>(),
988            ue_id: 0x0000_8000,
989            ue_version_major: 0x01,
990            resource_id: 0x0002,
991            ..Default::default()
992        };
993        "for authority exceeding max length")]
994    // additional test cases covering all sorts of invalid authority are
995    // included in [`test_from_string_fails`]
996    #[test_case(UUri {
997            authority_name: "valid".into(),
998            ue_id: 0x0000_8000,
999            ue_version_major: 0x0101,
1000            resource_id: 0x0002,
1001            ..Default::default()
1002        };
1003        "for invalid major version")]
1004    #[test_case(UUri {
1005            authority_name: "valid".into(),
1006            ue_id: 0x0000_8000,
1007            ue_version_major: 0x01,
1008            resource_id: 0x10002,
1009            ..Default::default()
1010        };
1011        "for invalid resource ID")]
1012    fn test_check_validity_fails(uuri: UUri) {
1013        assert!(uuri.check_validity().is_err());
1014    }
1015
1016    #[test_case("//*/A100/1/1"; "for any authority")]
1017    #[test_case("//vin/FFFF/1/1"; "for any entity type")]
1018    #[test_case("//vin/FFFF0ABC/1/1"; "for any entity instance")]
1019    #[test_case("//vin/A100/FF/1"; "for any version")]
1020    #[test_case("//vin/A100/1/FFFF"; "for any resource")]
1021    fn test_verify_no_wildcards_fails(uri: &str) {
1022        let uuri = UUri::try_from(uri).expect("should have been able to deserialize URI");
1023        assert!(uuri.verify_no_wildcards().is_err());
1024    }
1025
1026    // [utest->dsn~uri-authority-name-length~1]
1027    #[test]
1028    fn test_from_str_fails_for_authority_exceeding_max_length() {
1029        let host_name = "a".repeat(129);
1030        let uri = format!("//{}/A100/1/6501", host_name);
1031        assert!(UUri::from_str(&uri).is_err());
1032    }
1033
1034    // [utest->dsn~uri-path-mapping~2]
1035    #[test]
1036    fn test_from_str_accepts_lowercase_hex_encoding() {
1037        let result = UUri::try_from("up://vin/ffff0abc/a1/bcd1");
1038        assert!(result.is_ok_and(|uuri| {
1039            uuri.authority_name == "vin"
1040                && uuri.ue_id == 0xFFFF0ABC
1041                && uuri.ue_version_major == 0xA1
1042                && uuri.resource_id == 0xBCD1
1043        }));
1044    }
1045}