1 extern crate proc_macro;
2 
3 use quote::quote;
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, ItemTrait, Meta, Pat, TraitItem};
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 /// Specifies the `Stack::Message` associated with a topshim callback.
23 #[proc_macro_attribute]
stack_message(_attr: TokenStream, item: TokenStream) -> TokenStream24 pub fn stack_message(_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 topshim callback object that contains closures.
34 ///
35 /// The closures are generated to be calls to the corresponding `Stack::Message`.
36 #[proc_macro_attribute]
btif_callbacks_generator(attr: TokenStream, item: TokenStream) -> TokenStream37 pub fn btif_callbacks_generator(attr: TokenStream, item: TokenStream) -> TokenStream {
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 callbacks_struct_ident = if let Expr::Path(p) = &args[1] {
47         p.path.get_ident().unwrap()
48     } else {
49         panic!("callbacks struct ident must be specified");
50     };
51 
52     let ast: ItemTrait = syn::parse(item.clone()).unwrap();
53 
54     let mut fn_names = quote! {};
55     let mut closure_defs = quote! {};
56     for attr in ast.items {
57         if let TraitItem::Method(m) = attr {
58             if m.attrs.len() != 1 {
59                 continue;
60             }
61 
62             let attr = &m.attrs[0];
63             if !attr.path.get_ident().unwrap().to_string().eq("stack_message") {
64                 continue;
65             }
66 
67             let attr_args = attr.parse_meta().unwrap();
68             let stack_message = if let Meta::List(meta_list) = attr_args {
69                 Some(meta_list.nested[0].clone())
70             } else {
71                 None
72             };
73 
74             if stack_message.is_none() {
75                 continue;
76             }
77 
78             let mut arg_names = quote! {};
79             for input in m.sig.inputs {
80                 if let FnArg::Typed(t) = input {
81                     if let Pat::Ident(i) = *t.pat {
82                         let attr_name = i.ident;
83                         arg_names = quote! { #arg_names #attr_name, };
84                     }
85                 }
86             }
87             let method_ident = m.sig.ident;
88 
89             fn_names = quote! {
90                 #fn_names
91                 #method_ident,
92             };
93 
94             closure_defs = quote! {
95                 #closure_defs
96                 let tx_clone = tx.clone();
97                 let #method_ident = Box::new(move |#arg_names| {
98                     let tx = tx_clone.clone();
99                     topstack::get_runtime().spawn(async move {
100                         let result = tx.send(Message::#stack_message(#arg_names)).await;
101                         if let Err(e) = result {
102                             eprintln!("Error in sending message: {}", e);
103                         }
104                     });
105                 });
106             };
107         }
108     }
109 
110     let ori_item = proc_macro2::TokenStream::from(item.clone());
111 
112     let gen = quote! {
113         #ori_item
114 
115         /// Returns a callback object to be passed to topshim.
116         pub fn #fn_ident(tx: tokio::sync::mpsc::Sender<Message>) -> #callbacks_struct_ident {
117             #closure_defs
118             #callbacks_struct_ident {
119                 #fn_names
120                 // TODO: Handle these in main loop.
121                 acl_state_changed: Box::new(|_, _, _, _| {}),
122                 bond_state_changed: Box::new(|_, _, _| {}),
123                 device_found: Box::new(|_, _| {}),
124                 discovery_state_changed: Box::new(|_| {}),
125                 pin_request: Box::new(|_, _, _, _| {}),
126                 remote_device_properties_changed: Box::new(|_, _, _, _| {}),
127                 ssp_request: Box::new(|_, _, _, _, _| {}),
128             }
129         }
130     };
131 
132     // TODO: Have a simple framework to turn on/off macro-generated code debug.
133     debug_output_to_file(&gen, format!("/tmp/out-{}.rs", fn_ident.to_string()));
134 
135     gen.into()
136 }
137