use std::fmt::{self, Debug, Display};
use std::hash::{Hash, Hasher};
use std::convert::Infallible;
use std::borrow::Borrow;
use std::num::NonZero;
use std::str::FromStr;
use std::ops::Deref;
use std::sync::Arc;
#[repr(transparent)]
pub struct Name(str);
impl Name {
#[inline]
pub fn new(name: &str) -> &Self {
unsafe { &*(name as *const str as *const Self) }
}
#[inline]
pub fn as_str(&self) -> &str {
&self.0
}
#[inline]
pub fn to_arc(&self) -> ArcName {
ArcName(Arc::from(&self.0))
}
}
impl PartialEq for Name {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.0.eq_ignore_ascii_case(&other.0)
}
}
impl Eq for Name {}
impl Hash for Name {
fn hash<H: Hasher>(&self, state: &mut H) {
for byte in self.0.bytes() {
state.write_u8(byte.to_ascii_lowercase());
}
state.write_u8(0xff);
}
}
impl Debug for Name {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Debug::fmt(&self.0, f)
}
}
impl Display for Name {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
pub struct ArcName(Arc<str>);
impl From<Arc<str>> for ArcName {
fn from(value: Arc<str>) -> Self {
Self(value)
}
}
impl From<&str> for ArcName {
#[inline]
fn from(value: &str) -> Self {
Self(Arc::from(value))
}
}
impl FromStr for ArcName {
type Err = Infallible;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::from(s))
}
}
impl Deref for ArcName {
type Target = Name;
#[inline]
fn deref(&self) -> &Self::Target {
Name::new(&self.0)
}
}
impl Borrow<Name> for ArcName {
#[inline]
fn borrow(&self) -> &Name {
self
}
}
impl PartialEq for ArcName {
fn eq(&self, other: &Self) -> bool {
**self == **other
}
}
impl Eq for ArcName {}
impl Hash for ArcName {
fn hash<H: Hasher>(&self, state: &mut H) {
(**self).hash(state);
}
}
impl Debug for ArcName {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Debug::fmt(&**self, f)
}
}
impl Display for ArcName {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&**self, f)
}
}
fn check_non_empty_name(name: &str) -> bool {
!name.is_empty() && name.chars().enumerate().all(is_tango_name_char)
}
fn check_non_empty_u8_name(name: &str) -> bool {
!name.is_empty() && name.len() <= 255 && name.chars().enumerate().all(is_tango_name_char)
}
pub fn check_tango_class_name(name: &str) -> bool {
check_non_empty_name(name)
}
pub fn check_tango_server_part_name(part: &str) -> bool {
check_non_empty_name(part)
}
pub fn check_tango_server_name(name: &str) -> bool {
let Some((exe, inst)) = name.split_once('/') else { return false };
check_tango_server_part_name(exe) &&
check_tango_server_part_name(inst)
}
fn check_tango_dev_part_name(part: &str) -> bool {
!part.is_empty() && part.len() <= 85 && part.chars().enumerate().all(is_tango_name_char)
}
pub fn check_tango_dev_name(name: &str) -> bool {
let Some((domain, rest)) = name.split_once('/') else { return false };
let Some((family, member)) = rest.split_once('/') else { return false };
check_tango_dev_part_name(domain) &&
check_tango_dev_part_name(family) &&
check_tango_dev_part_name(member)
}
pub fn check_tango_attr_name(name: &str) -> bool {
check_non_empty_u8_name(name)
}
pub fn check_tango_command_name(name: &str) -> bool {
check_non_empty_u8_name(name)
}
pub fn check_tango_property_name(name: &str) -> bool {
check_non_empty_u8_name(name)
}
pub fn check_tango_alias(name: &str) -> bool {
check_non_empty_u8_name(name)
}
#[derive(thiserror::Error, Debug)]
pub enum TrlError {
#[error("Invalid protocol, expecting tango://")]
InvalidProtocol,
#[error("Invalid host, wrong characters or too long (> 255)")]
InvalidHost,
#[error("Invalid port, expecting non-null 16-bit integer")]
InvalidPort,
#[error("Invalid hash, expecting #dbase=yes/no")]
InvalidHash,
#[error("Invalid or missing path, device-name[/attribute] with device-name being an alias or domain/family/member")]
InvalidPath,
#[error("Invalid device name, wrong characters or too long (> 85 per domain/family/member)")]
InvalidDevName,
#[error("Invalid attribute name, wrong characters or too long (> 255)")]
InvalidAttrName,
#[error("Invalid device or attribute alias, wrong characters or too long (> 255)")]
InvalidAlias,
#[error("Invalid property name, wrong characters or too long (> 255)")]
InvalidPropName,
}
#[derive(Clone, Copy)]
pub struct Trl<'a> {
raw: &'a str,
meta: TrlMeta,
}
#[derive(Clone)]
pub struct ArcTrl {
raw: Arc<str>,
meta: TrlMeta,
}
#[derive(Debug, Clone, Copy)]
struct TrlMeta {
host_len_and_port: Option<(NonZero<u8>, NonZero<u16>)>,
object_index: u16,
object: TrlObjectMeta,
property_len: Option<NonZero<u8>>,
protocol: bool,
db: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum TrlObjectMeta {
Alias(NonZero<u8>),
Dev(NonZero<u8>),
Attr(NonZero<u8>, NonZero<u8>),
}
impl TrlMeta {
fn parse(raw: &str) -> Result<Self, TrlError> {
let mut rem = raw;
let mut object_offset = 0u16;
let mut protocol = false;
if let Some((proto, new_rem)) = rem.split_once("://") {
if proto != "tango" {
return Err(TrlError::InvalidProtocol);
}
object_offset += "tango://".len() as u16;
protocol = true;
rem = new_rem;
}
let mut db = true;
if let Some((new_rem, hash)) = rem.rsplit_once('#') {
db = match hash {
"dbase=yes" => true,
"dbase=no" => false,
_ => return Err(TrlError::InvalidHash),
};
rem = new_rem;
}
let mut property_len = None;
if let Some((new_rem, property_raw)) = rem.rsplit_once("->") {
if !check_tango_property_name(property_raw) {
return Err(TrlError::InvalidPropName);
}
property_len = Some(NonZero::new(property_raw.len() as u8).unwrap());
rem = new_rem;
}
let mut parts = [""; 5];
let mut parts_count = 0;
for part in rem.split('/') {
if parts_count >= parts.len() {
return Err(TrlError::InvalidPath);
}
parts[parts_count] = part;
parts_count += 1;
}
let mut parts = &mut parts[..parts_count];
debug_assert!(!parts.is_empty(), "A split we should have at least one element");
let mut host_len_and_port = None;
if let Some((host_raw, port_raw)) = parts[0].split_once(':') {
parts = &mut parts[1..];
if parts.is_empty() {
return Err(TrlError::InvalidPath)
}
let host_len = u8::try_from(host_raw.len()).ok()
.and_then(NonZero::new)
.ok_or(TrlError::InvalidHost)?;
if !host_raw.chars().enumerate().all(is_hostname_char) {
return Err(TrlError::InvalidHost);
}
let port = port_raw.parse::<NonZero<u16>>()
.map_err(|_| TrlError::InvalidPort)?;
host_len_and_port = Some((host_len, port));
object_offset += host_raw.len() as u16 + 1 + port_raw.len() as u16 + 1;
}
let object;
if parts.len() == 1 {
if !check_tango_alias(parts[0]) {
return Err(TrlError::InvalidAlias);
}
object = TrlObjectMeta::Alias(NonZero::new(parts[0].len() as u8).unwrap());
} else if parts.len() == 3 || parts.len() == 4 {
let mut dev_len_raw = 0; for &dev_part in &parts[..3] {
if !check_tango_dev_part_name(dev_part) {
return Err(TrlError::InvalidDevName);
}
dev_len_raw += dev_part.len();
}
let dev_len = NonZero::new(dev_len_raw as u8).unwrap();
if let Some(&attr_raw) = parts.get(3) {
if !check_tango_attr_name(attr_raw) {
return Err(TrlError::InvalidAttrName);
}
object = TrlObjectMeta::Attr(dev_len, NonZero::new(attr_raw.len() as u8).unwrap());
} else {
object = TrlObjectMeta::Dev(dev_len);
}
} else {
return Err(TrlError::InvalidPath);
}
Ok(Self {
host_len_and_port,
object_index: object_offset,
object,
property_len,
protocol,
db,
})
}
}
impl<'a> Trl<'a> {
pub fn parse(raw: &'a str) -> Result<Self, TrlError> {
Ok(Self { raw, meta: TrlMeta::parse(raw)? })
}
#[inline]
pub fn as_str(self) -> &'a str {
self.raw
}
#[inline]
pub fn to_arc(self) -> ArcTrl {
ArcTrl { raw: Arc::from(self.raw), meta: self.meta }
}
#[inline]
fn host_and_port_index(self) -> usize {
if self.meta.protocol { "tango://".len() } else { 0 }
}
pub fn host_and_port(self) -> Option<(&'a str, u16)> {
let index = self.host_and_port_index();
self.meta.host_len_and_port.map(|(host_len, port)| {
(&self.raw[index..][..host_len.get() as usize], port.get())
})
}
#[inline]
fn object_index(self) -> usize {
self.meta.object_index as usize
}
pub fn object(self) -> TrlObject<'a> {
let index = self.object_index();
match self.meta.object {
TrlObjectMeta::Alias(alias_len) => {
TrlObject::Alias(&self.raw[index..][..alias_len.get() as usize])
}
TrlObjectMeta::Dev(dev_len) => {
TrlObject::Dev(&self.raw[index..][..dev_len.get() as usize + 2])
}
TrlObjectMeta::Attr(dev_len, attr_len) => {
let (dev_name, raw) = self.raw[index..].split_at(dev_len.get() as usize + 2);
let attr_name = &raw[1..][..attr_len.get() as usize];
TrlObject::Attr(dev_name, attr_name)
}
}
}
#[inline]
fn property_index(self) -> usize {
self.object_index() + match self.meta.object {
TrlObjectMeta::Alias(alias_len) =>
alias_len.get() as usize,
TrlObjectMeta::Dev(dev_len) =>
dev_len.get() as usize + 2,
TrlObjectMeta::Attr(dev_len, attr_len) =>
dev_len.get() as usize + attr_len.get() as usize + 3,
} + 2 }
pub fn property(self) -> Option<&'a str> {
let index = self.property_index();
self.meta.property_len.map(|len| {
&self.raw[index..][..len.get() as usize]
})
}
#[inline]
pub fn db(self) -> bool {
self.meta.db
}
}
impl ArcTrl {
#[inline]
pub fn as_trl(&self) -> Trl<'_> {
Trl { raw: &self.raw, meta: self.meta }
}
#[inline]
pub fn as_str(&self) -> &str {
&self.raw
}
}
impl FromStr for ArcTrl {
type Err = TrlError;
fn from_str(raw: &str) -> Result<Self, Self::Err> {
Ok(Self { raw: Arc::from(raw), meta: TrlMeta::parse(raw)? })
}
}
impl From<Trl<'_>> for ArcTrl {
#[inline]
fn from(trl: Trl<'_>) -> Self {
trl.to_arc()
}
}
impl<'a> From<&'a ArcTrl> for Trl<'a> {
#[inline]
fn from(trl: &'a ArcTrl) -> Self {
trl.as_trl()
}
}
impl fmt::Debug for Trl<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Trl").field(&self.raw).finish()
}
}
impl fmt::Display for Trl<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.raw)
}
}
impl fmt::Debug for ArcTrl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("ArcTrl").field(&&*self.raw).finish()
}
}
impl fmt::Display for ArcTrl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.raw)
}
}
pub enum TrlObject<'a> {
Alias(&'a str),
Dev(&'a str),
Attr(&'a str, &'a str),
}
fn is_hostname_char((i, c): (usize, char)) -> bool {
c.is_ascii_alphanumeric() || (i != 0 && (c == '-' || c == '.'))
}
fn is_tango_name_char((i, c): (usize, char)) -> bool {
c.is_ascii_alphanumeric() || (i != 0 && (c == '_' || c == '-'))
}
pub trait IntoTrl<'a> {
fn into_trl(self) -> Result<Trl<'a>, TrlError>;
}
impl<'a> IntoTrl<'a> for Trl<'a> {
fn into_trl(self) -> Result<Trl<'a>, TrlError> {
Ok(self)
}
}
impl<'a> IntoTrl<'a> for &'a ArcTrl {
fn into_trl(self) -> Result<Trl<'a>, TrlError> {
Ok(self.as_trl())
}
}
impl<'a> IntoTrl<'a> for &'a str {
fn into_trl(self) -> Result<Trl<'a>, TrlError> {
Trl::parse(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::num::NonZero;
use std::collections::HashSet;
#[test]
fn name_ignores_case() {
assert_eq!(Name::new("Sys/TG_Test/1"), Name::new("sys/tg_test/1"));
assert_ne!(Name::new("sys/tg_test/1"), Name::new("sys/tg_test/2"));
assert_ne!(Name::new("attr"), Name::new("attr_2"));
let names = HashSet::from([ArcName::from("Double_Scalar")]);
assert!(names.contains(Name::new("double_scalar")));
assert!(!names.contains(Name::new("double_scalar_w")));
}
#[test]
fn name_keeps_case() {
let name = Name::new("Double_Scalar");
assert_eq!(name.as_str(), "Double_Scalar");
assert_eq!(name.to_string(), "Double_Scalar");
let arc_name = name.to_arc();
assert_eq!(arc_name.as_str(), "Double_Scalar");
assert_eq!(arc_name.to_string(), "Double_Scalar");
assert_eq!("double_scalar".parse::<ArcName>().unwrap(), arc_name);
}
#[test]
fn name_chars() {
assert!(check_tango_class_name("Motor_2-a"));
assert!(check_tango_class_name("2Motor"));
assert!(!check_tango_class_name(""));
assert!(!check_tango_class_name("_Motor"));
assert!(!check_tango_class_name("-Motor"));
assert!(!check_tango_class_name("Mo tor"));
assert!(!check_tango_class_name("Mo.tor"));
assert!(!check_tango_class_name("Mo/tor"));
assert!(!check_tango_class_name("Motör"));
}
#[test]
fn name_length() {
let (max, over) = ("a".repeat(255), "a".repeat(256));
assert!(check_tango_attr_name(&max) && !check_tango_attr_name(&over));
assert!(check_tango_command_name(&max) && !check_tango_command_name(&over));
assert!(check_tango_property_name(&max) && !check_tango_property_name(&over));
assert!(check_tango_alias(&max) && !check_tango_alias(&over));
assert!(check_tango_class_name(&over));
}
#[test]
fn dev_name() {
assert!(check_tango_dev_name("sys/tg_test/1"));
assert!(check_tango_dev_name(&format!("{0}/{0}/{0}", "a".repeat(85))));
assert!(!check_tango_dev_name(&format!("a/a/{}", "a".repeat(86))));
assert!(!check_tango_dev_name("sys/tg_test"));
assert!(!check_tango_dev_name("sys/tg_test/1/2"));
assert!(!check_tango_dev_name("sys//1"));
assert!(!check_tango_dev_name("sys/tg_test/_1"));
}
#[test]
fn server_name() {
assert!(check_tango_server_name("TangoTest/test"));
assert!(!check_tango_server_name("TangoTest"));
assert!(!check_tango_server_name("TangoTest/"));
assert!(!check_tango_server_name("TangoTest/te/st"));
assert!(!check_tango_server_name("TangoTest/te st"));
}
#[test]
fn parse_and_get() {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TrlObjectKind {
Alias,
Dev,
Attr,
}
for protocol in [false, true] {
for host_and_port in [false, true] {
for object in [TrlObjectKind::Alias, TrlObjectKind::Dev, TrlObjectKind::Attr] {
for property in [false, true] {
for db in [None, Some(false), Some(true)] {
let mut buf = String::new();
if protocol {
buf.push_str("tango://");
}
if host_and_port {
buf.push_str("example.com:10000/");
}
let object_index = buf.len() as u16;
match object {
TrlObjectKind::Alias => buf.push_str("alias"),
TrlObjectKind::Dev => buf.push_str("domain/family/member"),
TrlObjectKind::Attr => buf.push_str("domain/family/member/attribute"),
}
if property {
buf.push_str("->property");
}
match db {
Some(false) => buf.push_str("#dbase=no"),
Some(true) => buf.push_str("#dbase=yes"),
None => (),
}
let trl = Trl::parse(&buf).unwrap();
assert_eq!(trl.meta.protocol, protocol);
assert_eq!(trl.meta.host_len_and_port, host_and_port.then_some((NonZero::new(11u8).unwrap(), NonZero::new(10000u16).unwrap())));
assert_eq!(trl.meta.object_index, object_index);
assert_eq!(trl.meta.object, match object {
TrlObjectKind::Alias => TrlObjectMeta::Alias(NonZero::new(5u8).unwrap()),
TrlObjectKind::Dev => TrlObjectMeta::Dev(NonZero::new(18u8).unwrap()),
TrlObjectKind::Attr => TrlObjectMeta::Attr(NonZero::new(18u8).unwrap(), NonZero::new(9u8).unwrap()),
});
assert_eq!(trl.meta.property_len, property.then_some(NonZero::new(8u8).unwrap()));
assert_eq!(trl.meta.db, db.unwrap_or(true));
}
}
}
}
}
}
}