pairing_delegate/
lib.rs

1// Copyright 2018 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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
38// Prompt the user for a single character input with the given |msg|. Blocks
39// until the user enters one of the characters in |allowed|.
40fn 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                    // Keep reading user's input until enter key is pressed.
94                    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
116/// Handles requests from the `PairingDelegateRequestStream`, prompting for
117/// user input when necessary. Signals the status of an `OnPairingComplete`
118/// event using the provided  sig_channel.
119pub 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                            // Passkey value ignored if the method is not PasskeyEntry or PasskeyEntry failed.
174                            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); // drop client to make channel close
204
205        // Closing the client causes the server to exit.
206        let _ = delegate_server_task.await.expect_err("should have returned error");
207    }
208}