use std::{
borrow::Cow,
collections::HashMap,
future::Future,
io,
num::NonZeroUsize,
sync::{
Arc, OnceLock, Weak,
atomic::{AtomicBool, Ordering},
},
time::Duration,
};
use zruntime::{
Event, EventListener,
broadcast::{InactiveReceiver, Receiver, Sender as Broadcaster, channel},
};
#[cfg(feature = "service")]
use crate::ObjectServer;
#[cfg(feature = "service")]
use crate::log::debug;
use crate::{
DBusError, Error, MatchRule, ObjectPath, OwnedGuid, OwnedMatchRule, Result,
fdo::{ConnectionCredentials, ReleaseNameReply, RequestNameFlags, RequestNameReply},
log::{Instrument, info, info_span, trace, trace_span, warn},
message::{self, Flags, Message, Type},
names::{BusName, ErrorName, InterfaceName, MemberName, OwnedUniqueName, WellKnownName},
runtime::{Runtime, Task, locks::Mutex, traits},
utils::next,
};
mod activity_listener;
pub use activity_listener::ActivityListener;
mod builder;
pub use builder::Builder;
pub mod socket;
pub use socket::Socket;
mod socket_reader;
use socket_reader::{SocketReader, SocketStatus};
mod pending_method_calls;
use pending_method_calls::{PendingMethodCall, PendingMethodCalls};
pub(crate) mod handshake;
pub use handshake::AuthMechanism;
use handshake::Authenticated;
const DEFAULT_MAX_QUEUED: NonZeroUsize = NonZeroUsize::new(64).unwrap();
#[derive(Debug)]
pub(crate) struct ConnectionInner {
server_guid: OwnedGuid,
#[cfg(unix)]
cap_unix_fd: bool,
#[cfg(feature = "p2p")]
bus_conn: bool,
unique_name: OnceLock<OwnedUniqueName>,
registered_names: Mutex<HashMap<WellKnownName<'static>, NameStatus>>,
socket_status: Arc<SocketStatus>,
socket_write: Mutex<Box<dyn socket::WriteHalf>>,
#[allow(unused)]
socket_reader_task: OnceLock<Task<()>>,
pub(crate) msg_receiver: InactiveReceiver<Result<Message>>,
msg_senders: Arc<Mutex<HashMap<Option<OwnedMatchRule>, MsgBroadcaster>>>,
pending_method_calls: PendingMethodCalls,
subscriptions: Mutex<Subscriptions>,
#[cfg(feature = "service")]
object_server: OnceLock<ObjectServer>,
#[cfg(feature = "service")]
object_server_dispatch_task: OnceLock<Task<()>>,
runtime: Runtime,
drop_event: Event,
method_timeout: Option<Duration>,
credentials: OnceLock<Arc<ConnectionCredentials>>,
}
impl ConnectionInner {
pub(crate) fn is_closed(&self) -> bool {
self.socket_status.closed.load(Ordering::Acquire)
}
pub(crate) fn closed_listener(&self) -> EventListener {
self.socket_status.closed_event.listen()
}
}
impl Drop for ConnectionInner {
fn drop(&mut self) {
self.drop_event.notify(usize::MAX);
}
}
type Subscriptions = HashMap<OwnedMatchRule, (u64, InactiveReceiver<Result<Message>>)>;
pub(crate) type MsgBroadcaster = Broadcaster<Result<Message>>;
#[cfg_attr(
all(feature = "proxy", feature = "service"),
doc = "[`macro@crate::proxy`] and [`macro@crate::interface`] macros instead of doing it",
doc = "directly on a `Connection`."
)]
#[cfg_attr(
not(all(feature = "proxy", feature = "service")),
doc = "`proxy` and `interface` macros (requiring the `proxy` and `service` features",
doc = "respectively) instead of doing it directly on a `Connection`."
)]
#[cfg_attr(
all(feature = "proxy", feature = "service"),
doc = "system bus with [`Connection::system`]. Then the connection is used with",
doc = "[`crate::Proxy`] instances or the on-demand [`ObjectServer`] instance that can be",
doc = "accessed through [`Connection::object_server`]."
)]
#[cfg_attr(
not(all(feature = "proxy", feature = "service")),
doc = "system bus with [`Connection::system`]. Then the connection is used with `Proxy`",
doc = "instances (requires the `proxy` feature) or the on-demand `ObjectServer` instance that",
doc = "can be accessed through `Connection::object_server` (requires the `service` feature)."
)]
#[derive(Clone, Debug)]
#[must_use = "Dropping a `Connection` will close the underlying socket."]
pub struct Connection {
pub(crate) inner: Arc<ConnectionInner>,
}
impl Connection {
pub async fn send(&self, msg: &Message) -> Result<()> {
#[cfg(unix)]
if !msg.data().fds().is_empty() && !self.inner.cap_unix_fd {
return Err(Error::Unsupported);
}
self.inner.socket_status.activity_event.notify(usize::MAX);
let mut write = self.inner.socket_write.lock().await;
write.send_message(msg).await
}
pub async fn send_all(&self, msgs: &[Message]) -> Result<()> {
#[cfg(unix)]
if !self.inner.cap_unix_fd && msgs.iter().any(|msg| !msg.data().fds().is_empty()) {
return Err(Error::Unsupported);
}
if msgs.is_empty() {
return Ok(());
}
self.inner.socket_status.activity_event.notify(usize::MAX);
let mut write = self.inner.socket_write.lock().await;
write.send_messages(msgs).await
}
pub async fn call_method<'d, 'p, 'i, 'm, D, P, I, M, B>(
&self,
destination: Option<D>,
path: P,
interface: Option<I>,
method_name: M,
body: &B,
) -> Result<Message>
where
D: TryInto<BusName<'d>>,
P: TryInto<ObjectPath<'p>>,
I: TryInto<InterfaceName<'i>>,
M: TryInto<MemberName<'m>>,
D::Error: Into<Error>,
P::Error: Into<Error>,
I::Error: Into<Error>,
M::Error: Into<Error>,
B: serde::ser::Serialize + crate::DynamicType,
{
let method = self
.call_method_raw(
destination,
path,
interface,
method_name,
Flags::empty(),
body,
)
.await?
.expect("no reply");
if let Some(tout) = self.method_timeout() {
self.inner.runtime.timeout(method, tout).await
} else {
method.await
}
}
pub(crate) async fn call_method_raw<'d, 'p, 'i, 'm, D, P, I, M, B>(
&self,
destination: Option<D>,
path: P,
interface: Option<I>,
method_name: M,
flags: Flags,
body: &B,
) -> Result<Option<PendingMethodCall>>
where
D: TryInto<BusName<'d>>,
P: TryInto<ObjectPath<'p>>,
I: TryInto<InterfaceName<'i>>,
M: TryInto<MemberName<'m>>,
D::Error: Into<Error>,
P::Error: Into<Error>,
I::Error: Into<Error>,
M::Error: Into<Error>,
B: serde::ser::Serialize + crate::DynamicType,
{
let destination = destination
.map(TryInto::try_into)
.transpose()
.map_err(Into::into)?;
let path = path.try_into().map_err(Into::into)?;
let interface = interface
.map(TryInto::try_into)
.transpose()
.map_err(Into::into)?;
let method_name = method_name.try_into().map_err(Into::into)?;
let msg = self
.method_call_builder(destination, path, interface, method_name, flags)
.build(body)?;
self.send_method_call(msg, flags).await
}
fn method_call_builder<'b>(
&'b self,
destination: Option<BusName<'b>>,
path: ObjectPath<'b>,
interface: Option<InterfaceName<'b>>,
method_name: MemberName<'b>,
flags: Flags,
) -> message::Builder<'b> {
let mut builder = Message::method_call(path, method_name);
if let Some(sender) = self.unique_name() {
builder = builder.sender(sender)
}
if let Some(destination) = destination {
builder = builder.destination(destination)
}
if let Some(interface) = interface {
builder = builder.interface(interface)
}
builder.with_flags(flags)
}
async fn send_method_call(
&self,
msg: Message,
flags: Flags,
) -> Result<Option<PendingMethodCall>> {
let serial = msg.primary_header().serial_num();
if flags.contains(Flags::NO_REPLY_EXPECTED) {
self.send(&msg).await?;
Ok(None)
} else {
let pending_call = self.inner.pending_method_calls.register_call(serial);
self.send(&msg).await?;
Ok(Some(pending_call))
}
}
pub async fn emit_signal<'d, 'p, 'i, 'm, D, P, I, M, B>(
&self,
destination: Option<D>,
path: P,
interface: I,
signal_name: M,
body: &B,
) -> Result<()>
where
D: TryInto<BusName<'d>>,
P: TryInto<ObjectPath<'p>>,
I: TryInto<InterfaceName<'i>>,
M: TryInto<MemberName<'m>>,
D::Error: Into<Error>,
P::Error: Into<Error>,
I::Error: Into<Error>,
M::Error: Into<Error>,
B: serde::ser::Serialize + crate::DynamicType,
{
let destination = destination
.map(TryInto::try_into)
.transpose()
.map_err(Into::into)?;
let path = path.try_into().map_err(Into::into)?;
let interface = interface.try_into().map_err(Into::into)?;
let signal_name = signal_name.try_into().map_err(Into::into)?;
let m = self
.signal_builder(destination, path, interface, signal_name)
.build(body)?;
self.send(&m).await
}
fn signal_builder<'b>(
&'b self,
destination: Option<BusName<'b>>,
path: ObjectPath<'b>,
interface: InterfaceName<'b>,
signal_name: MemberName<'b>,
) -> message::Builder<'b> {
let mut builder = Message::signal(path, interface, signal_name);
if let Some(sender) = self.unique_name() {
builder = builder.sender(sender);
}
if let Some(destination) = destination {
builder = builder.destination(destination);
}
builder
}
pub async fn reply<B>(&self, call: &zbus::message::Header<'_>, body: &B) -> Result<()>
where
B: serde::ser::Serialize + crate::DynamicType,
{
let m = self.reply_message(Message::method_return(call), &mut |b| b.build(body))?;
self.send(&m).await
}
pub async fn reply_error<'e, E, B>(
&self,
call: &zbus::message::Header<'_>,
error_name: E,
body: &B,
) -> Result<()>
where
B: serde::ser::Serialize + crate::DynamicType,
E: TryInto<ErrorName<'e>>,
E::Error: Into<Error>,
{
let m = self.reply_message(Message::error(call, error_name), &mut |b| b.build(body))?;
self.send(&m).await
}
fn reply_message<'b>(
&'b self,
mut builder: message::Builder<'b>,
build: &mut dyn FnMut(message::Builder<'_>) -> Result<Message>,
) -> Result<Message> {
if let Some(sender) = self.unique_name() {
builder = builder.sender(sender);
}
build(builder)
}
pub async fn reply_dbus_error(
&self,
call: &zbus::message::Header<'_>,
err: impl DBusError,
) -> Result<()> {
let m = err.create_reply(call)?;
self.send(&m).await
}
pub async fn request_name<'w, W>(&self, well_known_name: W) -> Result<()>
where
W: TryInto<WellKnownName<'w>>,
W::Error: Into<Error>,
{
self.request_name_with_flags(well_known_name, RequestNameFlags::default())
.await
.map(|_| ())
}
#[cfg_attr(
feature = "proxy",
doc = "If the [`RequestNameFlags::DO_NOT_QUEUE`] flag is not specified and request ends",
doc = "up in the queue, you can use [`crate::fdo::NameAcquiredStream`] to be notified",
doc = "when the name is acquired. A queued name request can be cancelled using",
doc = "[`Connection::release_name`].",
doc = "",
doc = "If the [`RequestNameFlags::ALLOW_REPLACEMENT`] flag is specified, the requested",
doc = "name can be lost if another peer requests the same name. You can use",
doc = "[`crate::fdo::NameLostStream`] to be notified when the name is lost"
)]
#[cfg_attr(
not(feature = "proxy"),
doc = "If the [`RequestNameFlags::DO_NOT_QUEUE`] flag is not specified and request ends",
doc = "up in the queue, you can use `fdo::NameAcquiredStream` (requires the `proxy`",
doc = "feature) to be notified when the name is acquired. A queued name request can be",
doc = "cancelled using [`Connection::release_name`].",
doc = "",
doc = "If the [`RequestNameFlags::ALLOW_REPLACEMENT`] flag is specified, the requested",
doc = "name can be lost if another peer requests the same name. You can use",
doc = "`fdo::NameLostStream` (requires the `proxy` feature) to be notified when the name",
doc = "is lost"
)]
#[cfg_attr(
feature = "proxy",
doc = "* If you wish to track changes to name ownership after this call, make sure that",
doc = " the [`crate::fdo::NameAcquiredStream`] and/or [`crate::fdo::NameLostStream`]",
doc = " instance(s) are created **before** calling this method. Otherwise, you may loose",
doc = " the signal if it's emitted after this call but just before the stream instance",
doc = " get created."
)]
#[cfg_attr(
not(feature = "proxy"),
doc = "* If you wish to track changes to name ownership after this call, make sure that",
doc = " the `fdo::NameAcquiredStream` and/or `fdo::NameLostStream` instance(s) (requiring",
doc = " the `proxy` feature) are created **before** calling this method. Otherwise, you",
doc = " may loose the signal if it's emitted after this call but just before the stream",
doc = " instance get created."
)]
pub async fn request_name_with_flags<'w, W>(
&self,
well_known_name: W,
flags: RequestNameFlags,
) -> Result<RequestNameReply>
where
W: TryInto<WellKnownName<'w>>,
W::Error: Into<Error>,
{
let well_known_name = well_known_name.try_into().map_err(Into::into)?;
#[cfg(feature = "service")]
if self.is_bus() && self.inner.object_server.get().is_none() {
warn!(
"Requesting name `{well_known_name}` before setting up the object server. \
Method calls arriving before interfaces are registered may be lost. \
Consider using `connection::Builder::serve_at()` and `::name()` instead.",
);
}
let mut names = self.inner.registered_names.lock().await;
match names.get(&well_known_name) {
Some(NameStatus::Owner(_)) => return Ok(RequestNameReply::AlreadyOwner),
Some(NameStatus::Queued(_)) => return Ok(RequestNameReply::InQueue),
None => (),
}
if !self.is_bus() {
names.insert(well_known_name.to_owned(), NameStatus::Owner(None));
return Ok(RequestNameReply::PrimaryOwner);
}
let acquired_match_rule = MatchRule::fdo_signal_builder("NameAcquired")
.arg(0, well_known_name.as_ref())
.build()?;
let mut acquired_stream = self.add_match(acquired_match_rule.into(), None).await?;
let lost_match_rule = MatchRule::fdo_signal_builder("NameLost")
.arg(0, well_known_name.as_ref())
.build()?;
let mut lost_stream = self.add_match(lost_match_rule.into(), None).await?;
let reply = self
.call_method(
Some("org.freedesktop.DBus"),
"/org/freedesktop/DBus",
Some("org.freedesktop.DBus"),
"RequestName",
&(well_known_name.clone(), flags),
)
.await?
.body()
.deserialize::<RequestNameReply>()?;
let lost_task_name = format!("monitor_name_lost{{name={well_known_name}}}");
let lost_task_name_span = info_span!("monitor_name_lost", name = %well_known_name);
let name_lost_fut = if flags.contains(RequestNameFlags::ALLOW_REPLACEMENT) {
let weak_conn = WeakConnection::from(self);
let well_known_name = well_known_name.to_owned();
Some(
async move {
loop {
let signal = next(&mut lost_stream).await;
let inner = match weak_conn.upgrade() {
Some(conn) => conn.inner,
None => break,
};
match signal {
Some(signal) => match signal {
Ok(_) => {
info!(
"Connection `{}` lost name `{}`",
inner.unique_name.get().unwrap(),
well_known_name
);
inner.registered_names.lock().await.remove(&well_known_name);
break;
}
Err(e) => warn!("Failed to parse `NameLost` signal: {}", e),
},
None => {
trace!("`NameLost` signal stream closed");
break;
}
}
}
}
.instrument(lost_task_name_span),
)
} else {
None
};
let status = match reply {
RequestNameReply::InQueue => {
let weak_conn = WeakConnection::from(self);
let well_known_name = well_known_name.to_owned();
let task_name = format!("monitor_name_acquired{{name={well_known_name}}}");
let task_name_span = info_span!("monitor_name_acquired", name = %well_known_name);
let task = self.runtime().spawn(
task_name,
async move {
loop {
let signal = next(&mut acquired_stream).await;
let inner = match weak_conn.upgrade() {
Some(conn) => conn.inner,
None => break,
};
match signal {
Some(signal) => match signal {
Ok(_) => {
let mut names = inner.registered_names.lock().await;
if let Some(status) = names.get_mut(&well_known_name) {
let task = name_lost_fut.map(|fut| {
inner.runtime.spawn(lost_task_name, fut)
});
*status = NameStatus::Owner(task);
break;
}
}
Err(e) => warn!("Failed to parse `NameAcquired` signal: {}", e),
},
None => {
trace!("`NameAcquired` signal stream closed");
break;
}
}
}
}
.instrument(task_name_span),
);
NameStatus::Queued(task)
}
RequestNameReply::PrimaryOwner | RequestNameReply::AlreadyOwner => {
let task = name_lost_fut.map(|fut| self.runtime().spawn(lost_task_name, fut));
NameStatus::Owner(task)
}
RequestNameReply::Exists => return Err(Error::NameTaken),
};
names.insert(well_known_name.to_owned(), status);
Ok(reply)
}
pub async fn release_name<'w, W>(&self, well_known_name: W) -> Result<bool>
where
W: TryInto<WellKnownName<'w>>,
W::Error: Into<Error>,
{
let well_known_name: WellKnownName<'w> = well_known_name.try_into().map_err(Into::into)?;
let mut names = self.inner.registered_names.lock().await;
if names.remove(&well_known_name.to_owned()).is_none() {
return Ok(false);
};
if !self.is_bus() {
return Ok(true);
}
self.call_method(
Some("org.freedesktop.DBus"),
"/org/freedesktop/DBus",
Some("org.freedesktop.DBus"),
"ReleaseName",
&well_known_name,
)
.await?
.body()
.deserialize::<ReleaseNameReply>()
.map(|r| r == ReleaseNameReply::Released)
}
pub fn is_bus(&self) -> bool {
#[cfg(feature = "p2p")]
{
self.inner.bus_conn
}
#[cfg(not(feature = "p2p"))]
{
true
}
}
#[cfg_attr(feature = "bus-impl", doc = "[`Connection::set_unique_name`].")]
#[cfg_attr(
not(feature = "bus-impl"),
doc = "`Connection::set_unique_name` (requires the `bus-impl` feature)."
)]
pub fn unique_name(&self) -> Option<&OwnedUniqueName> {
self.inner.unique_name.get()
}
#[cfg(feature = "bus-impl")]
pub fn set_unique_name<U>(&self, unique_name: U) -> Result<()>
where
U: TryInto<OwnedUniqueName>,
U::Error: Into<Error>,
{
let name = unique_name.try_into().map_err(Into::into)?;
self.set_unique_name_(name);
Ok(())
}
pub fn max_queued(&self) -> NonZeroUsize {
self.inner.msg_receiver.capacity()
}
pub fn set_max_queued(&mut self, max: NonZeroUsize) {
self.inner.msg_receiver.clone().set_capacity(max);
}
pub fn server_guid(&self) -> &OwnedGuid {
&self.inner.server_guid
}
#[doc(hidden)]
pub fn spawn<T>(
&self,
name: impl Into<Cow<'static, str>>,
future: impl Future<Output = T> + Send + 'static,
) -> impl traits::TaskHandle<T>
where
T: Send + 'static,
{
self.inner.runtime.spawn(name, future)
}
pub(crate) fn runtime(&self) -> &Runtime {
&self.inner.runtime
}
#[cfg(feature = "service")]
pub fn object_server(&self) -> &ObjectServer {
self.ensure_object_server(true)
}
#[cfg(feature = "service")]
pub(crate) fn ensure_object_server(&self, start: bool) -> &ObjectServer {
self.inner
.object_server
.get_or_init(move || self.setup_object_server(start, None))
}
#[cfg(feature = "service")]
fn setup_object_server(&self, start: bool, started_event: Option<Event>) -> ObjectServer {
if start {
self.start_object_server(started_event);
}
ObjectServer::new(self)
}
#[cfg(feature = "service")]
#[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
pub(crate) fn start_object_server(&self, started_event: Option<Event>) {
self.inner.object_server_dispatch_task.get_or_init(|| {
trace!("starting ObjectServer task");
let weak_conn = WeakConnection::from(self);
self.inner.runtime.spawn(
"obj_server_task",
async move {
let mut stream = match weak_conn.upgrade() {
Some(conn) => {
let mut builder = MatchRule::builder().msg_type(Type::MethodCall);
if let Some(unique_name) = conn.unique_name() {
builder = builder.destination(&**unique_name);
}
let rule = builder
.build()
.expect("a unique name is a valid destination");
match conn.add_match(rule.into(), None).await {
Ok(stream) => stream,
Err(e) => {
debug!("Failed to create message stream: {}", e);
return;
}
}
}
None => {
trace!("Connection is gone, stopping associated object server task");
return;
}
};
if let Some(started_event) = started_event {
started_event.notify(1);
}
trace!("waiting for incoming method call messages..");
while let Some(msg) = next(&mut stream).await.and_then(|m| {
if let Err(e) = &m {
debug!("Error while reading from object server stream: {:?}", e);
}
m.ok()
}) {
if let Some(conn) = weak_conn.upgrade() {
let hdr = msg.header();
if !conn.is_bus() {
match hdr.destination() {
Some(BusName::Unique(_)) | None => (),
Some(BusName::WellKnown(dest)) => {
let names = conn.inner.registered_names.lock().await;
if !names.is_empty() && !names.contains_key(dest) {
trace!(
"Got a method call for a different destination: {}",
dest
);
continue;
}
}
}
}
let server = conn.object_server();
if let Err(e) = server.dispatch_call(&msg, &hdr).await {
debug!(
"Error dispatching message. Message: {:?}, error: {:?}",
msg, e
);
}
} else {
trace!("Connection is gone, stopping associated object server task");
break;
}
}
}
.instrument(info_span!("obj_server_task")),
)
});
}
pub(crate) async fn add_match(
&self,
rule: OwnedMatchRule,
max_queued: Option<NonZeroUsize>,
) -> Result<Receiver<Result<Message>>> {
use std::collections::hash_map::Entry;
if self.inner.msg_senders.lock().await.is_empty() {
return Err(Error::InputOutput(Arc::new(io::Error::new(
io::ErrorKind::BrokenPipe,
"Socket reader task has errored out",
))));
}
let mut subscriptions = self.inner.subscriptions.lock().await;
let msg_type = rule.msg_type().unwrap_or(Type::Signal);
match subscriptions.entry(rule.clone()) {
Entry::Vacant(e) => {
let max_queued = max_queued.unwrap_or(DEFAULT_MAX_QUEUED);
let (sender, mut receiver) = channel(max_queued);
receiver.set_await_active(false);
if self.is_bus() && msg_type == Type::Signal {
self.call_method(
Some("org.freedesktop.DBus"),
"/org/freedesktop/DBus",
Some("org.freedesktop.DBus"),
"AddMatch",
&e.key(),
)
.await?;
}
e.insert((1, receiver.clone().deactivate()));
self.inner
.msg_senders
.lock()
.await
.insert(Some(rule), sender);
Ok(receiver)
}
Entry::Occupied(mut e) => {
let (num_subscriptions, receiver) = e.get_mut();
*num_subscriptions += 1;
if let Some(max_queued) = max_queued
&& max_queued > receiver.capacity()
{
receiver.set_capacity(max_queued);
}
Ok(receiver.activate_cloned())
}
}
}
pub(crate) async fn remove_match(&self, rule: OwnedMatchRule) -> Result<bool> {
use std::collections::hash_map::Entry;
let mut subscriptions = self.inner.subscriptions.lock().await;
let msg_type = rule.msg_type().unwrap_or(Type::Signal);
match subscriptions.entry(rule) {
Entry::Vacant(_) => Ok(false),
Entry::Occupied(mut e) => {
let rule = e.key().inner().clone();
e.get_mut().0 -= 1;
if e.get().0 == 0 {
if self.is_bus() && msg_type == Type::Signal {
self.call_method(
Some("org.freedesktop.DBus"),
"/org/freedesktop/DBus",
Some("org.freedesktop.DBus"),
"RemoveMatch",
&rule,
)
.await?;
}
e.remove();
self.inner
.msg_senders
.lock()
.await
.remove(&Some(rule.into()));
}
Ok(true)
}
}
}
pub(crate) fn queue_remove_match(&self, rule: OwnedMatchRule) {
let conn = self.clone();
let task_name = format!("Remove match `{}`", *rule);
let remove_match = async move {
let _ = conn.remove_match(rule).await;
}
.instrument(trace_span!("{}", task_name));
self.inner.runtime.spawn(task_name, remove_match).detach()
}
pub fn method_timeout(&self) -> Option<Duration> {
self.inner.method_timeout
}
pub(crate) async fn new(
auth: Authenticated,
#[allow(unused)] bus_connection: bool,
runtime: Runtime,
method_timeout: Option<Duration>,
) -> Result<Self> {
#[cfg(unix)]
let cap_unix_fd = auth.cap_unix_fd;
macro_rules! create_msg_broadcast_channel {
($size:expr) => {{
let (msg_sender, msg_receiver) = channel($size);
let mut msg_receiver = msg_receiver.deactivate();
msg_receiver.set_await_active(false);
(msg_sender, msg_receiver)
}};
}
let (msg_sender, msg_receiver) = create_msg_broadcast_channel!(DEFAULT_MAX_QUEUED);
let mut msg_senders = HashMap::new();
msg_senders.insert(None, msg_sender);
let msg_senders = Arc::new(Mutex::new(msg_senders));
let pending_method_calls = PendingMethodCalls::default();
let subscriptions = Mutex::new(HashMap::new());
let connection = Self {
inner: Arc::new(ConnectionInner {
socket_status: Arc::new(SocketStatus {
activity_event: Event::new(),
closed: AtomicBool::new(false),
closed_event: Event::new(),
}),
socket_write: Mutex::new(auth.socket_write),
server_guid: auth.server_guid,
#[cfg(unix)]
cap_unix_fd,
#[cfg(feature = "p2p")]
bus_conn: bus_connection,
unique_name: OnceLock::new(),
subscriptions,
#[cfg(feature = "service")]
object_server: OnceLock::new(),
#[cfg(feature = "service")]
object_server_dispatch_task: OnceLock::new(),
socket_reader_task: OnceLock::new(),
msg_senders,
pending_method_calls,
msg_receiver,
registered_names: Mutex::new(HashMap::new()),
runtime,
drop_event: Event::new(),
method_timeout,
credentials: OnceLock::new(),
}),
};
if let Some(unique_name) = auth.unique_name {
connection.set_unique_name_(unique_name);
}
Ok(connection)
}
pub async fn session() -> Result<Self> {
Builder::session().build().await
}
pub async fn system() -> Result<Self> {
Builder::system().build().await
}
pub fn monitor_activity(&self) -> ActivityListener {
ActivityListener(self.inner.socket_status.activity_event.listen())
}
pub fn is_closed(&self) -> bool {
self.inner.is_closed()
}
pub async fn closed(&self) {
loop {
let listener = self.inner.socket_status.closed_event.listen();
if self.inner.socket_status.closed.load(Ordering::Acquire) {
return;
}
listener.await;
}
}
pub async fn peer_creds(&self) -> io::Result<&Arc<ConnectionCredentials>> {
let mut socket_write = self.inner.socket_write.lock().await;
if let Some(creds) = self.inner.credentials.get() {
return Ok(creds);
}
self.inner
.credentials
.set(socket_write.peer_credentials().await.map(Arc::new)?)
.expect("credentials cache set more than once");
Ok(self
.inner
.credentials
.get()
.expect("credentials should have been set"))
}
pub async fn close(self) -> Result<()> {
self.inner.socket_status.activity_event.notify(usize::MAX);
let result = self
.inner
.socket_write
.lock()
.await
.close()
.await
.map_err(Into::into);
self.inner
.socket_status
.closed
.store(true, Ordering::Release);
self.inner.socket_status.closed_event.notify(usize::MAX);
result
}
pub async fn graceful_shutdown(self) {
let listener = self.inner.drop_event.listen();
drop(self);
listener.await;
}
pub(crate) fn init_socket_reader(
&self,
socket_read: Box<dyn socket::ReadHalf>,
already_read: Vec<u8>,
#[cfg(unix)] already_received_fds: Vec<std::os::fd::OwnedFd>,
#[cfg(feature = "bus-impl")] validate_bodies: bool,
) {
let inner = &self.inner;
inner
.socket_reader_task
.set(
SocketReader::new(
socket_read,
inner.msg_senders.clone(),
inner.pending_method_calls.clone(),
already_read,
#[cfg(unix)]
already_received_fds,
inner.socket_status.clone(),
#[cfg(feature = "bus-impl")]
validate_bodies,
)
.spawn(&inner.runtime),
)
.expect("Attempted to set `socket_reader_task` twice");
}
fn set_unique_name_(&self, name: OwnedUniqueName) {
self.inner
.unique_name
.set(name)
.expect("unique name already set");
}
}
#[derive(Debug, Clone)]
pub(crate) struct WeakConnection {
inner: Weak<ConnectionInner>,
}
impl WeakConnection {
pub fn upgrade(&self) -> Option<Connection> {
self.inner.upgrade().map(|inner| Connection { inner })
}
}
impl From<&Connection> for WeakConnection {
fn from(conn: &Connection) -> Self {
Self {
inner: Arc::downgrade(&conn.inner),
}
}
}
#[derive(Debug)]
enum NameStatus {
Owner(#[allow(unused)] Option<Task<()>>),
Queued(#[allow(unused)] Task<()>),
}
#[cfg(test)]
mod tests {
#[cfg(all(feature = "service", any(feature = "default-rt", feature = "tokio")))]
use super::*;
#[cfg(all(
feature = "proxy",
feature = "service",
any(feature = "default-rt", feature = "tokio")
))]
use crate::fdo::DBusProxy;
use crate::runtime::io::tests::RefusedPort;
#[cfg(all(feature = "service", any(feature = "default-rt", feature = "tokio")))]
use ntest::timeout;
#[cfg(all(feature = "service", any(feature = "default-rt", feature = "tokio")))]
use std::{pin::pin, time::Duration};
#[cfg(all(
feature = "proxy",
feature = "service",
any(feature = "default-rt", feature = "tokio")
))]
use test_log::test;
#[cfg(windows)]
#[test]
fn connect_autolaunch_session_bus() {
let addr =
crate::win32::autolaunch_bus_address().expect("Unable to get session bus address");
crate::block_on(async {
addr.connect(&crate::runtime::Runtime::default_for_build()?)
.await
})
.expect("Unable to connect to session bus");
}
#[cfg(target_os = "macos")]
#[test]
fn connect_launchd_session_bus() {
use crate::address::{Address, Transport, transport::Launchd};
crate::block_on(async {
let addr = Address::from(Transport::Launchd(Launchd::new(
"DBUS_LAUNCHD_SESSION_BUS_SOCKET",
)));
addr.connect(&crate::runtime::Runtime::default_for_build()?)
.await
})
.expect("Unable to connect to session bus");
}
#[test]
#[ntest::timeout(15000)]
fn a_session_connection_over_an_external_runtime() {
crate::utils::block_on(async {
let connection = super::Builder::session()
.runtime(crate::runtime::test_runtime::TestRuntime::new())
.build()
.await
.unwrap();
connection
.call_method(
Some("org.freedesktop.DBus"),
"/org/freedesktop/DBus",
Some("org.freedesktop.DBus.Peer"),
"Ping",
&(),
)
.await
.unwrap();
});
}
#[test]
#[ntest::timeout(15000)]
fn a_tcp_host_name_resolves_through_spawn_blocking() {
let runtime = crate::runtime::test_runtime::TestRuntime::new();
let refused = RefusedPort::for_host("localhost");
let address = format!("tcp:host=localhost,port={}", refused.port());
let error = connection_error(address.as_str(), runtime.clone());
assert!(
matches!(&error, crate::Error::Connection(e, _)
if e.kind() == std::io::ErrorKind::ConnectionRefused),
"connecting to a closed port reported `{error}` rather than a refused connection",
);
let addresses = std::net::ToSocketAddrs::to_socket_addrs(&("localhost", refused.port()))
.unwrap()
.count();
assert_eq!(
runtime.blocking_calls(),
addresses + 1,
"the host name was resolved without asking the runtime for blocking work",
);
}
#[test]
#[ntest::timeout(15000)]
fn a_tcp_literal_skips_resolution() {
let runtime = crate::runtime::test_runtime::TestRuntime::new();
let refused = RefusedPort::on(std::net::Ipv4Addr::LOCALHOST.into());
let address = format!("tcp:host=127.0.0.1,port={}", refused.port());
let error = connection_error(address.as_str(), runtime.clone());
assert!(
matches!(&error, crate::Error::Connection(e, _)
if e.kind() == std::io::ErrorKind::ConnectionRefused),
"connecting to a closed port reported `{error}` rather than a refused connection",
);
assert_eq!(
runtime.blocking_calls(),
1,
"an address that is already an IP address was handed to the resolver anyway",
);
}
fn connection_error(
address: &str,
runtime: crate::runtime::test_runtime::TestRuntime,
) -> crate::Error {
crate::utils::block_on(super::Builder::address(address).runtime(runtime).build())
.unwrap_err()
}
#[cfg(all(
feature = "proxy",
feature = "service",
any(feature = "default-rt", feature = "tokio")
))]
#[test]
#[timeout(15000)]
fn disconnect_on_drop() {
crate::utils::block_on(test_disconnect_on_drop());
}
#[cfg(all(
feature = "proxy",
feature = "service",
any(feature = "default-rt", feature = "tokio")
))]
async fn test_disconnect_on_drop() {
use futures_lite::StreamExt;
#[derive(Default)]
struct MyInterface {}
#[crate::interface(name = "dev.peelz.FooBar.Baz")]
impl MyInterface {
fn do_thing(&self) {}
}
let name = "dev.peelz.foobar";
let connection = Builder::session()
.name(name)
.serve_at("/dev/peelz/FooBar", MyInterface::default())
.build()
.await
.unwrap();
let connection2 = Connection::session().await.unwrap();
let dbus = DBusProxy::new(&connection2).await.unwrap();
let mut stream = dbus
.receive_name_owner_changed_with_args(&[(0, name), (2, "")])
.await
.unwrap();
drop(connection);
stream.next().await.unwrap();
let name_has_owner = dbus.name_has_owner(name.try_into().unwrap()).await.unwrap();
assert!(!name_has_owner);
}
#[cfg(all(feature = "service", any(feature = "default-rt", feature = "tokio")))]
#[tokio::test(start_paused = true)]
#[timeout(15000)]
async fn test_graceful_shutdown() {
let connection = Connection::session().await.unwrap();
let clone = connection.clone();
let mut shutdown = pin!(connection.graceful_shutdown());
tokio::select! {
_ = tokio::time::sleep(Duration::from_secs(u64::MAX)) => {},
_ = &mut shutdown => {
panic!("Graceful shutdown unexpectedly completed");
}
}
drop(clone);
shutdown.await;
struct GracefulInterface {
method_called: Event,
wait_before_return: Option<EventListener>,
announce_done: Event,
}
#[crate::interface(name = "dev.peelz.TestGracefulShutdown")]
impl GracefulInterface {
async fn do_thing(&mut self) {
self.method_called.notify(1);
if let Some(listener) = self.wait_before_return.take() {
listener.await;
}
self.announce_done.notify(1);
}
}
let method_called = Event::new();
let method_called_listener = method_called.listen();
let trigger_return = Event::new();
let wait_before_return = Some(trigger_return.listen());
let announce_done = Event::new();
let done_listener = announce_done.listen();
let interface = GracefulInterface {
method_called,
wait_before_return,
announce_done,
};
let name = "dev.peelz.TestGracefulShutdown";
let obj = "/dev/peelz/TestGracefulShutdown";
let connection = Builder::session()
.name(name)
.serve_at(obj, interface)
.build()
.await
.unwrap();
let client_conn = Connection::session().await.unwrap();
tokio::spawn(async move {
client_conn
.call_method(Some(name), obj, Some(name), "DoThing", &())
.await
.unwrap();
});
method_called_listener.await;
let mut shutdown = pin!(connection.graceful_shutdown());
tokio::select! {
_ = tokio::time::sleep(Duration::from_secs(u64::MAX)) => {},
_ = &mut shutdown => {
panic!("Graceful shutdown unexpectedly completed");
}
}
trigger_return.notify(1);
shutdown.await;
done_listener.await;
}
}
#[cfg(feature = "p2p")]
#[cfg(all(test, any(feature = "default-rt", feature = "tokio")))]
mod p2p_tests {
use crate::wire::{Endian, NATIVE_ENDIAN};
use futures_util::TryStreamExt;
use ntest::timeout;
use test_log::test;
use zruntime::Event;
use super::{Builder, Connection, socket};
#[cfg(all(unix, feature = "tokio", feature = "default-rt"))]
use crate::runtime::Runtime;
use crate::{Guid, Message, MessageStream, Result, conn::AuthMechanism};
async fn test_p2p(
server1: Connection,
client1: Connection,
server2: Connection,
client2: Connection,
) -> Result<()> {
let forward1 = {
let stream = MessageStream::from(server1.clone());
let sink = client2.clone();
stream.try_for_each(move |msg| {
let sink = sink.clone();
async move { sink.send(&msg).await }
})
};
let forward2 = {
let stream = MessageStream::from(client2.clone());
let sink = server1.clone();
stream.try_for_each(move |msg| {
let sink = sink.clone();
async move { sink.send(&msg).await }
})
};
let _forward_task = client1.runtime().spawn("forward_task", async move {
let _ = futures_util::try_join!(forward1, forward2);
});
let server_ready = Event::new();
let server_ready_listener = server_ready.listen();
let client_done = Event::new();
let client_done_listener = client_done.listen();
let server_future = async move {
let mut stream = MessageStream::from(&server2);
server_ready.notify(1);
let method = loop {
let m = stream.try_next().await?.unwrap();
if m.to_string() == "Method call Test" {
assert_eq!(m.body().deserialize::<u64>().unwrap(), 64);
break m;
}
};
server2
.emit_signal(None::<()>, "/", "org.zbus.p2p", "ASignalForYou", &())
.await?;
server2.reply(&method.header(), &("yay")).await?;
client_done_listener.await;
Ok(())
};
let client_future = async move {
let mut stream = MessageStream::from(&client1);
server_ready_listener.await;
let endian = match NATIVE_ENDIAN {
Endian::Little => Endian::Big,
Endian::Big => Endian::Little,
};
let method = Message::method_call("/", "Test")
.interface("org.zbus.p2p")
.endian(endian)
.build(&64u64)?;
client1.send(&method).await?;
let m = stream.try_next().await?.unwrap();
client_done.notify(1);
assert_eq!(m.to_string(), "Signal ASignalForYou");
let reply = stream.try_next().await?.unwrap();
assert_eq!(reply.to_string(), "Method return");
assert_eq!(Endian::from(reply.primary_header().endian_sig()), endian);
reply.body().deserialize::<String>()
};
let (val, _) = futures_util::try_join!(client_future, server_future,)?;
assert_eq!(val, "yay");
Ok(())
}
#[test]
#[timeout(15000)]
fn tcp_p2p() {
crate::utils::block_on(test_tcp_p2p()).unwrap();
}
async fn test_tcp_p2p() -> Result<()> {
let (server1, client1) = tcp_p2p_pipe().await?;
let (server2, client2) = tcp_p2p_pipe().await?;
test_p2p(server1, client1, server2, client2).await
}
async fn tcp_p2p_pipe() -> Result<(Connection, Connection)> {
let guid = Guid::generate();
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let p1 = std::net::TcpStream::connect(addr).unwrap();
let p0 = listener.incoming().next().unwrap().unwrap();
futures_util::try_join!(
Builder::tcp_stream(p0)
.server(guid)
.p2p()
.auth_mechanism(AuthMechanism::Anonymous)
.build(),
Builder::tcp_stream(p1).p2p().build(),
)
}
#[cfg(unix)]
#[test]
#[timeout(15000)]
fn unix_p2p() {
crate::utils::block_on(test_unix_p2p()).unwrap();
}
#[cfg(unix)]
async fn test_unix_p2p() -> Result<()> {
let (server1, client1) = unix_p2p_pipe().await?;
let (server2, client2) = unix_p2p_pipe().await?;
test_p2p(server1, client1, server2, client2).await
}
#[cfg(unix)]
async fn unix_p2p_pipe() -> Result<(Connection, Connection)> {
use std::os::unix::net::UnixStream;
let guid = Guid::generate();
let (p0, p1) = UnixStream::pair().unwrap();
futures_util::try_join!(
Builder::unix_stream(p1).p2p().build(),
Builder::unix_stream(p0).server(guid).p2p().build(),
)
}
#[cfg(all(unix, feature = "tokio", feature = "default-rt"))]
#[test]
#[timeout(15000)]
fn unix_p2p_zruntime_backend() {
use futures_lite::FutureExt;
use std::time::Duration;
futures_lite::future::block_on(async {
let (server1, client1) = unix_p2p_pipe().await.unwrap();
assert!(matches!(server1.runtime(), Runtime::ZRuntime(_)));
assert!(matches!(client1.runtime(), Runtime::ZRuntime(_)));
server1
.runtime()
.spawn("a task outside every Tokio context", async {
assert!(
tokio::runtime::Handle::try_current().is_err(),
"the task unexpectedly entered a Tokio runtime",
);
})
.or(async {
client1.runtime().sleep(Duration::from_secs(5)).await;
panic!("the spawned task did not run");
})
.await
.unwrap();
let (server2, client2) = unix_p2p_pipe().await.unwrap();
test_p2p(server1, client1, server2, client2).await.unwrap();
});
}
#[test]
#[timeout(15000)]
fn channel_pair() {
crate::utils::block_on(test_channel_pair()).unwrap();
}
async fn test_channel_pair() -> Result<()> {
let (server1, client1) = create_channel_pair().await;
let (server2, client2) = create_channel_pair().await;
test_p2p(server1, client1, server2, client2).await
}
async fn create_channel_pair() -> (Connection, Connection) {
let (a, b) = socket::Channel::pair();
let guid = crate::Guid::generate();
let conn1 = Builder::authenticated_socket(a, guid.clone())
.p2p()
.build()
.await
.unwrap();
let conn2 = Builder::authenticated_socket(b, guid)
.p2p()
.build()
.await
.unwrap();
(conn1, conn2)
}
#[cfg(unix)]
#[test]
#[timeout(15000)]
fn send_all() {
crate::utils::block_on(async {
let (server, client) = unix_p2p_pipe().await?;
assert_sent_all(&client, &server).await?;
let (server, client) = create_channel_pair().await;
assert_sent_all(&client, &server).await
})
.unwrap();
}
#[cfg(unix)]
async fn assert_sent_all(sender: &Connection, receiver: &Connection) -> Result<()> {
let mut stream = MessageStream::from(receiver);
let stdout = std::io::stdout();
let msgs = (0..20u32)
.map(|i| {
let builder = Message::signal("/", "org.zbus.p2p", "Batch");
if i % 7 == 3 {
builder.build(&(i, crate::Fd::from(&stdout)))
} else {
builder.build(&i)
}
})
.collect::<Result<Vec<_>>>()?;
sender.send_all(&msgs).await?;
for sent in &msgs {
let received = stream.try_next().await?.unwrap();
assert_eq!(
received.primary_header().serial_num(),
sent.primary_header().serial_num(),
);
assert_eq!(received.data().fds().len(), sent.data().fds().len());
}
Ok(())
}
#[cfg(unix)]
#[test]
#[timeout(15000)]
fn send_all_of_fds_without_fd_passing() {
crate::utils::block_on(async {
let (server, client) = tcp_p2p_pipe().await?;
let mut stream = MessageStream::from(&server);
let stdout = std::io::stdout();
let msgs = [
Message::signal("/", "org.zbus.p2p", "First").build(&())?,
Message::signal("/", "org.zbus.p2p", "WithFd").build(&crate::Fd::from(&stdout))?,
];
let error = client.send_all(&msgs).await.unwrap_err();
assert!(
matches!(error, crate::Error::Unsupported),
"the messages failed with `{error}` rather than as unsupported",
);
let next = Message::signal("/", "org.zbus.p2p", "Next").build(&())?;
client.send_all(std::slice::from_ref(&next)).await?;
let received = stream.try_next().await?.unwrap();
assert_eq!(
received.primary_header().serial_num(),
next.primary_header().serial_num(),
);
Result::Ok(())
})
.unwrap();
}
#[test]
#[timeout(15000)]
fn send_all_hands_the_messages_over_at_once() {
#[derive(Debug)]
struct AtOnce(socket::channel::Writer);
#[async_trait::async_trait]
impl socket::WriteHalf for AtOnce {
async fn send_message(&mut self, _msg: &Message) -> Result<()> {
panic!("a message was handed over on its own");
}
async fn send_messages(&mut self, msgs: &[Message]) -> Result<()> {
for msg in msgs {
self.0.send_message(msg).await?;
}
Ok(())
}
async fn close(&mut self) -> std::io::Result<()> {
self.0.close().await
}
}
crate::utils::block_on(async {
let (a, b) = socket::Channel::pair();
let (read, write) = socket::Socket::split(a).take();
let a = socket::Split::new(
Box::new(read) as Box<dyn socket::ReadHalf>,
Box::new(AtOnce(write)) as Box<dyn socket::WriteHalf>,
);
let guid = Guid::generate();
let sender = Builder::authenticated_socket(a, guid.clone())
.p2p()
.build()
.await?;
let receiver = Builder::authenticated_socket(b, guid).p2p().build().await?;
let mut stream = MessageStream::from(&receiver);
let msgs = [
Message::signal("/", "org.zbus.p2p", "First").build(&())?,
Message::signal("/", "org.zbus.p2p", "Second").build(&())?,
];
sender.send_all(&msgs).await?;
for sent in &msgs {
let received = stream.try_next().await?.unwrap();
assert_eq!(
received.primary_header().serial_num(),
sent.primary_header().serial_num(),
);
}
Result::Ok(())
})
.unwrap();
}
}