| 1 | //! Connection base / building block. |
| 2 | //! |
| 3 | //! Contains some helper structs and traits common to all Connection types.- |
| 4 | |
| 5 | use crate::{Message, to_c_str, c_str_to_slice, MessageType}; |
| 6 | use crate::message::MatchRule; |
| 7 | |
| 8 | #[cfg (not(feature = "native-channel" ))] |
| 9 | mod ffichannel; |
| 10 | #[cfg (not(feature = "native-channel" ))] |
| 11 | pub use ffichannel::Channel; |
| 12 | |
| 13 | #[cfg (feature = "native-channel" )] |
| 14 | mod nativechannel; |
| 15 | #[cfg (feature = "native-channel" )] |
| 16 | pub use nativechannel::Channel; |
| 17 | |
| 18 | |
| 19 | /// Which bus to connect to |
| 20 | #[derive (Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] |
| 21 | pub enum BusType { |
| 22 | /// The Session bus - local to every logged in session |
| 23 | Session = ffi::DBusBusType::Session as isize, |
| 24 | /// The system wide bus |
| 25 | System = ffi::DBusBusType::System as isize, |
| 26 | /// The bus that started us, if any |
| 27 | Starter = ffi::DBusBusType::Starter as isize, |
| 28 | } |
| 29 | |
| 30 | /// Platform-specific file descriptor type |
| 31 | #[cfg (unix)] |
| 32 | pub type WatchFd = std::os::unix::io::RawFd; |
| 33 | |
| 34 | /// Platform-specific file descriptor type |
| 35 | #[cfg (windows)] |
| 36 | pub type WatchFd = std::os::windows::io::RawSocket; |
| 37 | |
| 38 | #[derive (Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] |
| 39 | /// A file descriptor, and an indication whether it should be read from, written to, or both. |
| 40 | pub struct Watch { |
| 41 | /// File descriptor |
| 42 | pub fd: WatchFd, |
| 43 | /// True if wakeup should happen when the file descriptor is ready for reading |
| 44 | pub read: bool, |
| 45 | /// True if wakeup should happen when the file descriptor is ready for writing |
| 46 | pub write: bool, |
| 47 | } |
| 48 | |
| 49 | /// Abstraction over different connections that send data |
| 50 | pub trait Sender { |
| 51 | /// Schedules a message for sending. |
| 52 | /// |
| 53 | /// Returns a serial number than can be used to match against a reply. |
| 54 | fn send(&self, msg: Message) -> Result<u32, ()>; |
| 55 | } |
| 56 | |
| 57 | /// Use in case you don't want the send the message, but just collect it instead. |
| 58 | impl Sender for std::cell::RefCell<Vec<Message>> { |
| 59 | fn send(&self, msg: Message) -> Result<u32, ()> { |
| 60 | self.borrow_mut().push(msg); |
| 61 | Ok(0) |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | /// Use in case you don't want the send the message, but just collect it instead. |
| 66 | impl Sender for std::sync::Mutex<Vec<Message>> { |
| 67 | fn send(&self, msg: Message) -> Result<u32, ()> { |
| 68 | self.lock().unwrap().push(msg); |
| 69 | Ok(0) |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | /// Token used to identify a callback in the MatchingReceiver trait |
| 74 | #[derive (Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] |
| 75 | pub struct Token(pub usize); |
| 76 | |
| 77 | /// Abstraction over different connections that receive data |
| 78 | pub trait MatchingReceiver { |
| 79 | /// Type of callback |
| 80 | type F; |
| 81 | /// Add a callback to be called in case a message matches. |
| 82 | /// |
| 83 | /// Returns an id that can be used to remove the callback. |
| 84 | fn start_receive(&self, m: MatchRule<'static>, f: Self::F) -> Token; |
| 85 | /// Remove a previously added callback. |
| 86 | fn stop_receive(&self, id: Token) -> Option<(MatchRule<'static>, Self::F)>; |
| 87 | } |
| 88 | |
| 89 | impl Sender for Channel { |
| 90 | fn send(&self, msg: Message) -> Result<u32, ()> { Channel::send(self, msg) } |
| 91 | } |
| 92 | |
| 93 | /// Handles what we need to be a good D-Bus citizen. |
| 94 | /// |
| 95 | /// Call this if you have not handled the message yourself: |
| 96 | /// * It handles calls to org.freedesktop.DBus.Peer. |
| 97 | /// * For other method calls, it sends an error reply back that the method was unknown. |
| 98 | pub fn default_reply(m: &Message) -> Option<Message> { |
| 99 | peer(&m).or_else(|| unknown_method(&m)) |
| 100 | } |
| 101 | |
| 102 | /// Replies if this is a call to org.freedesktop.DBus.Peer, otherwise returns None. |
| 103 | fn peer(m: &Message) -> Option<Message> { |
| 104 | if let Some(intf: Interface<'_>) = m.interface() { |
| 105 | if &*intf != "org.freedesktop.DBus.Peer" { return None; } |
| 106 | if let Some(method: Member<'_>) = m.member() { |
| 107 | if &*method == "Ping" { return Some(m.method_return()) } |
| 108 | if &*method == "GetMachineId" { |
| 109 | let mut r: Message = m.method_return(); |
| 110 | unsafe { |
| 111 | let id: *mut i8 = ffi::dbus_get_local_machine_id(); |
| 112 | if !id.is_null() { |
| 113 | r = r.append1(c_str_to_slice(&(id as *const _)).unwrap()); |
| 114 | ffi::dbus_free(memory:id as *mut _); |
| 115 | return Some(r) |
| 116 | } |
| 117 | } |
| 118 | return Some(m.error(&"org.freedesktop.DBus.Error.Failed" .into(), &to_c_str("Failed to retreive UUID" ))) |
| 119 | } |
| 120 | } |
| 121 | Some(m.error(&"org.freedesktop.DBus.Error.UnknownMethod" .into(), &to_c_str("Method does not exist" ))) |
| 122 | } else { None } |
| 123 | } |
| 124 | |
| 125 | /// For method calls, it replies that the method was unknown, otherwise returns None. |
| 126 | fn unknown_method(m: &Message) -> Option<Message> { |
| 127 | if m.msg_type() != MessageType::MethodCall { return None; } |
| 128 | // if m.get_no_reply() { return None; } // The reference implementation does not do this? |
| 129 | Some(m.error(&"org.freedesktop.DBus.Error.UnknownMethod" .into(), &to_c_str("Path, Interface, or Method does not exist" ))) |
| 130 | } |
| 131 | |
| 132 | #[test ] |
| 133 | fn test_channel_send_sync() { |
| 134 | fn is_send<T: Send>(_: &T) {} |
| 135 | fn is_sync<T: Sync>(_: &T) {} |
| 136 | let c = Channel::get_private(BusType::Session).unwrap(); |
| 137 | is_send(&c); |
| 138 | is_sync(&c); |
| 139 | } |
| 140 | |
| 141 | #[test ] |
| 142 | fn channel_simple_test() { |
| 143 | let mut c = Channel::get_private(BusType::Session).unwrap(); |
| 144 | assert!(c.is_connected()); |
| 145 | c.set_watch_enabled(true); |
| 146 | let fd = c.watch(); |
| 147 | println!("{:?}" , fd); |
| 148 | let m = Message::new_method_call("org.freedesktop.DBus" , "/" , "org.freedesktop.DBus" , "ListNames" ).unwrap(); |
| 149 | let reply = c.send(m).unwrap(); |
| 150 | let my_name = c.unique_name().unwrap(); |
| 151 | loop { |
| 152 | while let Some(mut msg) = c.pop_message() { |
| 153 | println!("{:?}" , msg); |
| 154 | if msg.get_reply_serial() == Some(reply) { |
| 155 | let r = msg.as_result().unwrap(); |
| 156 | let z: crate::arg::Array<&str, _> = r.get1().unwrap(); |
| 157 | for n in z { |
| 158 | println!("{}" , n); |
| 159 | if n == my_name { return; } // Hooray, we found ourselves! |
| 160 | } |
| 161 | assert!(false); |
| 162 | } else if let Some(r) = default_reply(&msg) { |
| 163 | c.send(r).unwrap(); |
| 164 | } |
| 165 | } |
| 166 | c.read_write(Some(std::time::Duration::from_millis(100))).unwrap(); |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | #[test ] |
| 171 | fn test_bus_type_is_compatible_with_set() { |
| 172 | use std::collections::HashSet; |
| 173 | |
| 174 | let mut set: HashSet<BusType> = HashSet::new(); |
| 175 | set.insert(BusType::Starter); |
| 176 | set.insert(BusType::Starter); |
| 177 | |
| 178 | assert_eq!(set.len(), 1); |
| 179 | assert!(!set.contains(&BusType::Session)); |
| 180 | assert!(!set.contains(&BusType::System)); |
| 181 | assert!(set.contains(&BusType::Starter)); |
| 182 | } |
| 183 | |
| 184 | |
| 185 | #[test ] |
| 186 | fn watchmap() { |
| 187 | let mut c = Channel::get_private(BusType::Session).unwrap(); |
| 188 | c.set_watch_enabled(true); |
| 189 | let w = c.watch(); |
| 190 | assert_eq!(w.write, false); |
| 191 | assert_eq!(w.read, true); |
| 192 | c.set_watch_enabled(false); |
| 193 | println!("{:?}" , w); |
| 194 | c.set_watch_enabled(true); |
| 195 | } |
| 196 | |