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