blob: 8252ecd4ddf8b148858943ca9d3e4513d3dc7763 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
failure::Error,
fidl::endpoints::RequestStream,
fidl_fuchsia_bluetooth_control::{
PairingDelegateRequest, PairingDelegateRequestStream, PairingMethod,
},
fuchsia_async as fasync,
fuchsia_bluetooth::error::Error as BtError,
futures::prelude::*,
std::io::{self, Read, Write},
};
fn print_and_flush(msg: &str) {
print!("{}", msg);
io::stdout().flush().unwrap();
}
macro_rules! print_and_flush {
($fmt:expr) => {
print_and_flush($fmt);
};
($fmt:expr, $($arg:tt),*) => {
print_and_flush(&format!($fmt, $($arg),*));
};
}
fn handle_confirm(val: char) -> bool {
if val == 'y' {
println!("Accepted pairing");
true
} else {
println!("Rejected pairing");
false
}
}
// Prompt the user for a single character input with the given |msg|. Blocks
// until the user enters one of the characters in |allowed|.
fn prompt_for_char(msg: &str, allowed: &[char]) -> Result<char, Error> {
print_and_flush!("{}: ", msg);
while let Some(input) = io::stdin().bytes().next() {
match input {
Ok(input) => {
let input = input as char;
if allowed.contains(&input) {
println!("{}", input);
return Ok(input);
}
}
Err(e) => {
println!("Failed to read input: {:?}", e);
return Err(e.into());
}
};
}
unreachable!();
}
fn prompt_for_consent() -> bool {
let val = prompt_for_char("Accept? (y/n)", &['y', 'n']).unwrap_or_else(|err| {
eprintln!("Failed to read input: {:#?}", err);
'n'
});
handle_confirm(val)
}
fn prompt_for_comparison(passkey: Option<String>) -> bool {
match passkey {
None => {
println!("No passkey provided for 'Passkey Comparison'!");
false
}
Some(passkey) => {
let msg = format!("Is the passkey '{}' displayed on device? (y/n)", passkey);
let val = prompt_for_char(&msg, &['y', 'n']).unwrap_or_else(|err| {
eprintln!("Failed to read input: {:#?}", err);
'n'
});
handle_confirm(val)
}
}
}
fn prompt_for_remote_input(passkey: Option<String>) -> bool {
match passkey {
None => {
println!("No passkey provided for 'Passkey Display'!");
false
}
Some(passkey) => {
println!("Enter the passkey '{}' on the peer.", passkey);
true
}
}
}
fn prompt_for_local_input() -> Option<String> {
print_and_flush!("Enter the passkey displayed on the peer (or nothing to reject): ");
let mut input = String::new();
match io::stdin().read_line(&mut input) {
Ok(_) => {
let input = input.trim().to_string();
if input.len() == 0 {
None
} else {
println!("Entered: {}", input);
Some(input)
}
}
Err(e) => {
println!("Failed to receive passkey: {}", e);
None
}
}
}
pub fn pairing_delegate(channel: fasync::Channel) -> impl Future<Output = Result<(), Error>> {
let stream = PairingDelegateRequestStream::from_channel(channel);
stream
.try_for_each(move |evt| {
match evt {
PairingDelegateRequest::OnPairingComplete {
device_id,
status,
control_handle: _,
} => {
println!(
"Pairing complete for peer (id: {}, status: {})",
device_id,
match status.error {
None => "success".to_string(),
Some(error) => format!("{}", BtError::from(*error)),
}
);
}
PairingDelegateRequest::OnPairingRequest {
device,
method,
displayed_passkey,
responder,
} => {
println!(
"Pairing request from peer: {}",
match &device.name {
Some(name) => format!("{} ({})", name, &device.address),
None => device.address,
}
);
let (confirm, entered_passkey) = match method {
PairingMethod::Consent => (prompt_for_consent(), None),
PairingMethod::PasskeyComparison => {
(prompt_for_comparison(displayed_passkey), None)
}
PairingMethod::PasskeyDisplay => {
(prompt_for_remote_input(displayed_passkey), None)
}
PairingMethod::PasskeyEntry => match prompt_for_local_input() {
None => (false, None),
passkey => (true, passkey),
},
};
let _ = responder.send(confirm, entered_passkey.as_ref().map(String::as_ref));
}
PairingDelegateRequest::OnRemoteKeypress {
device_id,
keypress,
control_handle: _,
} => {
eprintln!("Device: {} | {:?}", device_id, keypress);
}
};
future::ready(Ok(()))
})
.map_err(|e| e.into())
}