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}