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 /// Verifies that the given authority name complies with the UUri specification.
461 ///
462 /// # Arguments
463 ///
464 /// * `authority` - The authority name to verify.
465 ///
466 /// # Errors
467 ///
468 /// Returns an error if the authority name is invalid.
469 ///
470 /// # Examples
471 ///
472 /// ```rust
473 /// use up_rust::UUri;
474 ///
475 /// assert!(UUri::verify_authority("my.vin").is_ok());
476 /// ```
477 // [impl->dsn~uri-authority-name-length~1]
478 // [impl->dsn~uri-host-only~2]
479 pub fn verify_authority(authority: &str) -> Result<String, UUriError> {
480 Authority::try_from(authority)
481 .map_err(|e| UUriError::validation_error(format!("invalid authority: {e}")))
482 .and_then(|auth| Self::verify_parsed_authority(&auth))
483 }
484
485 // [impl->dsn~uri-authority-name-length~1]
486 // [impl->dsn~uri-host-only~2]
487 pub(crate) fn verify_parsed_authority(auth: &Authority) -> Result<String, UUriError> {
488 if auth.has_port() {
489 Err(UUriError::validation_error(
490 "uProtocol URI's authority must not contain port",
491 ))
492 } else if auth.has_username() || auth.has_password() {
493 Err(UUriError::validation_error(
494 "uProtocol URI's authority must not contain userinfo",
495 ))
496 } else {
497 match auth.host() {
498 uriparse::Host::IPv4Address(_) | uriparse::Host::IPv6Address(_) => {
499 Ok(auth.host().to_string())
500 }
501 uriparse::Host::RegisteredName(name) => {
502 if !WILDCARD_AUTHORITY.eq(name.as_str())
503 && !AUTHORITY_NAME_PATTERN.is_match(name.as_str())
504 {
505 return Err(UUriError::validation_error(
506 "uProtocol URI's authority contains invalid characters",
507 ));
508 }
509 Ok(name.to_string())
510 }
511 }
512 }
513 }
514
515 fn verify_major_version(major_version: u32) -> Result<u8, UUriError> {
516 u8::try_from(major_version).map_err(|_e| {
517 UUriError::ValidationError(
518 "uProtocol URI's major version must be an 8 bit unsigned integer".to_string(),
519 )
520 })
521 }
522
523 fn verify_resource_id(resource_id: u32) -> Result<u16, UUriError> {
524 u16::try_from(resource_id).map_err(|_e| {
525 UUriError::ValidationError(
526 "uProtocol URI's resource ID must be a 16 bit unsigned integer".to_string(),
527 )
528 })
529 }
530
531 /// Verifies that this UUri is indeed a valid uProtocol URI.
532 ///
533 /// This check is not necessary, if any of UUri's constructors functions has been used
534 /// to create the URI. However, if the origin of a UUri is unknown, e.g. when it has
535 /// been deserialized from a protobuf, then this function can be used to check if all
536 /// properties are compliant with the uProtocol specification.
537 ///
538 /// # Errors
539 ///
540 /// Returns an error if this UUri is not a valid uProtocol URI. The returned error may
541 /// contain details regarding the cause of the validation to have failed.
542 ///
543 /// # Examples
544 ///
545 /// ```rust
546 /// use up_rust::UUri;
547 ///
548 /// let uuri = UUri {
549 /// authority_name: "valid_name".into(),
550 /// ue_id: 0x1000,
551 /// ue_version_major: 0x01,
552 /// resource_id: 0x8100,
553 /// ..Default::default()
554 /// };
555 /// assert!(uuri.check_validity().is_ok());
556 /// ```
557 pub fn check_validity(&self) -> Result<(), UUriError> {
558 Self::verify_authority(self.authority_name.as_str())?;
559 Self::verify_major_version(self.ue_version_major)?;
560 Self::verify_resource_id(self.resource_id)?;
561 Ok(())
562 }
563
564 /// Checks if this URI is empty.
565 ///
566 /// # Returns
567 ///
568 /// 'true' if this URI is equal to `UUri::default()`, `false` otherwise.
569 ///
570 /// # Examples
571 ///
572 /// ```rust
573 /// use up_rust::UUri;
574 ///
575 /// let uuri = UUri::try_from_parts("myvin", 0xa13b, 0x01, 0x7f4e).unwrap();
576 /// assert!(!uuri.is_empty());
577 /// assert!(UUri::default().is_empty());
578 /// ```
579 pub fn is_empty(&self) -> bool {
580 self.eq(&UUri::default())
581 }
582
583 /// Check if an `UUri` is remote, by comparing authority fields.
584 /// UUris with empty authority are considered to be local.
585 ///
586 /// # Returns
587 ///
588 /// 'true' if other_uri has a different authority than `Self`, `false` otherwise.
589 ///
590 /// # Examples
591 ///
592 /// ```rust
593 /// use std::str::FromStr;
594 /// use up_rust::UUri;
595 ///
596 /// let authority_a = UUri::from_str("up://authority.a/100A/1/0").unwrap();
597 /// let authority_b = UUri::from_str("up://authority.b/200B/2/20").unwrap();
598 /// assert!(authority_a.is_remote(&authority_b));
599 ///
600 /// let authority_local = UUri::from_str("up:///100A/1/0").unwrap();
601 /// assert!(!authority_local.is_remote(&authority_a));
602 ///
603 /// let authority_wildcard = UUri::from_str("up://*/100A/1/0").unwrap();
604 /// assert!(!authority_wildcard.is_remote(&authority_a));
605 /// assert!(!authority_a.is_remote(&authority_wildcard));
606 /// assert!(!authority_wildcard.is_remote(&authority_wildcard));
607 /// ````
608 pub fn is_remote(&self, other_uri: &UUri) -> bool {
609 self.is_remote_authority(&other_uri.authority_name)
610 }
611
612 /// Check if an authority is remote compared to the authority field of the UUri.
613 /// Empty authorities are considered to be local.
614 ///
615 /// # Returns
616 ///
617 /// 'true' if authority is a different than `Self.authority_name`, `false` otherwise.
618 ///
619 /// # Examples
620 ///
621 /// ```rust
622 /// use std::str::FromStr;
623 /// use up_rust::UUri;
624 ///
625 /// let authority_a = UUri::from_str("up://authority.a/100A/1/0").unwrap();
626 /// let authority_b = "authority.b".to_string();
627 /// assert!(authority_a.is_remote_authority(&authority_b));
628 ///
629 /// let authority_local = "".to_string();
630 /// assert!(!authority_a.is_remote_authority(&authority_local));
631 ///
632 /// let authority_wildcard = "*".to_string();
633 /// assert!(!authority_a.is_remote_authority(&authority_wildcard));
634 /// ```
635 pub fn is_remote_authority(&self, authority: &String) -> bool {
636 !authority.is_empty()
637 && !self.authority_name.is_empty()
638 && !self.has_wildcard_authority()
639 && authority != WILDCARD_AUTHORITY
640 && self.authority_name != *authority
641 }
642
643 /// Checks if this UUri has an empty authority name.
644 ///
645 /// # Examples
646 ///
647 /// ```rust
648 /// use up_rust::UUri;
649 ///
650 /// let uuri = UUri::try_from_parts("", 0x9b3a, 0x01, 0x145b).unwrap();
651 /// assert!(uuri.has_empty_authority());
652 /// ```
653 pub fn has_empty_authority(&self) -> bool {
654 self.authority_name.is_empty()
655 }
656
657 /// Checks if this UUri has a wildcard authority name.
658 ///
659 /// # Examples
660 ///
661 /// ```rust
662 /// use up_rust::UUri;
663 ///
664 /// let uuri = UUri::try_from_parts("*", 0x9b3a, 0x01, 0x145b).unwrap();
665 /// assert!(uuri.has_wildcard_authority());
666 /// ```
667 pub fn has_wildcard_authority(&self) -> bool {
668 self.authority_name == WILDCARD_AUTHORITY
669 }
670
671 /// Checks if this UUri has an entity identifier matching any instance.
672 ///
673 /// # Examples
674 ///
675 /// ```rust
676 /// use up_rust::UUri;
677 ///
678 /// let uuri = UUri::try_from_parts("vin", 0xFFFF_0123, 0x01, 0x145b).unwrap();
679 /// assert!(uuri.has_wildcard_entity_instance());
680 /// ```
681 pub fn has_wildcard_entity_instance(&self) -> bool {
682 self.ue_id & WILDCARD_ENTITY_INSTANCE == WILDCARD_ENTITY_INSTANCE
683 }
684
685 /// Checks if this UUri has an entity identifier matching any type.
686 ///
687 /// # Examples
688 ///
689 /// ```rust
690 /// use up_rust::UUri;
691 ///
692 /// let uuri = UUri::try_from_parts("vin", 0x00C0_FFFF, 0x01, 0x145b).unwrap();
693 /// assert!(uuri.has_wildcard_entity_type());
694 /// ```
695 pub fn has_wildcard_entity_type(&self) -> bool {
696 self.ue_id & WILDCARD_ENTITY_TYPE == WILDCARD_ENTITY_TYPE
697 }
698
699 /// Checks if this UUri has a wildcard major version.
700 ///
701 /// # Examples
702 ///
703 /// ```rust
704 /// use up_rust::UUri;
705 ///
706 /// let uuri = UUri::try_from_parts("vin", 0x9b3a, 0xFF, 0x145b).unwrap();
707 /// assert!(uuri.has_wildcard_version());
708 /// ```
709 pub fn has_wildcard_version(&self) -> bool {
710 self.ue_version_major == WILDCARD_ENTITY_VERSION
711 }
712
713 /// Checks if this UUri has a wildcard entity identifier.
714 ///
715 /// # Examples
716 ///
717 /// ```rust
718 /// use up_rust::UUri;
719 ///
720 /// let uuri = UUri::try_from_parts("vin", 0x9b3a, 0x01, 0xFFFF).unwrap();
721 /// assert!(uuri.has_wildcard_resource_id());
722 /// ```
723 pub fn has_wildcard_resource_id(&self) -> bool {
724 self.resource_id == WILDCARD_RESOURCE_ID
725 }
726
727 /// Verifies that this UUri does not contain any wildcards.
728 ///
729 /// # Errors
730 ///
731 /// Returns an error if any of this UUri's properties contain a wildcard value.
732 ///
733 /// # Examples
734 ///
735 /// ```rust
736 /// use up_rust::UUri;
737 ///
738 /// let uri = UUri {
739 /// authority_name: String::from("VIN.vehicles"),
740 /// ue_id: 0x0000_2310,
741 /// ue_version_major: 0x03,
742 /// resource_id: 0xa000,
743 /// ..Default::default()
744 /// };
745 /// assert!(uri.verify_no_wildcards().is_ok());
746 /// ```
747 pub fn verify_no_wildcards(&self) -> Result<(), UUriError> {
748 if self.has_wildcard_authority() {
749 Err(UUriError::validation_error(format!(
750 "Authority must not contain wildcard character [{WILDCARD_AUTHORITY}]"
751 )))
752 } else if self.has_wildcard_entity_instance() {
753 Err(UUriError::validation_error(format!(
754 "Entity instance ID must not be set to wildcard value [{WILDCARD_ENTITY_INSTANCE:#X}]")))
755 } else if self.has_wildcard_entity_type() {
756 Err(UUriError::validation_error(format!(
757 "Entity type ID must not be set to wildcard value [{WILDCARD_ENTITY_TYPE:#X}]"
758 )))
759 } else if self.has_wildcard_version() {
760 Err(UUriError::validation_error(format!(
761 "Entity version must not be set to wildcard value [{WILDCARD_ENTITY_VERSION:#X}]"
762 )))
763 } else if self.has_wildcard_resource_id() {
764 Err(UUriError::validation_error(format!(
765 "Resource ID must not be set to wildcard value [{WILDCARD_RESOURCE_ID:#X}]"
766 )))
767 } else {
768 Ok(())
769 }
770 }
771
772 /// Checks if this UUri refers to a service method.
773 ///
774 /// Returns `true` if 0 < resource ID < 0x8000.
775 ///
776 /// # Examples
777 ///
778 /// ```rust
779 /// use up_rust::UUri;
780 ///
781 /// let uri = UUri {
782 /// resource_id: 0x7FFF,
783 /// ..Default::default()
784 /// };
785 /// assert!(uri.is_rpc_method());
786 /// ```
787 pub fn is_rpc_method(&self) -> bool {
788 self.resource_id > RESOURCE_ID_RESPONSE && self.resource_id < RESOURCE_ID_MIN_EVENT
789 }
790
791 /// Verifies that this UUri refers to a service method.
792 ///
793 /// # Errors
794 ///
795 /// Returns an error if [`Self::is_rpc_method`] fails or
796 /// the UUri [contains any wildcards](Self::verify_no_wildcards).
797 ///
798 /// # Examples
799 ///
800 /// ```rust
801 /// use up_rust::UUri;
802 ///
803 /// let uri = UUri {
804 /// resource_id: 0x8000,
805 /// ..Default::default()
806 /// };
807 /// assert!(uri.verify_rpc_method().is_err());
808 ///
809 /// let uri = UUri {
810 /// resource_id: 0x0,
811 /// ..Default::default()
812 /// };
813 /// assert!(uri.verify_rpc_method().is_err());
814 /// ```
815 pub fn verify_rpc_method(&self) -> Result<(), UUriError> {
816 if !self.is_rpc_method() {
817 Err(UUriError::validation_error(format!(
818 "Resource ID must be a value from ]{RESOURCE_ID_RESPONSE:#X}, {RESOURCE_ID_MIN_EVENT:#X}[")))
819 } else {
820 self.verify_no_wildcards()
821 }
822 }
823
824 /// Checks if this UUri represents a destination for a Notification.
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_notification_destination());
838 /// ```
839 pub fn is_notification_destination(&self) -> bool {
840 self.resource_id == RESOURCE_ID_RESPONSE
841 }
842
843 /// Checks if this UUri represents an RPC response address.
844 ///
845 /// Returns `true` if resource ID is 0.
846 ///
847 /// # Examples
848 ///
849 /// ```rust
850 /// use up_rust::UUri;
851 ///
852 /// let uri = UUri {
853 /// resource_id: 0,
854 /// ..Default::default()
855 /// };
856 /// assert!(uri.is_rpc_response());
857 /// ```
858 pub fn is_rpc_response(&self) -> bool {
859 self.resource_id == RESOURCE_ID_RESPONSE
860 }
861
862 /// Verifies that this UUri represents an RPC response address.
863 ///
864 /// # Errors
865 ///
866 /// Returns an error if [`Self::is_rpc_response`] fails or
867 /// the UUri [contains any wildcards](Self::verify_no_wildcards).
868 ///
869 /// # Examples
870 ///
871 /// ```rust
872 /// use up_rust::UUri;
873 ///
874 /// let uri = UUri {
875 /// resource_id: 0x4001,
876 /// ..Default::default()
877 /// };
878 /// assert!(uri.verify_rpc_response().is_err());
879 /// ```
880 pub fn verify_rpc_response(&self) -> Result<(), UUriError> {
881 if !self.is_rpc_response() {
882 Err(UUriError::validation_error(format!(
883 "Resource ID must be {RESOURCE_ID_RESPONSE:#X}"
884 )))
885 } else {
886 self.verify_no_wildcards()
887 }
888 }
889
890 /// Checks if this UUri can be used as the source of an event.
891 ///
892 /// Returns `true` if resource ID >= 0x8000.
893 ///
894 /// # Examples
895 ///
896 /// ```rust
897 /// use up_rust::UUri;
898 ///
899 /// let uri = UUri {
900 /// resource_id: 0x8000,
901 /// ..Default::default()
902 /// };
903 /// assert!(uri.is_event());
904 /// ```
905 pub fn is_event(&self) -> bool {
906 self.resource_id >= RESOURCE_ID_MIN_EVENT
907 }
908
909 /// Verifies that this UUri can be used as the source of an event.
910 ///
911 /// # Errors
912 ///
913 /// Returns an error if [`Self::is_event`] fails or
914 /// the UUri [contains any wildcards](Self::verify_no_wildcards).
915 ///
916 /// # Examples
917 ///
918 /// ```rust
919 /// use up_rust::UUri;
920 ///
921 /// let uri = UUri {
922 /// resource_id: 0x7FFF,
923 /// ..Default::default()
924 /// };
925 /// assert!(uri.verify_event().is_err());
926 /// ```
927 pub fn verify_event(&self) -> Result<(), UUriError> {
928 if !self.is_event() {
929 Err(UUriError::validation_error(format!(
930 "Resource ID must be >= {RESOURCE_ID_MIN_EVENT:#X}"
931 )))
932 } else {
933 self.verify_no_wildcards()
934 }
935 }
936
937 fn matches_authority(&self, candidate: &UUri) -> bool {
938 self.has_wildcard_authority() || self.authority_name == candidate.authority_name
939 }
940
941 fn matches_entity_type(&self, candidate: &UUri) -> bool {
942 self.has_wildcard_entity_type() || self.uentity_type_id() == candidate.uentity_type_id()
943 }
944
945 fn matches_entity_instance(&self, candidate: &UUri) -> bool {
946 self.has_wildcard_entity_instance()
947 || self.uentity_instance_id() == candidate.uentity_instance_id()
948 }
949
950 fn matches_entity_version(&self, candidate: &UUri) -> bool {
951 self.has_wildcard_version()
952 || self.uentity_major_version() == candidate.uentity_major_version()
953 }
954
955 fn matches_entity(&self, candidate: &UUri) -> bool {
956 self.matches_entity_type(candidate)
957 && self.matches_entity_instance(candidate)
958 && self.matches_entity_version(candidate)
959 }
960
961 fn matches_resource(&self, candidate: &UUri) -> bool {
962 self.has_wildcard_resource_id() || self.resource_id == candidate.resource_id
963 }
964
965 /// Checks if a given candidate URI matches a pattern.
966 ///
967 /// # Returns
968 ///
969 /// `true` if the candiadate matches the pattern represented by this UUri.
970 ///
971 /// # Examples
972 ///
973 /// ```rust
974 /// use up_rust::UUri;
975 ///
976 /// let pattern = UUri::try_from("//vin/A14F/3/FFFF").unwrap();
977 /// let candidate = UUri::try_from("//vin/A14F/3/B1D4").unwrap();
978 /// assert!(pattern.matches(&candidate));
979 /// ```
980 // [impl->dsn~uri-pattern-matching~2]
981 pub fn matches(&self, candidate: &UUri) -> bool {
982 self.matches_authority(candidate)
983 && self.matches_entity(candidate)
984 && self.matches_resource(candidate)
985 }
986}
987
988#[cfg(test)]
989mod tests {
990 use super::*;
991 use test_case::test_case;
992
993 // [utest->dsn~uri-authority-name-length~1]
994 // [utest->dsn~uri-host-only~2]
995 #[test_case(UUri {
996 authority_name: "invalid:5671".into(),
997 ue_id: 0x0000_8000,
998 ue_version_major: 0x01,
999 resource_id: 0x0002,
1000 ..Default::default()
1001 };
1002 "for authority including port")]
1003 #[test_case(UUri {
1004 authority_name: ['a'; 129].iter().collect::<String>(),
1005 ue_id: 0x0000_8000,
1006 ue_version_major: 0x01,
1007 resource_id: 0x0002,
1008 ..Default::default()
1009 };
1010 "for authority exceeding max length")]
1011 // additional test cases covering all sorts of invalid authority are
1012 // included in [`test_from_string_fails`]
1013 #[test_case(UUri {
1014 authority_name: "valid".into(),
1015 ue_id: 0x0000_8000,
1016 ue_version_major: 0x0101,
1017 resource_id: 0x0002,
1018 ..Default::default()
1019 };
1020 "for invalid major version")]
1021 #[test_case(UUri {
1022 authority_name: "valid".into(),
1023 ue_id: 0x0000_8000,
1024 ue_version_major: 0x01,
1025 resource_id: 0x10002,
1026 ..Default::default()
1027 };
1028 "for invalid resource ID")]
1029 fn test_check_validity_fails(uuri: UUri) {
1030 assert!(uuri.check_validity().is_err());
1031 }
1032
1033 #[test_case("//*/A100/1/1"; "for any authority")]
1034 #[test_case("//vin/FFFF/1/1"; "for any entity type")]
1035 #[test_case("//vin/FFFF0ABC/1/1"; "for any entity instance")]
1036 #[test_case("//vin/A100/FF/1"; "for any version")]
1037 #[test_case("//vin/A100/1/FFFF"; "for any resource")]
1038 fn test_verify_no_wildcards_fails(uri: &str) {
1039 let uuri = UUri::try_from(uri).expect("should have been able to deserialize URI");
1040 assert!(uuri.verify_no_wildcards().is_err());
1041 }
1042
1043 // [utest->dsn~uri-authority-name-length~1]
1044 #[test]
1045 fn test_from_str_fails_for_authority_exceeding_max_length() {
1046 let host_name = "a".repeat(129);
1047 let uri = format!("//{}/A100/1/6501", host_name);
1048 assert!(UUri::from_str(&uri).is_err());
1049 }
1050
1051 // [utest->dsn~uri-path-mapping~2]
1052 #[test]
1053 fn test_from_str_accepts_lowercase_hex_encoding() {
1054 let result = UUri::try_from("up://vin/ffff0abc/a1/bcd1");
1055 assert!(result.is_ok_and(|uuri| {
1056 uuri.authority_name == "vin"
1057 && uuri.ue_id == 0xFFFF0ABC
1058 && uuri.ue_version_major == 0xA1
1059 && uuri.resource_id == 0xBCD1
1060 }));
1061 }
1062}