1use std::{error::Error, fmt};
11
12use regex::Regex;
13
14use crate::types::{
15 node_id::{Identifier, NodeId},
16 node_ids::*,
17 qualified_name::QualifiedName,
18 service_types::{RelativePath, RelativePathElement},
19 string::UAString,
20};
21
22#[derive(Debug)]
23struct RelativePathError;
24
25impl fmt::Display for RelativePathError {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 write!(f, "RelativePathError")
28 }
29}
30
31impl Error for RelativePathError {}
32
33impl RelativePath {
34 const MAX_TOKEN_LEN: usize = 256;
36 const MAX_ELEMENTS: usize = 32;
38
39 pub fn from_str<CB>(path: &str, node_resolver: &CB) -> Result<RelativePath, ()>
43 where
44 CB: Fn(u16, &str) -> Option<NodeId>,
45 {
46 let mut elements: Vec<RelativePathElement> = Vec::new();
47
48 let mut escaped_char = false;
52 let mut token = String::with_capacity(path.len());
53 for c in path.chars() {
54 if escaped_char {
55 token.push(c);
56 escaped_char = false;
57 } else {
58 match c {
60 '&' => {
61 escaped_char = true;
63 }
64 '/' | '.' | '<' => {
65 if !token.is_empty() {
67 if elements.len() == Self::MAX_ELEMENTS {
68 break;
69 }
70 elements.push(RelativePathElement::from_str(&token, node_resolver)?);
71 token.clear();
72 }
73 }
74 _ => {}
75 }
76 token.push(c);
77 }
78 if token.len() > Self::MAX_TOKEN_LEN {
79 error!("Path segment seems unusually long and has been rejected");
80 return Err(());
81 }
82 }
83
84 if !token.is_empty() {
85 if elements.len() == Self::MAX_ELEMENTS {
86 error!("Number of elements in relative path is too long, rejecting it");
87 return Err(());
88 }
89 elements.push(RelativePathElement::from_str(&token, node_resolver)?);
90 }
91
92 Ok(RelativePath {
93 elements: Some(elements),
94 })
95 }
96}
97
98impl<'a> From<&'a RelativePathElement> for String {
99 fn from(element: &'a RelativePathElement) -> String {
100 let mut result = element
101 .relative_path_reference_type(&RelativePathElement::default_browse_name_resolver);
102 if !element.target_name.name.is_null() {
103 let always_use_namespace = true;
104 let target_browse_name = escape_browse_name(element.target_name.name.as_ref());
105 if always_use_namespace || element.target_name.namespace_index > 0 {
106 result.push_str(&format!(
107 "{}:{}",
108 element.target_name.namespace_index, target_browse_name
109 ));
110 } else {
111 result.push_str(&target_browse_name);
112 }
113 }
114 result
115 }
116}
117
118impl RelativePathElement {
119 pub fn default_node_resolver(namespace: u16, browse_name: &str) -> Option<NodeId> {
126 let node_id = if namespace == 0 {
127 match browse_name {
128 "References" => ReferenceTypeId::References.into(),
129 "NonHierarchicalReferences" => ReferenceTypeId::NonHierarchicalReferences.into(),
130 "HierarchicalReferences" => ReferenceTypeId::HierarchicalReferences.into(),
131 "HasChild" => ReferenceTypeId::HasChild.into(),
132 "Organizes" => ReferenceTypeId::Organizes.into(),
133 "HasEventSource" => ReferenceTypeId::HasEventSource.into(),
134 "HasModellingRule" => ReferenceTypeId::HasModellingRule.into(),
135 "HasEncoding" => ReferenceTypeId::HasEncoding.into(),
136 "HasDescription" => ReferenceTypeId::HasDescription.into(),
137 "HasTypeDefinition" => ReferenceTypeId::HasTypeDefinition.into(),
138 "GeneratesEvent" => ReferenceTypeId::GeneratesEvent.into(),
139 "Aggregates" => ReferenceTypeId::Aggregates.into(),
140 "HasSubtype" => ReferenceTypeId::HasSubtype.into(),
141 "HasProperty" => ReferenceTypeId::HasProperty.into(),
142 "HasComponent" => ReferenceTypeId::HasComponent.into(),
143 "HasNotifier" => ReferenceTypeId::HasNotifier.into(),
144 "HasOrderedComponent" => ReferenceTypeId::HasOrderedComponent.into(),
145 "FromState" => ReferenceTypeId::FromState.into(),
146 "ToState" => ReferenceTypeId::ToState.into(),
147 "HasCause" => ReferenceTypeId::HasCause.into(),
148 "HasEffect" => ReferenceTypeId::HasEffect.into(),
149 "HasHistoricalConfiguration" => ReferenceTypeId::HasHistoricalConfiguration.into(),
150 "HasSubStateMachine" => ReferenceTypeId::HasSubStateMachine.into(),
151 "AlwaysGeneratesEvent" => ReferenceTypeId::AlwaysGeneratesEvent.into(),
152 "HasTrueSubState" => ReferenceTypeId::HasTrueSubState.into(),
153 "HasFalseSubState" => ReferenceTypeId::HasFalseSubState.into(),
154 "HasCondition" => ReferenceTypeId::HasCondition.into(),
155 _ => NodeId::new(0, UAString::from(browse_name)),
156 }
157 } else {
158 NodeId::new(namespace, UAString::from(browse_name))
159 };
160 Some(node_id)
161 }
162
163 fn id_from_reference_type(id: u32) -> Option<String> {
164 Some(
166 match id {
167 id if id == ReferenceTypeId::References as u32 => "References",
168 id if id == ReferenceTypeId::NonHierarchicalReferences as u32 => {
169 "NonHierarchicalReferences"
170 }
171 id if id == ReferenceTypeId::HierarchicalReferences as u32 => {
172 "HierarchicalReferences"
173 }
174 id if id == ReferenceTypeId::HasChild as u32 => "HasChild",
175 id if id == ReferenceTypeId::Organizes as u32 => "Organizes",
176 id if id == ReferenceTypeId::HasEventSource as u32 => "HasEventSource",
177 id if id == ReferenceTypeId::HasModellingRule as u32 => "HasModellingRule",
178 id if id == ReferenceTypeId::HasEncoding as u32 => "HasEncoding",
179 id if id == ReferenceTypeId::HasDescription as u32 => "HasDescription",
180 id if id == ReferenceTypeId::HasTypeDefinition as u32 => "HasTypeDefinition",
181 id if id == ReferenceTypeId::GeneratesEvent as u32 => "GeneratesEvent",
182 id if id == ReferenceTypeId::Aggregates as u32 => "Aggregates",
183 id if id == ReferenceTypeId::HasSubtype as u32 => "HasSubtype",
184 id if id == ReferenceTypeId::HasProperty as u32 => "HasProperty",
185 id if id == ReferenceTypeId::HasComponent as u32 => "HasComponent",
186 id if id == ReferenceTypeId::HasNotifier as u32 => "HasNotifier",
187 id if id == ReferenceTypeId::HasOrderedComponent as u32 => "HasOrderedComponent",
188 id if id == ReferenceTypeId::FromState as u32 => "FromState",
189 id if id == ReferenceTypeId::ToState as u32 => "ToState",
190 id if id == ReferenceTypeId::HasCause as u32 => "HasCause",
191 id if id == ReferenceTypeId::HasEffect as u32 => "HasEffect",
192 id if id == ReferenceTypeId::HasHistoricalConfiguration as u32 => {
193 "HasHistoricalConfiguration"
194 }
195 id if id == ReferenceTypeId::HasSubStateMachine as u32 => "HasSubStateMachine",
196 id if id == ReferenceTypeId::AlwaysGeneratesEvent as u32 => "AlwaysGeneratesEvent",
197 id if id == ReferenceTypeId::HasTrueSubState as u32 => "HasTrueSubState",
198 id if id == ReferenceTypeId::HasFalseSubState as u32 => "HasFalseSubState",
199 id if id == ReferenceTypeId::HasCondition as u32 => "HasCondition",
200 _ => return None,
201 }
202 .to_string(),
203 )
204 }
205
206 pub fn default_browse_name_resolver(node_id: &NodeId) -> Option<String> {
207 match &node_id.identifier {
208 Identifier::String(browse_name) => Some(browse_name.as_ref().to_string()),
209 Identifier::Numeric(id) => {
210 if node_id.namespace == 0 {
211 Self::id_from_reference_type(*id)
212 } else {
213 None
214 }
215 }
216 _ => None,
217 }
218 }
219
220 pub fn from_str<CB>(path: &str, node_resolver: &CB) -> Result<RelativePathElement, ()>
241 where
242 CB: Fn(u16, &str) -> Option<NodeId>,
243 {
244 lazy_static! {
245 static ref RE: Regex = Regex::new(r"(?P<reftype>/|\.|(<(?P<flags>#|!|#!)?((?P<nsidx>[0-9]+):)?(?P<name>[^#!].*)>))(?P<target>.*)").unwrap();
246 }
247
248 if let Some(captures) = RE.captures(path) {
251 let target_name = target_name(captures.name("target").unwrap().as_str())?;
252
253 let reference_type = captures.name("reftype").unwrap();
254 let (reference_type_id, include_subtypes, is_inverse) = match reference_type.as_str() {
255 "/" => (ReferenceTypeId::HierarchicalReferences.into(), true, false),
256 "." => (ReferenceTypeId::Aggregates.into(), true, false),
257 _ => {
258 let (include_subtypes, is_inverse) = if let Some(flags) = captures.name("flags")
259 {
260 match flags.as_str() {
261 "#" => (false, false),
262 "!" => (true, true),
263 "#!" => (false, true),
264 _ => panic!("Error in regular expression for flags"),
265 }
266 } else {
267 (true, false)
268 };
269
270 let browse_name = captures.name("name").unwrap().as_str();
271
272 let reference_type_id = if let Some(namespace) = captures.name("nsidx") {
274 let namespace = namespace.as_str();
275 if namespace == "0" || namespace.is_empty() {
276 node_resolver(0, browse_name)
277 } else if let Ok(namespace) = namespace.parse::<u16>() {
278 node_resolver(namespace, browse_name)
279 } else {
280 error!("Namespace {} is out of range", namespace);
281 return Err(());
282 }
283 } else {
284 node_resolver(0, browse_name)
285 };
286 if reference_type_id.is_none() {
287 error!(
288 "Supplied node resolver was unable to resolve a reference type from {}",
289 path
290 );
291 return Err(());
292 }
293 (reference_type_id.unwrap(), include_subtypes, is_inverse)
294 }
295 };
296 Ok(RelativePathElement {
297 reference_type_id,
298 is_inverse,
299 include_subtypes,
300 target_name,
301 })
302 } else {
303 error!("Path {} does not match a relative path", path);
304 Err(())
305 }
306 }
307
308 pub(crate) fn relative_path_reference_type<CB>(&self, browse_name_resolver: &CB) -> String
312 where
313 CB: Fn(&NodeId) -> Option<String>,
314 {
315 let browse_name = browse_name_resolver(&self.reference_type_id).unwrap();
316 let mut result = String::with_capacity(1024);
317 if self.include_subtypes && !self.is_inverse {
319 if self.reference_type_id == ReferenceTypeId::HierarchicalReferences.into() {
320 result.push('/');
321 } else if self.reference_type_id == ReferenceTypeId::Aggregates.into() {
322 result.push('.');
323 }
324 };
325 if result.is_empty() {
327 result.push('<');
328 if !self.include_subtypes {
329 result.push('#');
330 }
331 if self.is_inverse {
332 result.push('!');
333 }
334
335 let browse_name = escape_browse_name(browse_name.as_ref());
336 if self.reference_type_id.namespace != 0 {
337 result.push_str(&format!(
338 "{}:{}",
339 self.reference_type_id.namespace, browse_name
340 ));
341 } else {
342 result.push_str(&browse_name);
343 }
344 result.push('>');
345 }
346
347 result
348 }
349}
350
351impl<'a> From<&'a RelativePath> for String {
352 fn from(path: &'a RelativePath) -> String {
353 if let Some(ref elements) = path.elements {
354 let mut result = String::with_capacity(1024);
355 for e in elements.iter() {
356 result.push_str(String::from(e).as_ref());
357 }
358 result
359 } else {
360 String::new()
361 }
362 }
363}
364
365const BROWSE_NAME_RESERVED_CHARS: &str = "&/.<>:#!";
367
368fn escape_browse_name(name: &str) -> String {
370 let mut result = String::from(name);
371 BROWSE_NAME_RESERVED_CHARS.chars().for_each(|c| {
372 result = result.replace(c, &format!("&{}", c));
373 });
374 result
375}
376
377fn unescape_browse_name(name: &str) -> String {
379 let mut result = String::from(name);
380 BROWSE_NAME_RESERVED_CHARS.chars().for_each(|c| {
381 result = result.replace(&format!("&{}", c), &c.to_string());
382 });
383 result
384}
385
386fn target_name(target_name: &str) -> Result<QualifiedName, ()> {
395 lazy_static! {
396 static ref RE: Regex = Regex::new(r"((?P<nsidx>[0-9+]):)?(?P<name>.*)").unwrap();
397 }
398 if let Some(captures) = RE.captures(target_name) {
399 let namespace = if let Some(namespace) = captures.name("nsidx") {
400 if let Ok(namespace) = namespace.as_str().parse::<u16>() {
401 namespace
402 } else {
403 error!(
404 "Namespace {} for target name is out of range",
405 namespace.as_str()
406 );
407 return Err(());
408 }
409 } else {
410 0
411 };
412 let name = if let Some(name) = captures.name("name") {
413 let name = name.as_str();
414 if name.is_empty() {
415 UAString::null()
416 } else {
417 UAString::from(unescape_browse_name(name))
418 }
419 } else {
420 UAString::null()
421 };
422 Ok(QualifiedName::new(namespace, name))
423 } else {
424 Ok(QualifiedName::null())
425 }
426}
427
428#[test]
430fn test_escape_browse_name() {
431 [
432 ("", ""),
433 ("Hello World", "Hello World"),
434 ("Hello &World", "Hello &&World"),
435 ("Hello &&World", "Hello &&&&World"),
436 ("Block.Output", "Block&.Output"),
437 ("/Name_1", "&/Name_1"),
438 (".Name_2", "&.Name_2"),
439 (":Name_3", "&:Name_3"),
440 ("&Name_4", "&&Name_4"),
441 ]
442 .iter()
443 .for_each(|n| {
444 let original = n.0.to_string();
445 let escaped = n.1.to_string();
446 assert_eq!(escaped, escape_browse_name(&original));
447 assert_eq!(unescape_browse_name(&escaped), original);
448 });
449}
450
451#[test]
454fn test_relative_path_element() {
455 use crate::types::qualified_name::QualifiedName;
456
457 [
458 (
459 RelativePathElement {
460 reference_type_id: ReferenceTypeId::HierarchicalReferences.into(),
461 is_inverse: false,
462 include_subtypes: true,
463 target_name: QualifiedName::new(0, "foo1"),
464 },
465 "/0:foo1",
466 ),
467 (
468 RelativePathElement {
469 reference_type_id: ReferenceTypeId::HierarchicalReferences.into(),
470 is_inverse: false,
471 include_subtypes: true,
472 target_name: QualifiedName::new(0, ".foo2"),
473 },
474 "/0:&.foo2",
475 ),
476 (
477 RelativePathElement {
478 reference_type_id: ReferenceTypeId::HierarchicalReferences.into(),
479 is_inverse: true,
480 include_subtypes: true,
481 target_name: QualifiedName::new(2, "foo3"),
482 },
483 "<!HierarchicalReferences>2:foo3",
484 ),
485 (
486 RelativePathElement {
487 reference_type_id: ReferenceTypeId::HierarchicalReferences.into(),
488 is_inverse: true,
489 include_subtypes: false,
490 target_name: QualifiedName::new(0, "foo4"),
491 },
492 "<#!HierarchicalReferences>0:foo4",
493 ),
494 (
495 RelativePathElement {
496 reference_type_id: ReferenceTypeId::Aggregates.into(),
497 is_inverse: false,
498 include_subtypes: true,
499 target_name: QualifiedName::new(0, "foo5"),
500 },
501 ".0:foo5",
502 ),
503 (
504 RelativePathElement {
505 reference_type_id: ReferenceTypeId::HasHistoricalConfiguration.into(),
506 is_inverse: false,
507 include_subtypes: true,
508 target_name: QualifiedName::new(0, "foo6"),
509 },
510 "<HasHistoricalConfiguration>0:foo6",
511 ),
512 ]
513 .iter()
514 .for_each(|n| {
515 let element = &n.0;
516 let expected = n.1.to_string();
517
518 let actual = String::from(element);
520 assert_eq!(expected, actual);
521
522 let actual =
524 RelativePathElement::from_str(&actual, &RelativePathElement::default_node_resolver)
525 .unwrap();
526 assert_eq!(*element, actual);
527 });
528}
529
530#[test]
533fn test_relative_path() {
534 use crate::types::qualified_name::QualifiedName;
535
536 let tests = vec![
538 (
539 vec![RelativePathElement {
540 reference_type_id: ReferenceTypeId::HierarchicalReferences.into(),
541 is_inverse: false,
542 include_subtypes: true,
543 target_name: QualifiedName::new(2, "Block.Output"),
544 }],
545 "/2:Block&.Output",
546 ),
547 (
548 vec![
549 RelativePathElement {
550 reference_type_id: ReferenceTypeId::HierarchicalReferences.into(),
551 is_inverse: false,
552 include_subtypes: true,
553 target_name: QualifiedName::new(3, "Truck"),
554 },
555 RelativePathElement {
556 reference_type_id: ReferenceTypeId::Aggregates.into(),
557 is_inverse: false,
558 include_subtypes: true,
559 target_name: QualifiedName::new(0, "NodeVersion"),
560 },
561 ],
562 "/3:Truck.0:NodeVersion",
563 ),
564 (
565 vec![
566 RelativePathElement {
567 reference_type_id: NodeId::new(1, "ConnectedTo"),
568 is_inverse: false,
569 include_subtypes: true,
570 target_name: QualifiedName::new(1, "Boiler"),
571 },
572 RelativePathElement {
573 reference_type_id: ReferenceTypeId::HierarchicalReferences.into(),
574 is_inverse: false,
575 include_subtypes: true,
576 target_name: QualifiedName::new(1, "HeatSensor"),
577 },
578 ],
579 "<1:ConnectedTo>1:Boiler/1:HeatSensor",
580 ),
581 (
582 vec![
583 RelativePathElement {
584 reference_type_id: NodeId::new(1, "ConnectedTo"),
585 is_inverse: false,
586 include_subtypes: true,
587 target_name: QualifiedName::new(1, "Boiler"),
588 },
589 RelativePathElement {
590 reference_type_id: ReferenceTypeId::HierarchicalReferences.into(),
591 is_inverse: false,
592 include_subtypes: true,
593 target_name: QualifiedName::null(),
594 },
595 ],
596 "<1:ConnectedTo>1:Boiler/",
597 ),
598 (
599 vec![RelativePathElement {
600 reference_type_id: ReferenceTypeId::HasChild.into(),
601 is_inverse: false,
602 include_subtypes: true,
603 target_name: QualifiedName::new(2, "Wheel"),
604 }],
605 "<HasChild>2:Wheel",
606 ),
607 (
608 vec![RelativePathElement {
609 reference_type_id: ReferenceTypeId::HasChild.into(),
610 is_inverse: true,
611 include_subtypes: true,
612 target_name: QualifiedName::new(0, "Truck"),
613 }],
614 "<!HasChild>0:Truck",
615 ),
616 (
617 vec![RelativePathElement {
618 reference_type_id: ReferenceTypeId::HasChild.into(),
619 is_inverse: false,
620 include_subtypes: true,
621 target_name: QualifiedName::null(),
622 }],
623 "<HasChild>",
624 ),
625 ];
626
627 tests.into_iter().for_each(|n| {
628 let relative_path = RelativePath {
629 elements: Some(n.0),
630 };
631 let expected = n.1.to_string();
632
633 let actual = String::from(&relative_path);
635 assert_eq!(expected, actual);
636
637 let actual =
639 RelativePath::from_str(&actual, &RelativePathElement::default_node_resolver).unwrap();
640 assert_eq!(relative_path, actual);
641 });
642}