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 | |