Skip to content

Commit

Permalink
Add addr module
Browse files Browse the repository at this point in the history
Signed-off-by: Marc-André Lureau <[email protected]>
  • Loading branch information
elmarco committed Aug 5, 2023
1 parent 85eb406 commit 4a21054
Show file tree
Hide file tree
Showing 14 changed files with 967 additions and 1 deletion.
146 changes: 146 additions & 0 deletions zbus/src/addr/address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
use std::{borrow::Cow, collections::HashSet, fmt};

use crate::{Error, Guid, Result};

use super::{
percent::{decode_percents_str, Encodable},
transport::Transport,
};

#[derive(Debug, PartialEq, Eq)]
pub struct Address<'a> {
pub(super) addr: &'a str,
}

impl<'a> Address<'a> {
pub fn guid(&self) -> Option<Result<Guid>> {
match self.get_string("guid") {
Some(Ok(v)) => Some(Guid::try_from(v.as_ref())),
Some(Err(e)) => Some(Err(e)),
_ => None,
}
}

pub fn transport(&self) -> Result<Transport<'_>> {
self.try_into()
}

fn from_str(addr: &'a str) -> Result<Self> {
let addr = Self { addr };
addr.transport()?;
let mut set = HashSet::new();
for (k, _) in addr.key_val_iter() {
if !set.insert(k) {
return Err(Error::Address(format!("Duplicate key: {k}")));
}
}
Ok(addr)
}

pub(super) fn key_val_iter(&self) -> KeyValIter<'a> {
let mut split = self.addr.splitn(2, ':');
// skip transport:..
split.next();
let kv = split.next().unwrap_or(&"");
KeyValIter::new(kv)
}

fn get_string(&self, key: &str) -> Option<Result<Cow<'a, str>>> {
for (k, v) in self.key_val_iter() {
if key == k {
return v.map(decode_percents_str);
}
}
None
}
}

impl<'a> TryFrom<&'a str> for Address<'a> {
type Error = Error;

fn try_from(addr: &'a str) -> Result<Self> {
Self::from_str(addr)
}
}

impl fmt::Display for Address<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let kv = KeyValFmt::new().add("guid", self.guid().map(|v| v.ok()).flatten());
let t = self.transport().map_err(|_| fmt::Error)?;
let kv = t.key_val_fmt_add(kv);
write!(f, "{t}:{kv}")?;
Ok(())
}
}

pub(super) struct KeyValIter<'a> {
data: &'a str,
next_index: usize,
}

impl<'a> KeyValIter<'a> {
fn new(data: &'a str) -> Self {
KeyValIter {
data,
next_index: 0,
}
}
}

impl<'a> Iterator for KeyValIter<'a> {
type Item = (&'a str, Option<&'a str>);

fn next(&mut self) -> Option<Self::Item> {
if self.next_index >= self.data.len() {
return None;
}

let mut pair = &self.data[self.next_index..];
if let Some(end) = pair.find(',') {
pair = &pair[..end];
self.next_index += end + 1;
} else {
self.next_index = self.data.len();
}
let mut split = pair.split('=');
// SAFETY: first split always returns something
let key = split.next().unwrap();
Some((key, split.next()))
}
}

pub(crate) trait KeyValFmtAdd {
fn key_val_fmt_add<'a: 'b, 'b>(&'a self, kv: KeyValFmt<'b>) -> KeyValFmt<'b>;
}

pub(crate) struct KeyValFmt<'a> {
fields: Vec<(&'a str, Box<dyn Encodable + 'a>)>,
}

impl<'a> KeyValFmt<'a> {
fn new() -> Self {
Self { fields: vec![] }
}

pub(crate) fn add<T: Encodable + 'a>(mut self, key: &'a str, val: Option<T>) -> Self {
if let Some(val) = val {
self.fields.push((key, Box::new(val)));
}
self
}
}

impl<'a> fmt::Display for KeyValFmt<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut first = true;
for (k, v) in self.fields.iter() {
if !first {
write!(f, ",")?;
}
write!(f, "{k}=")?;
v.encode(f)?;
first = false;
}
Ok(())
}
}
41 changes: 41 additions & 0 deletions zbus/src/addr/address_list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use std::fmt;

use crate::{Error, Result};

use super::Address;

pub struct AddressList<'a> {
list: Vec<Address<'a>>,
}

impl<'a> AddressList<'a> {
pub fn list(&self) -> &[Address<'_>] {
self.list.as_ref()
}
}

impl<'a> TryFrom<&'a str> for AddressList<'a> {
type Error = Error;

fn try_from(value: &'a str) -> Result<Self> {
let mut list = vec![];
for addr in value.split(';') {
list.push(Address::try_from(addr)?);
}
Ok(Self { list })
}
}

impl fmt::Display for AddressList<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut first = true;
for a in self.list() {
if !first {
write!(f, ";")?;
}
write!(f, "{}", a)?;
first = false;
}
Ok(())
}
}
55 changes: 55 additions & 0 deletions zbus/src/addr/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//! D-Bus address handling.
//!
//! Server addresses consist of a transport name followed by a colon, and then an optional,
//! comma-separated list of keys and values in the form key=value.
//!
//! See also:
//!
//! * [Server addresses] in the D-Bus specification.
//!
//! [Server addresses]: https://dbus.freedesktop.org/doc/dbus-specification.html#addresses

// note: assumes values are utf-8 encoded - this should be clarified in the spec
// otherwise, fail to read them or use lossy representation for display
//
// assumes that empty key=val is accepted, so "transport:,,guid=..." is valid
//
// allows key only, so "transport:foo,bar" is ok
//
// mostly ignores unknown keys and transport

pub mod transport;

mod address;
pub use address::Address;

mod address_list;
pub use address_list::AddressList;

mod percent;

#[cfg(test)]
mod tests {
use std::{borrow::Cow, ffi::OsStr};

use super::{
transport::{unix::UnixAddrKind, Transport},
Address,
};

#[test]
fn parse_dbus_addresses() {
let addr =
Address::try_from("unix:path=/tmp/dbus-foo,guid=9406e28972c595c590766c9564ce623f")
.unwrap();
let guid = addr.guid();
assert!(matches!(guid, Some(Ok(_))));
let Transport::Unix(u) = addr.transport().unwrap() else {
panic!();
};
assert_eq!(
u.kind(),
&UnixAddrKind::Path(Cow::Borrowed(OsStr::new("/tmp/dbus-foo")))
);
}
}
154 changes: 154 additions & 0 deletions zbus/src/addr/percent.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
use std::{
borrow::Cow,
ffi::{OsStr, OsString},
fmt,
str::from_utf8_unchecked,
};

use crate::{Error, Result};

pub(crate) trait Encodable {
fn encode(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result;
}

impl<T: ToString> Encodable for T {
fn encode(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
encode_percents(f, self.to_string().as_bytes())
}
}

pub(crate) struct EncData<T: ?Sized>(pub T);

impl<T: AsRef<[u8]>> Encodable for EncData<T> {
fn encode(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
encode_percents(f, self.0.as_ref())
}
}

pub(crate) struct EncOsStr<T: ?Sized>(pub T);

impl Encodable for EncOsStr<&Cow<'_, OsStr>> {
fn encode(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
encode_percents(f, self.0.to_string_lossy().as_bytes())
}
}

impl Encodable for EncOsStr<&OsStr> {
fn encode(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
encode_percents(f, self.0.to_string_lossy().as_bytes())
}
}

pub(super) fn encode_percents(f: &mut fmt::Formatter<'_>, mut value: &[u8]) -> std::fmt::Result {
const LOOKUP: &str = "\
%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f\
%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f\
%20%21%22%23%24%25%26%27%28%29%2a%2b%2c%2d%2e%2f\
%30%31%32%33%34%35%36%37%38%39%3a%3b%3c%3d%3e%3f\
%40%41%42%43%44%45%46%47%48%49%4a%4b%4c%4d%4e%4f\
%50%51%52%53%54%55%56%57%58%59%5a%5b%5c%5d%5e%5f\
%60%61%62%63%64%65%66%67%68%69%6a%6b%6c%6d%6e%6f\
%70%71%72%73%74%75%76%77%78%79%7a%7b%7c%7d%7e%7f\
%80%81%82%83%84%85%86%87%88%89%8a%8b%8c%8d%8e%8f\
%90%91%92%93%94%95%96%97%98%99%9a%9b%9c%9d%9e%9f\
%a0%a1%a2%a3%a4%a5%a6%a7%a8%a9%aa%ab%ac%ad%ae%af\
%b0%b1%b2%b3%b4%b5%b6%b7%b8%b9%ba%bb%bc%bd%be%bf\
%c0%c1%c2%c3%c4%c5%c6%c7%c8%c9%ca%cb%cc%cd%ce%cf\
%d0%d1%d2%d3%d4%d5%d6%d7%d8%d9%da%db%dc%dd%de%df\
%e0%e1%e2%e3%e4%e5%e6%e7%e8%e9%ea%eb%ec%ed%ee%ef\
%f0%f1%f2%f3%f4%f5%f6%f7%f8%f9%fa%fb%fc%fd%fe%ff";

loop {
let pos = value.iter().position(
|c| !matches!(c, b'-' | b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'_' | b'/' | b'.' | b'\\' | b'*'),
);

if let Some(pos) = pos {
// SAFETY: The above `position()` call made sure that only ASCII chars are in the string
// up to `pos`
f.write_str(unsafe { from_utf8_unchecked(&value[..pos]) })?;

let c = value[pos];
value = &value[pos + 1..];

let pos = c as usize * 3;
f.write_str(&LOOKUP[pos..pos + 3])?;
} else {
// SAFETY: The above `position()` call made sure that only ASCII chars are in the rest
// of the string
f.write_str(unsafe { from_utf8_unchecked(value) })?;
return Ok(());
}
}
}

fn decode_hex(c: char) -> Result<u8> {
match c {
'0'..='9' => Ok(c as u8 - b'0'),
'a'..='f' => Ok(c as u8 - b'a' + 10),
'A'..='F' => Ok(c as u8 - b'A' + 10),

_ => Err(Error::Address(
"invalid hexadecimal character in percent-encoded sequence".to_owned(),
)),
}
}

pub(super) fn decode_percents(value: &str) -> Result<Cow<'_, [u8]>> {
if value.find('%').is_none() {
return Ok(value.as_bytes().into());
}

let mut iter = value.chars();
let mut decoded = Vec::new();

while let Some(c) = iter.next() {
if matches!(c, '-' | '0'..='9' | 'A'..='Z' | 'a'..='z' | '_' | '/' | '.' | '\\' | '*') {
decoded.push(c as u8)
} else if c == '%' {
decoded.push(
decode_hex(iter.next().ok_or_else(|| {
Error::Address("incomplete percent-encoded sequence".into())
})?)?
<< 4
| decode_hex(iter.next().ok_or_else(|| {
Error::Address("incomplete percent-encoded sequence".into())
})?)?,
);
} else {
return Err(Error::Address("Invalid character in address".into()));
}
}

Ok(decoded.into())
}

pub(super) fn decode_percents_str(value: &str) -> Result<Cow<'_, str>> {
cow_bytes_to_str(decode_percents(value)?)
}

fn cow_bytes_to_str<'a>(cow: Cow<'a, [u8]>) -> Result<Cow<'a, str>> {
match cow {
Cow::Borrowed(bytes) => Ok(Cow::Borrowed(
std::str::from_utf8(bytes).map_err(|e| Error::Address(format!("{e}")))?,
)),
Cow::Owned(bytes) => Ok(Cow::Owned(
String::from_utf8(bytes).map_err(|e| Error::Address(format!("{e}")))?,
)),
}
}

pub(super) fn decode_percents_os_str(value: &str) -> Result<Cow<'_, OsStr>> {
cow_bytes_to_os_str(decode_percents(value)?)
}

fn cow_bytes_to_os_str<'a>(cow: Cow<'a, [u8]>) -> Result<Cow<'a, OsStr>> {
match cow {
Cow::Borrowed(bytes) => Ok(Cow::Borrowed(OsStr::new(
std::str::from_utf8(bytes).map_err(|e| Error::Address(format!("{e}")))?,
))),
Cow::Owned(bytes) => Ok(Cow::Owned(OsString::from(
String::from_utf8(bytes).map_err(|e| Error::Address(format!("{e}")))?,
))),
}
}
Loading

0 comments on commit 4a21054

Please sign in to comment.