pub(crate) const METHOD_CALL: u8 = 1;
pub(crate) const METHOD_RETURN: u8 = 2;
pub(crate) const ERROR: u8 = 3;
pub(crate) const FIELD_REPLY_SERIAL: u8 = 5;
pub(crate) const FIELD_PATH: u8 = 1;
pub(crate) const FIELD_INTERFACE: u8 = 2;
pub(crate) const FIELD_MEMBER: u8 = 3;
pub(crate) const FIELD_ERROR_NAME: u8 = 4;
pub(crate) const FIELD_DESTINATION: u8 = 6;
pub(crate) const FIELD_SIGNATURE: u8 = 8;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CallError {
Connection(String),
Authentication(String),
TimedOut,
Manager {
name: String,
text: String,
},
Malformed(String),
}
pub(crate) struct Writer {
bytes: Vec<u8>,
}
impl Writer {
fn align(&mut self, boundary: usize) {
while self.bytes.len() % boundary != 0 {
self.bytes.push(0);
}
}
fn unsigned(&mut self, value: u32) {
self.align(4);
self.bytes.extend(value.to_le_bytes());
}
fn text(&mut self, value: &str) {
self.unsigned(u32::try_from(value.len()).unwrap_or(u32::MAX));
self.bytes.extend(value.as_bytes());
self.bytes.push(0);
}
fn signature(&mut self, value: &str) {
self.bytes.push(u8::try_from(value.len()).unwrap_or(u8::MAX));
self.bytes.extend(value.as_bytes());
self.bytes.push(0);
}
fn field(&mut self, code: u8, kind: char, value: &str) {
self.align(8);
self.bytes.push(code);
self.signature(&kind.to_string());
if kind == 'g' {
self.signature(value);
} else {
self.text(value);
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Target<'a> {
pub destination: &'a str,
pub path: &'a str,
pub interface: &'a str,
pub member: &'a str,
}
pub fn encode_call(serial: u32, target: &Target<'_>, arguments: &[&str]) -> Vec<u8> {
let mut body: Writer = Writer { bytes: Vec::new() };
for argument in arguments {
body.text(argument);
}
let mut fields: Writer = Writer { bytes: vec![0; 16] };
fields.field(FIELD_PATH, 'o', target.path);
fields.field(FIELD_DESTINATION, 's', target.destination);
fields.field(FIELD_INTERFACE, 's', target.interface);
fields.field(FIELD_MEMBER, 's', target.member);
if !arguments.is_empty() {
fields.field(FIELD_SIGNATURE, 'g', &"s".repeat(arguments.len()));
}
let array_length: u32 = u32::try_from(fields.bytes.len() - 16).unwrap_or(u32::MAX);
fields.align(8);
let mut message: Vec<u8> = fields.bytes;
message[0] = b'l';
message[1] = METHOD_CALL;
message[2] = 0;
message[3] = 1;
message[4..8].copy_from_slice(&u32::try_from(body.bytes.len()).unwrap_or(u32::MAX).to_le_bytes());
message[8..12].copy_from_slice(&serial.to_le_bytes());
message[12..16].copy_from_slice(&array_length.to_le_bytes());
message.extend(body.bytes);
return message;
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Reply {
pub kind: u8,
pub reply_serial: Option<u32>,
pub error_name: Option<String>,
pub first_text: Option<String>,
}
pub(crate) fn read_u32(bytes: &[u8], at: usize) -> Option<u32> {
return bytes.get(at..at.checked_add(4)?).map(u32_of_four_bytes);
}
fn u32_of_four_bytes(slice: &[u8]) -> u32 {
return u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]);
}
fn text_of(read: (String, usize)) -> String {
return read.0;
}
pub(crate) fn read_text(bytes: &[u8], at: usize) -> Option<(String, usize)> {
let length: usize = usize::try_from(read_u32(bytes, at)?).ok()?;
let start: usize = at.checked_add(4)?;
let end: usize = start.checked_add(length)?;
let text: &[u8] = bytes.get(start..end)?;
if *bytes.get(end)? != 0 {
return None;
}
return Some((String::from_utf8_lossy(text).into_owned(), end.saturating_add(1)));
}
fn read_variant_text(bytes: &[u8], at: usize) -> Option<String> {
let length: usize = usize::from(*bytes.get(at)?);
let inner_start: usize = at.checked_add(1)?;
let inner_end: usize = inner_start.checked_add(length)?;
let inner: &[u8] = bytes.get(inner_start..inner_end)?;
if inner != b"s" || *bytes.get(inner_end)? != 0 {
return None;
}
return read_text(bytes, aligned(inner_end.checked_add(1)?, 4)).map(text_of);
}
pub(crate) fn aligned(offset: usize, boundary: usize) -> usize {
return match offset.checked_add(boundary - 1) {
Some(sum) => sum / boundary * boundary,
None => usize::MAX,
};
}
pub fn decode_reply(bytes: &[u8]) -> Result<Reply, CallError> {
if bytes.len() < 16 || bytes[0] != b'l' {
return Err(malformed("the reply is not a little-endian message"));
}
let kind: u8 = bytes[1];
let Some(body_length_field) = read_u32(bytes, 4) else {
return Err(malformed("no body length"));
};
let body_length: usize = usize::try_from(body_length_field).unwrap_or(usize::MAX);
let Some(fields_length_field) = read_u32(bytes, 12) else {
return Err(malformed("no header length"));
};
let fields_length: usize = usize::try_from(fields_length_field).unwrap_or(usize::MAX);
let fields_end: usize = 16usize.saturating_add(fields_length);
let body_start: usize = aligned(fields_end, 8);
if bytes.len() < body_start.saturating_add(body_length) || fields_end > bytes.len() {
return Err(malformed("the reply is shorter than it says"));
}
let mut error_name: Option<String> = None;
let mut reply_serial: Option<u32> = None;
let mut signature: String = String::new();
let header: &[u8] = bytes.get(..fields_end).unwrap_or_default();
let body: &[u8] = bytes.get(..body_start.saturating_add(body_length)).unwrap_or_default();
let mut at: usize = 16;
while at < fields_end {
at = aligned(at, 8);
let (Some(&code), Some(&signature_length)) = (header.get(at), header.get(at.saturating_add(1))) else { break };
let kind_at: usize = at.saturating_add(2);
let Some(&value_kind) = header.get(kind_at) else { break };
let value_at: usize = kind_at.saturating_add(usize::from(signature_length)).saturating_add(1);
match (value_kind, code) {
(b's' | b'o', _) => {
let Some((text, next)) = read_text(header, aligned(value_at, 4)) else { break };
if code == FIELD_ERROR_NAME {
error_name = Some(text);
}
at = next;
}
(b'g', _) => {
let length: usize = usize::from(*header.get(value_at).unwrap_or(&0));
if code == FIELD_SIGNATURE {
let start: usize = value_at.saturating_add(1);
if header.get(start.saturating_add(length)) == Some(&0) {
signature = String::from_utf8_lossy(header.get(start..start.saturating_add(length)).unwrap_or_default()).into_owned();
}
}
at = value_at.saturating_add(length).saturating_add(2);
}
(b'u', _) => {
let number_at: usize = aligned(value_at, 4);
if code == FIELD_REPLY_SERIAL {
reply_serial = read_u32(header, number_at);
}
at = number_at.saturating_add(4);
}
_ => break,
}
}
let first_text: Option<String> = if signature.starts_with('s') || signature.starts_with('o') {
read_text(body, aligned(body_start, 4)).map(text_of)
} else if signature == "v" {
read_variant_text(body, body_start)
} else {
None
};
return Ok(Reply { kind, reply_serial, error_name, first_text });
}
fn malformed(what: &str) -> CallError {
return CallError::Malformed(what.to_string());
}