pairing_delegate/
lib.rs
1use anyhow::{format_err, Error};
6use fidl_fuchsia_bluetooth_sys::{
7 PairingDelegateRequest, PairingDelegateRequestStream, PairingMethod,
8};
9use fuchsia_bluetooth::types::{Address, PeerId};
10use futures::channel::mpsc::Sender;
11use futures::StreamExt;
12use std::io::{self, Read, Write};
13
14fn print_and_flush(msg: &str) {
15 print!("{}", msg);
16 io::stdout().flush().unwrap();
17}
18
19macro_rules! print_and_flush {
20 ($fmt:expr) => {
21 print_and_flush($fmt);
22 };
23 ($fmt:expr, $($arg:tt),*) => {
24 print_and_flush(&format!($fmt, $($arg),*));
25 };
26}
27
28fn handle_confirm(val: char) -> bool {
29 if val == 'y' {
30 println!("Accepted pairing");
31 true
32 } else {
33 println!("Rejected pairing");
34 false
35 }
36}
37
38fn prompt_for_char(msg: &str, allowed: &[char]) -> Result<char, Error> {
41 print_and_flush!("{}: ", msg);
42
43 #[allow(clippy::unbuffered_bytes)]
44 while let Some(input) = io::stdin().bytes().next() {
45 match input {
46 Ok(input) => {
47 let input = input as char;
48 if allowed.contains(&input) {
49 println!("{}", input);
50 return Ok(input);
51 }
52 }
53 Err(e) => {
54 println!("Failed to read input: {:?}", e);
55 return Err(e.into());
56 }
57 };
58 }
59 unreachable!();
60}
61
62fn prompt_for_consent() -> bool {
63 let val = prompt_for_char("Accept? (y/n)", &['y', 'n']).unwrap_or_else(|err| {
64 eprintln!("Failed to read input: {:#?}", err);
65 'n'
66 });
67 handle_confirm(val)
68}
69
70fn prompt_for_comparison(passkey: u32) -> bool {
71 let msg = format!("Is the passkey '{}' displayed on device? (y/n)", passkey);
72 let val = prompt_for_char(&msg, &['y', 'n']).unwrap_or_else(|err| {
73 eprintln!("Failed to read input: {:#?}", err);
74 'n'
75 });
76 handle_confirm(val)
77}
78
79fn prompt_for_remote_input(passkey: u32) -> bool {
80 println!("Enter the passkey '{}' on the peer.", passkey);
81 true
82}
83
84fn prompt_for_local_input() -> Option<u32> {
85 print_and_flush!("Enter the passkey displayed on the peer (or nothing to reject): ");
86 let mut passphrase = String::new();
87
88 #[allow(clippy::unbuffered_bytes)]
89 while let Some(input) = io::stdin().bytes().next() {
90 match input {
91 Ok(input) => {
92 if input != 13 {
93 print_and_flush!("{}", (input as char));
95 passphrase.push(input as char);
96 continue;
97 }
98 println!("");
99 match passphrase.parse::<u32>() {
100 Ok(passkey) => return Some(passkey),
101 Err(_) => {
102 eprintln!("Error: passkey not an integer.");
103 return None;
104 }
105 }
106 }
107 Err(e) => {
108 println!("Failed to receive passkey: {:?}", e);
109 return None;
110 }
111 }
112 }
113 unreachable!();
114}
115
116pub async fn handle_requests(
120 mut stream: PairingDelegateRequestStream,
121 mut sig_channel: Sender<(PeerId, bool)>,
122) -> Result<(), Error> {
123 while let Some(req) = stream.next().await {
124 match req {
125 Ok(event) => match event {
126 PairingDelegateRequest::OnPairingComplete { id, success, control_handle: _ } => {
127 println!(
128 "Pairing complete for peer (id: {}, status: {})",
129 PeerId::from(id),
130 match success {
131 true => "success".to_string(),
132 false => "failure".to_string(),
133 }
134 );
135 sig_channel.try_send((id.into(), success))?;
136 }
137 PairingDelegateRequest::OnPairingRequest {
138 peer,
139 method,
140 displayed_passkey,
141 responder,
142 } => {
143 let address = match &peer.address {
144 Some(address) => Address::from(address).to_string(),
145 None => "Unknown Address".to_string(),
146 };
147
148 println!(
149 "Pairing request from peer: {}",
150 match &peer.name {
151 Some(name) => format!("{} ({})", name, address),
152 None => address,
153 }
154 );
155
156 let (accept, entered_passkey) = match method {
157 PairingMethod::Consent => (prompt_for_consent(), None),
158 PairingMethod::PasskeyComparison => {
159 (prompt_for_comparison(displayed_passkey), None)
160 }
161 PairingMethod::PasskeyDisplay => {
162 (prompt_for_remote_input(displayed_passkey), None)
163 }
164 PairingMethod::PasskeyEntry => match prompt_for_local_input() {
165 None => (false, None),
166 passkey => (true, passkey),
167 },
168 };
169 let _ = responder.send(
170 accept,
171 match entered_passkey {
172 Some(passkey) => passkey,
173 None => 0u32,
175 },
176 );
177 }
178 PairingDelegateRequest::OnRemoteKeypress { id, keypress, control_handle: _ } => {
179 eprintln!("Peer: {} | {:?}", PeerId::from(id), keypress);
180 }
181 },
182 Err(e) => return Err(format_err!("error encountered {:?}", e)),
183 };
184 }
185 Err(format_err!("PairingDelegate channel closed (likely due to pre-existing PairingDelegate)"))
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191 use fidl_fuchsia_bluetooth_sys::PairingDelegateMarker;
192 use fuchsia_async as fasync;
193 use futures::channel::mpsc::channel;
194
195 #[fuchsia::test]
196 async fn test_pairing_delegate() {
197 let (pairing_delegate_client, pairing_delegate_server_stream) =
198 fidl::endpoints::create_request_stream::<PairingDelegateMarker>();
199 let (sig_sender, _sig_receiver) = channel(0);
200 let pairing_server = handle_requests(pairing_delegate_server_stream, sig_sender);
201 let delegate_server_task = fasync::Task::spawn(async move { pairing_server.await });
202
203 std::mem::drop(pairing_delegate_client); let _ = delegate_server_task.await.expect_err("should have returned error");
207 }
208}