1 extern crate proc_macro;
2 
3 use quote::{format_ident, quote, ToTokens};
4 
5 use std::fs::File;
6 use std::io::Write;
7 use std::path::Path;
8 
9 use syn::parse::Parser;
10 use syn::punctuated::Punctuated;
11 use syn::token::Comma;
12 use syn::{Expr, FnArg, ImplItem, ItemImpl, ItemStruct, Meta, Pat, ReturnType, Type};
13 
14 use crate::proc_macro::TokenStream;
15 
debug_output_to_file(gen: &proc_macro2::TokenStream, filename: String)16 fn debug_output_to_file(gen: &proc_macro2::TokenStream, filename: String) {
17     let path = Path::new(filename.as_str());
18     let mut file = File::create(&path).unwrap();
19     file.write_all(gen.to_string().as_bytes()).unwrap();
20 }
21 
22 /// Marks a method to be projected to a D-Bus method and specifies the D-Bus method name.
23 #[proc_macro_attribute]
dbus_method(_attr: TokenStream, item: TokenStream) -> TokenStream24 pub fn dbus_method(_attr: TokenStream, item: TokenStream) -> TokenStream {
25     let ori_item: proc_macro2::TokenStream = item.clone().into();
26     let gen = quote! {
27         #[allow(unused_variables)]
28         #ori_item
29     };
30     gen.into()
31 }
32 
33 /// Generates a function to export a Rust object to D-Bus.
34 #[proc_macro_attribute]
generate_dbus_exporter(attr: TokenStream, item: TokenStream) -> TokenStream35 pub fn generate_dbus_exporter(attr: TokenStream, item: TokenStream) -> TokenStream {
36     let ori_item: proc_macro2::TokenStream = item.clone().into();
37 
38     let args = Punctuated::<Expr, Comma>::parse_separated_nonempty.parse(attr.clone()).unwrap();
39 
40     let fn_ident = if let Expr::Path(p) = &args[0] {
41         p.path.get_ident().unwrap()
42     } else {
43         panic!("function name must be specified");
44     };
45 
46     let dbus_iface_name = if let Expr::Lit(lit) = &args[1] {
47         lit
48     } else {
49         panic!("D-Bus interface name must be specified");
50     };
51 
52     let ast: ItemImpl = syn::parse(item.clone()).unwrap();
53     let api_iface_ident = ast.trait_.unwrap().1.to_token_stream();
54 
55     let mut register_methods = quote! {};
56 
57     for item in ast.items {
58         if let ImplItem::Method(method) = item {
59             if method.attrs.len() != 1 {
60                 continue;
61             }
62 
63             let attr = &method.attrs[0];
64             if !attr.path.get_ident().unwrap().to_string().eq("dbus_method") {
65                 continue;
66             }
67 
68             let attr_args = attr.parse_meta().unwrap();
69             let dbus_method_name = if let Meta::List(meta_list) = attr_args {
70                 Some(meta_list.nested[0].clone())
71             } else {
72                 None
73             };
74 
75             if dbus_method_name.is_none() {
76                 continue;
77             }
78 
79             let method_name = method.sig.ident;
80 
81             let mut arg_names = quote! {};
82             let mut method_args = quote! {};
83             let mut make_args = quote! {};
84             let mut dbus_input_vars = quote! {};
85             let mut dbus_input_types = quote! {};
86 
87             for input in method.sig.inputs {
88                 if let FnArg::Typed(ref typed) = input {
89                     let arg_type = &typed.ty;
90                     if let Pat::Ident(pat_ident) = &*typed.pat {
91                         let ident = pat_ident.ident.clone();
92                         let mut dbus_input_ident = ident.to_string();
93                         dbus_input_ident.push_str("_");
94                         let dbus_input_arg = format_ident!("{}", dbus_input_ident);
95                         let ident_string = ident.to_string();
96 
97                         arg_names = quote! {
98                             #arg_names #ident_string,
99                         };
100 
101                         method_args = quote! {
102                             #method_args #ident,
103                         };
104 
105                         dbus_input_vars = quote! {
106                             #dbus_input_vars #dbus_input_arg,
107                         };
108 
109                         dbus_input_types = quote! {
110                             #dbus_input_types
111                             <#arg_type as DBusArg>::DBusType,
112                         };
113 
114                         make_args = quote! {
115                             #make_args
116                             let #ident = <#arg_type as DBusArg>::from_dbus(
117                                 #dbus_input_arg,
118                                 conn_clone.clone(),
119                                 ctx.message().sender().unwrap().into_static(),
120                                 dc_watcher_clone.clone(),
121                             );
122 
123                             if let Result::Err(e) = #ident {
124                                 return Err(dbus_crossroads::MethodErr::invalid_arg(
125                                     e.to_string().as_str()
126                                 ));
127                             }
128 
129                             let #ident = #ident.unwrap();
130                         };
131                     }
132                 }
133             }
134 
135             let dbus_input_args = quote! {
136                 (#dbus_input_vars): (#dbus_input_types)
137             };
138 
139             let mut output_names = quote! {};
140             let mut output_type = quote! {};
141             let mut ret = quote! {Ok(())};
142             if let ReturnType::Type(_, t) = method.sig.output {
143                 output_type = quote! {#t,};
144                 ret = quote! {Ok((ret,))};
145                 output_names = quote! { "out", };
146             }
147 
148             register_methods = quote! {
149                 #register_methods
150 
151                 let conn_clone = conn.clone();
152                 let dc_watcher_clone = disconnect_watcher.clone();
153                 let handle_method = move |ctx: &mut dbus_crossroads::Context,
154                                           obj: &mut ObjType,
155                                           #dbus_input_args |
156                       -> Result<(#output_type), dbus_crossroads::MethodErr> {
157                     #make_args
158                     let ret = obj.lock().unwrap().#method_name(#method_args);
159                     #ret
160                 };
161                 ibuilder.method(
162                     #dbus_method_name,
163                     (#arg_names),
164                     (#output_names),
165                     handle_method,
166                 );
167             };
168         }
169     }
170 
171     let gen = quote! {
172         #ori_item
173 
174         type ObjType = std::sync::Arc<std::sync::Mutex<dyn #api_iface_ident + Send>>;
175 
176         pub fn #fn_ident(
177             path: &'static str,
178             conn: std::sync::Arc<SyncConnection>,
179             cr: &mut dbus_crossroads::Crossroads,
180             obj: ObjType,
181             disconnect_watcher: Arc<Mutex<dbus_projection::DisconnectWatcher>>,
182         ) {
183             fn get_iface_token(
184                 conn: Arc<SyncConnection>,
185                 cr: &mut dbus_crossroads::Crossroads,
186                 disconnect_watcher: std::sync::Arc<std::sync::Mutex<dbus_projection::DisconnectWatcher>>,
187             ) -> dbus_crossroads::IfaceToken<ObjType> {
188                 cr.register(#dbus_iface_name, |ibuilder| {
189                     #register_methods
190                 })
191             }
192 
193             let iface_token = get_iface_token(conn, cr, disconnect_watcher);
194             cr.insert(path, &[iface_token], obj);
195         }
196     };
197 
198     // TODO: Have a switch to turn on/off this debug.
199     debug_output_to_file(&gen, format!("/tmp/out-{}.rs", fn_ident.to_string()));
200 
201     gen.into()
202 }
203 
copy_without_attributes(item: &TokenStream) -> TokenStream204 fn copy_without_attributes(item: &TokenStream) -> TokenStream {
205     let mut ast: ItemStruct = syn::parse(item.clone()).unwrap();
206     for field in &mut ast.fields {
207         field.attrs.clear();
208     }
209 
210     let gen = quote! {
211         #ast
212     };
213 
214     gen.into()
215 }
216 
217 /// Generates a DBusArg implementation to transform Rust plain structs to a D-Bus data structure.
218 // TODO: Support more data types of struct fields (currently only supports integers and enums).
219 #[proc_macro_attribute]
dbus_propmap(attr: TokenStream, item: TokenStream) -> TokenStream220 pub fn dbus_propmap(attr: TokenStream, item: TokenStream) -> TokenStream {
221     let ori_item: proc_macro2::TokenStream = copy_without_attributes(&item).into();
222 
223     let ast: ItemStruct = syn::parse(item.clone()).unwrap();
224 
225     let args = Punctuated::<Expr, Comma>::parse_separated_nonempty.parse(attr.clone()).unwrap();
226     let struct_ident =
227         if let Expr::Path(p) = &args[0] { p.path.get_ident().unwrap().clone() } else { ast.ident };
228 
229     let struct_str = struct_ident.to_string();
230 
231     let mut make_fields = quote! {};
232     let mut field_idents = quote! {};
233 
234     let mut insert_map_fields = quote! {};
235     for field in ast.fields {
236         let field_ident = field.ident;
237 
238         if field_ident.is_none() {
239             continue;
240         }
241 
242         let field_str = field_ident.as_ref().unwrap().clone().to_string();
243 
244         let propmap_attr = field.attrs.clone().into_iter().find(|x| {
245             let ident = x.path.get_ident();
246 
247             if ident.is_none() {
248                 return false;
249             }
250 
251             ident.unwrap().to_string().eq("dbus_propmap_field_propmap")
252         });
253 
254         let field_type_str = if let Type::Path(t) = field.ty {
255             t.path.get_ident().unwrap().to_string()
256         } else {
257             String::from("")
258         };
259 
260         let field_type_ident = format_ident!("{}", field_type_str);
261 
262         field_idents = quote! {
263             #field_idents #field_ident,
264         };
265 
266         let make_field = if !propmap_attr.is_none() {
267             quote! {
268                 let mut map: dbus::arg::PropMap = HashMap::new();
269 
270                 let mut iter = #field_ident.as_iter().unwrap();
271                 let mut iter = iter.next().unwrap().as_iter().unwrap();
272 
273                 let mut i1 = iter.next();
274                 let mut i2 = iter.next();
275                 while !i1.is_none() && !i2.is_none() {
276                     let k = i1.unwrap().as_str().unwrap().to_string();
277                     let v = dbus::arg::Variant(i2.unwrap().box_clone());
278                     map.insert(k, v);
279                     i1 = iter.next();
280                     i2 = iter.next();
281                 }
282 
283                 let #field_ident = #field_type_ident::from_dbus(
284                     map,
285                     conn.clone(),
286                     remote.clone(),
287                     disconnect_watcher.clone(),
288                 )?;
289             }
290         } else {
291             quote! {
292                 match #field_ident.arg_type() {
293                     dbus::arg::ArgType::Variant => {}
294                     _ => {
295                         return Err(Box::new(DBusArgError::new(String::from(format!(
296                             "{}.{} must be a variant",
297                             #struct_str, #field_str
298                         )))));
299                     }
300                 };
301                 let #field_ident = #field_ident.as_static_inner(0).unwrap();
302                 let any = #field_ident.as_any();
303                 if !any.is::<<#field_type_ident as DBusArg>::DBusType>() {
304                     return Err(Box::new(DBusArgError::new(String::from(format!(
305                         "{}.{} type does not match: expected {}, found {}",
306                         #struct_str,
307                         #field_str,
308                         std::any::type_name::<<#field_type_ident as DBusArg>::DBusType>(),
309                         #field_ident.arg_type().as_str(),
310                     )))));
311                 }
312                 let #field_ident = *any.downcast_ref::<<#field_type_ident as DBusArg>::DBusType>().unwrap();
313                 let #field_ident = #field_type_ident::from_dbus(
314                     #field_ident,
315                     conn.clone(),
316                     remote.clone(),
317                     disconnect_watcher.clone(),
318                 )?;
319             }
320         };
321 
322         make_fields = quote! {
323             #make_fields
324 
325             let #field_ident = match data.get(#field_str) {
326                 Some(data) => data,
327                 None => {
328                     return Err(Box::new(DBusArgError::new(String::from(format!(
329                         "{}.{} is required",
330                         #struct_str, #field_str
331                     )))));
332                 }
333             };
334             #make_field
335         };
336 
337         insert_map_fields = quote! {
338             #insert_map_fields
339             let field_data = DBusArg::to_dbus(data.#field_ident)?;
340             map.insert(String::from(#field_str), dbus::arg::Variant(Box::new(field_data)));
341         };
342     }
343 
344     let gen = quote! {
345         #[allow(dead_code)]
346         #ori_item
347 
348         impl DBusArg for #struct_ident {
349             type DBusType = dbus::arg::PropMap;
350 
351             fn from_dbus(
352                 data: dbus::arg::PropMap,
353                 conn: Arc<SyncConnection>,
354                 remote: BusName<'static>,
355                 disconnect_watcher: Arc<Mutex<dbus_projection::DisconnectWatcher>>,
356             ) -> Result<#struct_ident, Box<dyn Error>> {
357                 #make_fields
358 
359                 return Ok(#struct_ident {
360                     #field_idents
361                     ..Default::default()
362                 });
363             }
364 
365             fn to_dbus(data: #struct_ident) -> Result<dbus::arg::PropMap, Box<dyn Error>> {
366                 let mut map: dbus::arg::PropMap = HashMap::new();
367                 #insert_map_fields
368                 return Ok(map);
369             }
370         }
371     };
372 
373     // TODO: Have a switch to turn this debug off/on.
374     debug_output_to_file(&gen, format!("/tmp/out-{}.rs", struct_ident.to_string()));
375 
376     gen.into()
377 }
378 
379 /// Generates a DBusArg implementation of a Remote RPC proxy object.
380 #[proc_macro_attribute]
dbus_proxy_obj(attr: TokenStream, item: TokenStream) -> TokenStream381 pub fn dbus_proxy_obj(attr: TokenStream, item: TokenStream) -> TokenStream {
382     let ori_item: proc_macro2::TokenStream = item.clone().into();
383 
384     let args = Punctuated::<Expr, Comma>::parse_separated_nonempty.parse(attr.clone()).unwrap();
385 
386     let struct_ident = if let Expr::Path(p) = &args[0] {
387         p.path.get_ident().unwrap()
388     } else {
389         panic!("struct name must be specified");
390     };
391 
392     let dbus_iface_name = if let Expr::Lit(lit) = &args[1] {
393         lit
394     } else {
395         panic!("D-Bus interface name must be specified");
396     };
397 
398     let mut method_impls = quote! {};
399 
400     let ast: ItemImpl = syn::parse(item.clone()).unwrap();
401     let self_ty = ast.self_ty;
402     let trait_ = ast.trait_.unwrap().1;
403 
404     for item in ast.items {
405         if let ImplItem::Method(method) = item {
406             if method.attrs.len() != 1 {
407                 continue;
408             }
409 
410             let attr = &method.attrs[0];
411             if !attr.path.get_ident().unwrap().to_string().eq("dbus_method") {
412                 continue;
413             }
414 
415             let attr_args = attr.parse_meta().unwrap();
416             let dbus_method_name = if let Meta::List(meta_list) = attr_args {
417                 Some(meta_list.nested[0].clone())
418             } else {
419                 None
420             };
421 
422             if dbus_method_name.is_none() {
423                 continue;
424             }
425 
426             let method_sig = method.sig.clone();
427 
428             let mut method_args = quote! {};
429 
430             for input in method.sig.inputs {
431                 if let FnArg::Typed(ref typed) = input {
432                     if let Pat::Ident(pat_ident) = &*typed.pat {
433                         let ident = pat_ident.ident.clone();
434 
435                         method_args = quote! {
436                             #method_args DBusArg::to_dbus(#ident).unwrap(),
437                         };
438                     }
439                 }
440             }
441 
442             method_impls = quote! {
443                 #method_impls
444                 #[allow(unused_variables)]
445                 #method_sig {
446                     let remote = self.remote.clone();
447                     let objpath = self.objpath.clone();
448                     let conn = self.conn.clone();
449                     bt_topshim::topstack::get_runtime().spawn(async move {
450                         let proxy = dbus::nonblock::Proxy::new(
451                             remote,
452                             objpath,
453                             std::time::Duration::from_secs(2),
454                             conn,
455                         );
456                         let future: dbus::nonblock::MethodReply<()> = proxy.method_call(
457                             #dbus_iface_name,
458                             #dbus_method_name,
459                             (#method_args),
460                         );
461                         let _result = future.await;
462                     });
463                 }
464             };
465         }
466     }
467 
468     let gen = quote! {
469         #ori_item
470 
471         impl RPCProxy for #self_ty {
472             fn register_disconnect(&mut self, _disconnect_callback: Box<dyn Fn() + Send>) {}
473         }
474 
475         struct #struct_ident {
476             conn: Arc<SyncConnection>,
477             remote: BusName<'static>,
478             objpath: Path<'static>,
479             disconnect_watcher: Arc<Mutex<DisconnectWatcher>>,
480         }
481 
482         impl #trait_ for #struct_ident {
483             #method_impls
484         }
485 
486         impl RPCProxy for #struct_ident {
487             fn register_disconnect(&mut self, disconnect_callback: Box<dyn Fn() + Send>) {
488                 self.disconnect_watcher.lock().unwrap().add(self.remote.clone(), disconnect_callback);
489             }
490         }
491 
492         impl DBusArg for Box<dyn #trait_ + Send> {
493             type DBusType = Path<'static>;
494 
495             fn from_dbus(
496                 objpath: Path<'static>,
497                 conn: Arc<SyncConnection>,
498                 remote: BusName<'static>,
499                 disconnect_watcher: Arc<Mutex<DisconnectWatcher>>,
500             ) -> Result<Box<dyn #trait_ + Send>, Box<dyn Error>> {
501                 Ok(Box::new(#struct_ident { conn, remote, objpath, disconnect_watcher }))
502             }
503 
504             fn to_dbus(_data: Box<dyn #trait_ + Send>) -> Result<Path<'static>, Box<dyn Error>> {
505                 // This impl represents a remote DBus object, so `to_dbus` does not make sense.
506                 panic!("not implemented");
507             }
508         }
509     };
510 
511     // TODO: Have a switch to turn this debug off/on.
512     debug_output_to_file(&gen, format!("/tmp/out-{}.rs", struct_ident.to_string()));
513 
514     gen.into()
515 }
516 
517 /// Generates the definition of `DBusArg` trait required for D-Bus projection.
518 ///
519 /// Due to Rust orphan rule, `DBusArg` trait needs to be defined locally in the crate that wants to
520 /// use D-Bus projection. Providing `DBusArg` as a public trait won't let other crates implement
521 /// it for structs defined in foreign crates. As a workaround, this macro is provided to generate
522 /// `DBusArg` trait definition.
523 #[proc_macro]
generate_dbus_arg(_item: TokenStream) -> TokenStream524 pub fn generate_dbus_arg(_item: TokenStream) -> TokenStream {
525     let gen = quote! {
526         use dbus::arg::PropMap;
527         use dbus::nonblock::SyncConnection;
528         use dbus::strings::BusName;
529         use dbus_projection::DisconnectWatcher;
530 
531         use std::error::Error;
532         use std::fmt;
533         use std::sync::{Arc, Mutex};
534 
535         #[derive(Debug)]
536         pub(crate) struct DBusArgError {
537             message: String,
538         }
539 
540         impl DBusArgError {
541             pub fn new(message: String) -> DBusArgError {
542                 DBusArgError { message }
543             }
544         }
545 
546         impl fmt::Display for DBusArgError {
547             fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
548                 write!(f, "{}", self.message)
549             }
550         }
551 
552         impl Error for DBusArgError {}
553 
554         pub(crate) trait DBusArg {
555             type DBusType;
556 
557             fn from_dbus(
558                 x: Self::DBusType,
559                 conn: Arc<SyncConnection>,
560                 remote: BusName<'static>,
561                 disconnect_watcher: Arc<Mutex<DisconnectWatcher>>,
562             ) -> Result<Self, Box<dyn Error>>
563             where
564                 Self: Sized;
565 
566             fn to_dbus(x: Self) -> Result<Self::DBusType, Box<dyn Error>>;
567         }
568 
569         // Types that implement dbus::arg::Append do not need any conversion.
570         pub(crate) trait DirectDBus {}
571         impl DirectDBus for i32 {}
572         impl DirectDBus for u32 {}
573         impl DirectDBus for String {}
574         impl<T: DirectDBus> DBusArg for T {
575             type DBusType = T;
576 
577             fn from_dbus(
578                 data: T,
579                 _conn: Arc<SyncConnection>,
580                 _remote: BusName<'static>,
581                 _disconnect_watcher: Arc<Mutex<DisconnectWatcher>>,
582             ) -> Result<T, Box<dyn Error>> {
583                 return Ok(data);
584             }
585 
586             fn to_dbus(data: T) -> Result<T, Box<dyn Error>> {
587                 return Ok(data);
588             }
589         }
590 
591         impl<T: DBusArg> DBusArg for Vec<T> {
592             type DBusType = Vec<T::DBusType>;
593 
594             fn from_dbus(
595                 data: Vec<T::DBusType>,
596                 conn: Arc<SyncConnection>,
597                 remote: BusName<'static>,
598                 disconnect_watcher: Arc<Mutex<DisconnectWatcher>>,
599             ) -> Result<Vec<T>, Box<dyn Error>> {
600                 let mut list: Vec<T> = vec![];
601                 for prop in data {
602                     let t = T::from_dbus(
603                         prop,
604                         conn.clone(),
605                         remote.clone(),
606                         disconnect_watcher.clone(),
607                     )?;
608                     list.push(t);
609                 }
610                 Ok(list)
611             }
612 
613             fn to_dbus(data: Vec<T>) -> Result<Vec<T::DBusType>, Box<dyn Error>> {
614                 let mut list: Vec<T::DBusType> = vec![];
615                 for item in data {
616                     let t = T::to_dbus(item)?;
617                     list.push(t);
618                 }
619                 Ok(list)
620             }
621         }
622     };
623 
624     // TODO: Have a switch to turn this debug off/on.
625     debug_output_to_file(&gen, format!("/tmp/out-generate_dbus_arg.rs"));
626 
627     gen.into()
628 }
629