1use std::fmt::{self, Debug, Display};
5use std::hash::{Hash, Hasher};
6use std::convert::Infallible;
7use std::borrow::Borrow;
8use std::num::NonZero;
9use std::str::FromStr;
10use std::ops::Deref;
11use std::sync::Arc;
12
13
14#[repr(transparent)]
22pub struct Name(str);
23
24impl Name {
25
26 #[inline]
29 pub fn new(name: &str) -> &Self {
30 unsafe { &*(name as *const str as *const Self) }
33 }
34
35 #[inline]
37 pub fn as_str(&self) -> &str {
38 &self.0
39 }
40
41 #[inline]
43 pub fn to_arc(&self) -> ArcName {
44 ArcName(Arc::from(&self.0))
45 }
46
47}
48
49impl PartialEq for Name {
50 #[inline]
51 fn eq(&self, other: &Self) -> bool {
52 self.0.eq_ignore_ascii_case(&other.0)
53 }
54}
55
56impl Eq for Name {}
57
58impl Hash for Name {
59 fn hash<H: Hasher>(&self, state: &mut H) {
60 for byte in self.0.bytes() {
64 state.write_u8(byte.to_ascii_lowercase());
65 }
66 state.write_u8(0xff);
67 }
68}
69
70impl Debug for Name {
71 #[inline]
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 Debug::fmt(&self.0, f)
74 }
75}
76
77impl Display for Name {
78 #[inline]
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 Display::fmt(&self.0, f)
81 }
82}
83
84pub struct ArcName(Arc<str>);
86
87impl From<Arc<str>> for ArcName {
88 fn from(value: Arc<str>) -> Self {
89 Self(value)
90 }
91}
92
93impl From<&str> for ArcName {
94 #[inline]
95 fn from(value: &str) -> Self {
96 Self(Arc::from(value))
97 }
98}
99
100impl FromStr for ArcName {
101
102 type Err = Infallible;
103
104 #[inline]
105 fn from_str(s: &str) -> Result<Self, Self::Err> {
106 Ok(Self::from(s))
107 }
108
109}
110
111impl Deref for ArcName {
112 type Target = Name;
113 #[inline]
114 fn deref(&self) -> &Self::Target {
115 Name::new(&self.0)
116 }
117}
118
119impl Borrow<Name> for ArcName {
120 #[inline]
121 fn borrow(&self) -> &Name {
122 self
123 }
124}
125
126impl PartialEq for ArcName {
127 fn eq(&self, other: &Self) -> bool {
128 **self == **other
129 }
130}
131
132impl Eq for ArcName {}
133
134impl Hash for ArcName {
135 fn hash<H: Hasher>(&self, state: &mut H) {
136 (**self).hash(state);
137 }
138}
139
140impl Debug for ArcName {
141 #[inline]
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 Debug::fmt(&**self, f)
144 }
145}
146
147impl Display for ArcName {
148 #[inline]
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 Display::fmt(&**self, f)
151 }
152}
153
154fn check_non_empty_name(name: &str) -> bool {
159 !name.is_empty() && name.chars().enumerate().all(is_tango_name_char)
160}
161
162fn check_non_empty_u8_name(name: &str) -> bool {
163 !name.is_empty() && name.len() <= 255 && name.chars().enumerate().all(is_tango_name_char)
164}
165
166pub fn check_tango_class_name(name: &str) -> bool {
168 check_non_empty_name(name)
169}
170
171pub fn check_tango_server_part_name(part: &str) -> bool {
173 check_non_empty_name(part)
174}
175
176pub fn check_tango_server_name(name: &str) -> bool {
178 let Some((exe, inst)) = name.split_once('/') else { return false };
179 check_tango_server_part_name(exe) &&
180 check_tango_server_part_name(inst)
181}
182
183fn check_tango_dev_part_name(part: &str) -> bool {
184 !part.is_empty() && part.len() <= 85 && part.chars().enumerate().all(is_tango_name_char)
185}
186
187pub fn check_tango_dev_name(name: &str) -> bool {
190 let Some((domain, rest)) = name.split_once('/') else { return false };
191 let Some((family, member)) = rest.split_once('/') else { return false };
192 check_tango_dev_part_name(domain) &&
193 check_tango_dev_part_name(family) &&
194 check_tango_dev_part_name(member)
195}
196
197pub fn check_tango_attr_name(name: &str) -> bool {
199 check_non_empty_u8_name(name)
200}
201
202pub fn check_tango_command_name(name: &str) -> bool {
204 check_non_empty_u8_name(name)
205}
206
207pub fn check_tango_property_name(name: &str) -> bool {
209 check_non_empty_u8_name(name)
210}
211
212pub fn check_tango_alias(name: &str) -> bool {
214 check_non_empty_u8_name(name)
215}
216
217#[derive(thiserror::Error, Debug)]
219pub enum TrlError {
220 #[error("Invalid protocol, expecting tango://")]
221 InvalidProtocol,
222 #[error("Invalid host, wrong characters or too long (> 255)")]
223 InvalidHost,
224 #[error("Invalid port, expecting non-null 16-bit integer")]
225 InvalidPort,
226 #[error("Invalid hash, expecting #dbase=yes/no")]
227 InvalidHash,
228 #[error("Invalid or missing path, device-name[/attribute] with device-name being an alias or domain/family/member")]
229 InvalidPath,
230 #[error("Invalid device name, wrong characters or too long (> 85 per domain/family/member)")]
231 InvalidDevName,
232 #[error("Invalid attribute name, wrong characters or too long (> 255)")]
233 InvalidAttrName,
234 #[error("Invalid device or attribute alias, wrong characters or too long (> 255)")]
235 InvalidAlias,
236 #[error("Invalid property name, wrong characters or too long (> 255)")]
237 InvalidPropName,
238}
239
240#[derive(Clone, Copy)]
251pub struct Trl<'a> {
252 raw: &'a str,
254 meta: TrlMeta,
256}
257
258#[derive(Clone)]
263pub struct ArcTrl {
264 raw: Arc<str>,
266 meta: TrlMeta,
268}
269
270#[derive(Debug, Clone, Copy)]
272struct TrlMeta {
273 host_len_and_port: Option<(NonZero<u8>, NonZero<u16>)>,
275 object_index: u16,
277 object: TrlObjectMeta,
279 property_len: Option<NonZero<u8>>,
281 protocol: bool,
283 db: bool,
285}
286
287#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
293enum TrlObjectMeta {
294 Alias(NonZero<u8>),
295 Dev(NonZero<u8>),
296 Attr(NonZero<u8>, NonZero<u8>),
297}
298
299impl TrlMeta {
300
301 fn parse(raw: &str) -> Result<Self, TrlError> {
302
303 let mut rem = raw;
304 let mut object_offset = 0u16;
305
306 let mut protocol = false;
308 if let Some((proto, new_rem)) = rem.split_once("://") {
309 if proto != "tango" {
310 return Err(TrlError::InvalidProtocol);
311 }
312 object_offset += "tango://".len() as u16;
313 protocol = true;
314 rem = new_rem;
315 }
316
317 let mut db = true;
319 if let Some((new_rem, hash)) = rem.rsplit_once('#') {
320 db = match hash {
321 "dbase=yes" => true,
322 "dbase=no" => false,
323 _ => return Err(TrlError::InvalidHash),
324 };
325 rem = new_rem;
326 }
327
328 let mut property_len = None;
331 if let Some((new_rem, property_raw)) = rem.rsplit_once("->") {
332 if !check_tango_property_name(property_raw) {
333 return Err(TrlError::InvalidPropName);
334 }
335 property_len = Some(NonZero::new(property_raw.len() as u8).unwrap());
336 rem = new_rem;
337 }
338
339 let mut parts = [""; 5];
341 let mut parts_count = 0;
342 for part in rem.split('/') {
343 if parts_count >= parts.len() {
345 return Err(TrlError::InvalidPath);
346 }
347 parts[parts_count] = part;
348 parts_count += 1;
349 }
350
351
352 let mut parts = &mut parts[..parts_count];
354 debug_assert!(!parts.is_empty(), "A split we should have at least one element");
355
356 let mut host_len_and_port = None;
358 if let Some((host_raw, port_raw)) = parts[0].split_once(':') {
359
360 parts = &mut parts[1..];
362 if parts.is_empty() {
363 return Err(TrlError::InvalidPath)
364 }
365
366 let host_len = u8::try_from(host_raw.len()).ok()
367 .and_then(NonZero::new)
368 .ok_or(TrlError::InvalidHost)?;
369
370 if !host_raw.chars().enumerate().all(is_hostname_char) {
371 return Err(TrlError::InvalidHost);
372 }
373
374 let port = port_raw.parse::<NonZero<u16>>()
375 .map_err(|_| TrlError::InvalidPort)?;
376
377 host_len_and_port = Some((host_len, port));
378
379 object_offset += host_raw.len() as u16 + 1 + port_raw.len() as u16 + 1;
381
382 }
383
384 let object;
386 if parts.len() == 1 {
387 if !check_tango_alias(parts[0]) {
388 return Err(TrlError::InvalidAlias);
389 }
390 object = TrlObjectMeta::Alias(NonZero::new(parts[0].len() as u8).unwrap());
391 } else if parts.len() == 3 || parts.len() == 4 {
392
393 let mut dev_len_raw = 0; for &dev_part in &parts[..3] {
395 if !check_tango_dev_part_name(dev_part) {
396 return Err(TrlError::InvalidDevName);
397 }
398 dev_len_raw += dev_part.len();
399 }
400
401 let dev_len = NonZero::new(dev_len_raw as u8).unwrap();
403
404 if let Some(&attr_raw) = parts.get(3) {
405 if !check_tango_attr_name(attr_raw) {
406 return Err(TrlError::InvalidAttrName);
407 }
408 object = TrlObjectMeta::Attr(dev_len, NonZero::new(attr_raw.len() as u8).unwrap());
409 } else {
410 object = TrlObjectMeta::Dev(dev_len);
411 }
412
413 } else {
414 return Err(TrlError::InvalidPath);
415 }
416
417 Ok(Self {
418 host_len_and_port,
419 object_index: object_offset,
420 object,
421 property_len,
422 protocol,
423 db,
424 })
425
426 }
427
428}
429
430impl<'a> Trl<'a> {
431
432 pub fn parse(raw: &'a str) -> Result<Self, TrlError> {
434 Ok(Self { raw, meta: TrlMeta::parse(raw)? })
435 }
436
437 #[inline]
439 pub fn as_str(self) -> &'a str {
440 self.raw
441 }
442
443 #[inline]
445 pub fn to_arc(self) -> ArcTrl {
446 ArcTrl { raw: Arc::from(self.raw), meta: self.meta }
447 }
448
449 #[inline]
450 fn host_and_port_index(self) -> usize {
451 if self.meta.protocol { "tango://".len() } else { 0 }
452 }
453
454 pub fn host_and_port(self) -> Option<(&'a str, u16)> {
456 let index = self.host_and_port_index();
457 self.meta.host_len_and_port.map(|(host_len, port)| {
458 (&self.raw[index..][..host_len.get() as usize], port.get())
459 })
460 }
461
462 #[inline]
463 fn object_index(self) -> usize {
464 self.meta.object_index as usize
465 }
466
467 pub fn object(self) -> TrlObject<'a> {
469 let index = self.object_index();
470 match self.meta.object {
471 TrlObjectMeta::Alias(alias_len) => {
472 TrlObject::Alias(&self.raw[index..][..alias_len.get() as usize])
473 }
474 TrlObjectMeta::Dev(dev_len) => {
475 TrlObject::Dev(&self.raw[index..][..dev_len.get() as usize + 2])
477 }
478 TrlObjectMeta::Attr(dev_len, attr_len) => {
479 let (dev_name, raw) = self.raw[index..].split_at(dev_len.get() as usize + 2);
481 let attr_name = &raw[1..][..attr_len.get() as usize];
483 TrlObject::Attr(dev_name, attr_name)
484 }
485 }
486 }
487
488 #[inline]
489 fn property_index(self) -> usize {
490 self.object_index() + match self.meta.object {
491 TrlObjectMeta::Alias(alias_len) =>
492 alias_len.get() as usize,
493 TrlObjectMeta::Dev(dev_len) =>
494 dev_len.get() as usize + 2,
495 TrlObjectMeta::Attr(dev_len, attr_len) =>
496 dev_len.get() as usize + attr_len.get() as usize + 3,
497 } + 2 }
499
500 pub fn property(self) -> Option<&'a str> {
502 let index = self.property_index();
503 self.meta.property_len.map(|len| {
504 &self.raw[index..][..len.get() as usize]
505 })
506 }
507
508 #[inline]
510 pub fn db(self) -> bool {
511 self.meta.db
512 }
513
514}
515
516impl ArcTrl {
517
518 #[inline]
520 pub fn as_trl(&self) -> Trl<'_> {
521 Trl { raw: &self.raw, meta: self.meta }
522 }
523
524 #[inline]
526 pub fn as_str(&self) -> &str {
527 &self.raw
528 }
529
530}
531
532impl FromStr for ArcTrl {
533
534 type Err = TrlError;
535
536 fn from_str(raw: &str) -> Result<Self, Self::Err> {
537 Ok(Self { raw: Arc::from(raw), meta: TrlMeta::parse(raw)? })
538 }
539
540}
541
542impl From<Trl<'_>> for ArcTrl {
543 #[inline]
544 fn from(trl: Trl<'_>) -> Self {
545 trl.to_arc()
546 }
547}
548
549impl<'a> From<&'a ArcTrl> for Trl<'a> {
550 #[inline]
551 fn from(trl: &'a ArcTrl) -> Self {
552 trl.as_trl()
553 }
554}
555
556impl fmt::Debug for Trl<'_> {
557 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
558 f.debug_tuple("Trl").field(&self.raw).finish()
559 }
560}
561
562impl fmt::Display for Trl<'_> {
563 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
564 f.write_str(self.raw)
565 }
566}
567
568impl fmt::Debug for ArcTrl {
569 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
570 f.debug_tuple("ArcTrl").field(&&*self.raw).finish()
571 }
572}
573
574impl fmt::Display for ArcTrl {
575 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
576 f.write_str(&self.raw)
577 }
578}
579
580pub enum TrlObject<'a> {
582 Alias(&'a str),
584 Dev(&'a str),
586 Attr(&'a str, &'a str),
588}
589
590fn is_hostname_char((i, c): (usize, char)) -> bool {
592 c.is_ascii_alphanumeric() || (i != 0 && (c == '-' || c == '.'))
593}
594
595fn is_tango_name_char((i, c): (usize, char)) -> bool {
601 c.is_ascii_alphanumeric() || (i != 0 && (c == '_' || c == '-'))
602}
603
604pub trait IntoTrl<'a> {
606 fn into_trl(self) -> Result<Trl<'a>, TrlError>;
608}
609
610impl<'a> IntoTrl<'a> for Trl<'a> {
611 fn into_trl(self) -> Result<Trl<'a>, TrlError> {
612 Ok(self)
613 }
614}
615
616impl<'a> IntoTrl<'a> for &'a ArcTrl {
617 fn into_trl(self) -> Result<Trl<'a>, TrlError> {
618 Ok(self.as_trl())
619 }
620}
621
622impl<'a> IntoTrl<'a> for &'a str {
623 fn into_trl(self) -> Result<Trl<'a>, TrlError> {
624 Trl::parse(self)
625 }
626}
627
628
629#[cfg(test)]
630mod tests {
631
632 use super::*;
633 use std::num::NonZero;
634 use std::collections::HashSet;
635
636 #[test]
637 fn name_ignores_case() {
638
639 assert_eq!(Name::new("Sys/TG_Test/1"), Name::new("sys/tg_test/1"));
640 assert_ne!(Name::new("sys/tg_test/1"), Name::new("sys/tg_test/2"));
641 assert_ne!(Name::new("attr"), Name::new("attr_2"));
642
643 let names = HashSet::from([ArcName::from("Double_Scalar")]);
644 assert!(names.contains(Name::new("double_scalar")));
645 assert!(!names.contains(Name::new("double_scalar_w")));
646
647 }
648
649 #[test]
650 fn name_keeps_case() {
651
652 let name = Name::new("Double_Scalar");
653 assert_eq!(name.as_str(), "Double_Scalar");
654 assert_eq!(name.to_string(), "Double_Scalar");
655
656 let arc_name = name.to_arc();
657 assert_eq!(arc_name.as_str(), "Double_Scalar");
658 assert_eq!(arc_name.to_string(), "Double_Scalar");
659 assert_eq!("double_scalar".parse::<ArcName>().unwrap(), arc_name);
660
661 }
662
663 #[test]
665 fn name_chars() {
666 assert!(check_tango_class_name("Motor_2-a"));
667 assert!(check_tango_class_name("2Motor"));
668 assert!(!check_tango_class_name(""));
669 assert!(!check_tango_class_name("_Motor"));
670 assert!(!check_tango_class_name("-Motor"));
671 assert!(!check_tango_class_name("Mo tor"));
672 assert!(!check_tango_class_name("Mo.tor"));
673 assert!(!check_tango_class_name("Mo/tor"));
674 assert!(!check_tango_class_name("Motör"));
675 }
676
677 #[test]
680 fn name_length() {
681 let (max, over) = ("a".repeat(255), "a".repeat(256));
682 assert!(check_tango_attr_name(&max) && !check_tango_attr_name(&over));
683 assert!(check_tango_command_name(&max) && !check_tango_command_name(&over));
684 assert!(check_tango_property_name(&max) && !check_tango_property_name(&over));
685 assert!(check_tango_alias(&max) && !check_tango_alias(&over));
686 assert!(check_tango_class_name(&over));
687 }
688
689 #[test]
691 fn dev_name() {
692 assert!(check_tango_dev_name("sys/tg_test/1"));
693 assert!(check_tango_dev_name(&format!("{0}/{0}/{0}", "a".repeat(85))));
694 assert!(!check_tango_dev_name(&format!("a/a/{}", "a".repeat(86))));
695 assert!(!check_tango_dev_name("sys/tg_test"));
696 assert!(!check_tango_dev_name("sys/tg_test/1/2"));
697 assert!(!check_tango_dev_name("sys//1"));
698 assert!(!check_tango_dev_name("sys/tg_test/_1"));
699 }
700
701 #[test]
703 fn server_name() {
704 assert!(check_tango_server_name("TangoTest/test"));
705 assert!(!check_tango_server_name("TangoTest"));
706 assert!(!check_tango_server_name("TangoTest/"));
707 assert!(!check_tango_server_name("TangoTest/te/st"));
708 assert!(!check_tango_server_name("TangoTest/te st"));
709 }
710
711 #[test]
712 fn parse_and_get() {
713
714 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
715 enum TrlObjectKind {
716 Alias,
717 Dev,
718 Attr,
719 }
720
721 for protocol in [false, true] {
722 for host_and_port in [false, true] {
723 for object in [TrlObjectKind::Alias, TrlObjectKind::Dev, TrlObjectKind::Attr] {
724 for property in [false, true] {
725 for db in [None, Some(false), Some(true)] {
726
727 let mut buf = String::new();
728
729 if protocol {
730 buf.push_str("tango://");
731 }
732
733 if host_and_port {
734 buf.push_str("example.com:10000/");
735 }
736
737 let object_index = buf.len() as u16;
738 match object {
739 TrlObjectKind::Alias => buf.push_str("alias"),
740 TrlObjectKind::Dev => buf.push_str("domain/family/member"),
741 TrlObjectKind::Attr => buf.push_str("domain/family/member/attribute"),
742 }
743
744 if property {
745 buf.push_str("->property");
746 }
747
748 match db {
749 Some(false) => buf.push_str("#dbase=no"),
750 Some(true) => buf.push_str("#dbase=yes"),
751 None => (),
752 }
753
754 let trl = Trl::parse(&buf).unwrap();
755
756 assert_eq!(trl.meta.protocol, protocol);
757 assert_eq!(trl.meta.host_len_and_port, host_and_port.then_some((NonZero::new(11u8).unwrap(), NonZero::new(10000u16).unwrap())));
758 assert_eq!(trl.meta.object_index, object_index);
759 assert_eq!(trl.meta.object, match object {
760 TrlObjectKind::Alias => TrlObjectMeta::Alias(NonZero::new(5u8).unwrap()),
761 TrlObjectKind::Dev => TrlObjectMeta::Dev(NonZero::new(18u8).unwrap()),
762 TrlObjectKind::Attr => TrlObjectMeta::Attr(NonZero::new(18u8).unwrap(), NonZero::new(9u8).unwrap()),
763 });
764 assert_eq!(trl.meta.property_len, property.then_some(NonZero::new(8u8).unwrap()));
765 assert_eq!(trl.meta.db, db.unwrap_or(true));
766
767 }
768 }
769 }
770 }
771 }
772
773 }
774
775}