use std::fmt;
use std::hash::Hash;
use std::num::NonZero;
use std::ops::{Deref, DerefMut};
use std::str::FromStr;
use std::sync::Arc;
#[derive(thiserror::Error, Debug)]
pub enum ParseTrlError {
#[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)")]
InvalidDeviceName,
#[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)")]
InvalidPropertyName,
}
#[derive(Clone)]
pub struct Trl {
raw: Arc<str>,
host_len_and_port: Option<(NonZero<u8>, NonZero<u16>)>,
object_index: u16,
object: TrlObjectInner,
property_len: Option<NonZero<u8>>,
protocol: bool,
db: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum TrlObjectInner {
Alias(NonZero<u8>),
Dev(NonZero<u8>),
Attr(NonZero<u8>, NonZero<u8>),
}
impl FromStr for Trl {
type Err = ParseTrlError;
fn from_str(raw: &str) -> Result<Self, Self::Err> {
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(ParseTrlError::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(ParseTrlError::InvalidHash),
};
rem = new_rem;
}
let mut property_len = None;
if let Some((new_rem, property_raw)) = rem.rsplit_once("->") {
property_len = Some(u8::try_from(property_raw.len()).ok()
.and_then(NonZero::new)
.ok_or(ParseTrlError::InvalidPropertyName)?);
if !property_raw.chars().enumerate().all(is_tango_name_char) {
return Err(ParseTrlError::InvalidPropertyName);
}
rem = new_rem;
}
let mut parts = [""; 5];
let mut parts_count = 0;
for part in rem.split('/') {
if parts_count >= parts.len() {
return Err(ParseTrlError::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(ParseTrlError::InvalidPath)
}
let host_len = u8::try_from(host_raw.len()).ok()
.and_then(NonZero::new)
.ok_or(ParseTrlError::InvalidHost)?;
if !host_raw.chars().enumerate().all(is_hostname_char) {
return Err(ParseTrlError::InvalidHost);
}
let port = port_raw.parse::<NonZero<u16>>()
.map_err(|_| ParseTrlError::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 {
object = TrlObjectInner::Alias(u8::try_from(parts[0].len()).ok()
.and_then(NonZero::new)
.ok_or(ParseTrlError::InvalidAlias)?);
if !parts[0].chars().enumerate().all(is_tango_name_char) {
return Err(ParseTrlError::InvalidAlias)
}
} else if parts.len() == 3 || parts.len() == 4 {
let mut dev_len_raw = 0; for dev_part in &parts[..3] {
if dev_part.len() == 0 || dev_part.len() > 85 {
return Err(ParseTrlError::InvalidDeviceName);
}
if !dev_part.chars().enumerate().all(is_tango_name_char) {
return Err(ParseTrlError::InvalidDeviceName);
}
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) {
object = TrlObjectInner::Attr(dev_len, u8::try_from(attr_raw.len()).ok()
.and_then(NonZero::new)
.ok_or(ParseTrlError::InvalidAttrName)?);
if !attr_raw.chars().enumerate().all(is_tango_name_char) {
return Err(ParseTrlError::InvalidAttrName);
}
} else {
object = TrlObjectInner::Dev(dev_len);
}
} else {
return Err(ParseTrlError::InvalidPath);
}
Ok(Self {
raw: Arc::from(raw),
host_len_and_port,
object_index: object_offset,
object,
property_len,
protocol,
db,
})
}
}
impl Trl {
#[inline]
pub fn as_str(&self) -> &str {
&self.raw
}
#[inline]
fn host_and_port_index(&self) -> usize {
if self.protocol { "tango://".len() } else { 0 }
}
pub fn host_and_port(&self) -> Option<(&str, u16)> {
let index = self.host_and_port_index();
self.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.object_index as usize
}
pub fn object(&self) -> TrlObject<'_> {
let index = self.object_index();
match self.object {
TrlObjectInner::Alias(alias_len) => {
TrlObject::Alias(&self.raw[index..][..alias_len.get() as usize])
}
TrlObjectInner::Dev(dev_len) => {
TrlObject::Dev(&self.raw[index..][..dev_len.get() as usize + 2])
}
TrlObjectInner::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.object {
TrlObjectInner::Alias(alias_len) =>
alias_len.get() as usize,
TrlObjectInner::Dev(dev_len) =>
dev_len.get() as usize + 2,
TrlObjectInner::Attr(dev_len, attr_len) =>
dev_len.get() as usize + attr_len.get() as usize + 3,
} + 2 }
pub fn property(&self) -> Option<&str> {
let index = self.property_index();
self.property_len.map(|len| {
&self.raw[index..][..len.get() as usize]
})
}
#[inline]
pub fn db(&self) -> bool {
self.db
}
}
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)
}
}
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 {
fn into_trl(self) -> Result<Trl, ParseTrlError>;
}
impl IntoTrl for Trl {
fn into_trl(self) -> Result<Trl, ParseTrlError> {
Ok(self)
}
}
impl IntoTrl for &'_ str {
fn into_trl(self) -> Result<Trl, ParseTrlError> {
Trl::from_str(self)
}
}
#[repr(transparent)]
pub struct DevName(str);
impl DevName {
pub unsafe fn new_unchecked<'a>(raw: &'a str) -> &'a Self {
unsafe { &*(raw as *const str as *const Self) }
}
pub unsafe fn new_unchecked_mut<'a>(raw: &'a mut str) -> &'a mut Self {
unsafe { &mut *(raw as *mut str as *mut Self) }
}
#[inline]
pub fn domain(&self) -> &str {
self.0.split('/').nth(0).unwrap()
}
#[inline]
pub fn family(&self) -> &str {
self.0.split('/').nth(1).unwrap()
}
#[inline]
pub fn member(&self) -> &str {
self.0.split('/').nth(2).unwrap()
}
#[inline]
pub fn as_str(&self) -> &str {
&self.0
}
#[inline]
pub fn to_buf(&self) -> DevNameBuf {
DevNameBuf { inner: self.0.to_string() }
}
}
pub struct DevNameBuf {
inner: String,
}
impl Deref for DevNameBuf {
type Target = DevName;
#[inline]
fn deref(&self) -> &Self::Target {
unsafe { &*(self.inner.as_str() as *const str as *const DevName) }
}
}
impl DerefMut for DevNameBuf {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *(self.inner.as_mut_str() as *mut str as *mut DevName) }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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::from_str(&buf).unwrap();
assert_eq!(trl.protocol, protocol);
assert_eq!(trl.host_len_and_port, host_and_port.then_some((NonZero::new(11u8).unwrap(), NonZero::new(10000u16).unwrap())));
assert_eq!(trl.object_index, object_index);
assert_eq!(trl.object, match object {
TrlObjectKind::Alias => TrlObjectInner::Alias(NonZero::new(5u8).unwrap()),
TrlObjectKind::Dev => TrlObjectInner::Dev(NonZero::new(18u8).unwrap()),
TrlObjectKind::Attr => TrlObjectInner::Attr(NonZero::new(18u8).unwrap(), NonZero::new(9u8).unwrap()),
});
assert_eq!(trl.property_len, property.then_some(NonZero::new(8u8).unwrap()));
assert_eq!(trl.db, db.unwrap_or(true));
}
}
}
}
}
}
}