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