use anyhow::{format_err, Error};
use fidl_fuchsia_bluetooth_sys::{
PairingDelegateRequest, PairingDelegateRequestStream, PairingMethod,
};
use fuchsia_bluetooth::types::{Address, PeerId};
use futures::channel::mpsc::Sender;
use futures::StreamExt;
use 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
}
}
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: u32) -> bool {
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: u32) -> bool {
println!("Enter the passkey '{}' on the peer.", passkey);
true
}
fn prompt_for_local_input() -> Option<u32> {
print_and_flush!("Enter the passkey displayed on the peer (or nothing to reject): ");
let mut passphrase = String::new();
while let Some(input) = io::stdin().bytes().next() {
match input {
Ok(input) => {
if input != 13 {
print_and_flush!("{}", (input as char));
passphrase.push(input as char);
continue;
}
println!("");
match passphrase.parse::<u32>() {
Ok(passkey) => return Some(passkey),
Err(_) => {
eprintln!("Error: passkey not an integer.");
return None;
}
}
}
Err(e) => {
println!("Failed to receive passkey: {:?}", e);
return None;
}
}
}
unreachable!();
}
pub async fn handle_requests(
mut stream: PairingDelegateRequestStream,
mut sig_channel: Sender<(PeerId, bool)>,
) -> Result<(), Error> {
while let Some(req) = stream.next().await {
match req {
Ok(event) => match event {
PairingDelegateRequest::OnPairingComplete { id, success, control_handle: _ } => {
println!(
"Pairing complete for peer (id: {}, status: {})",
PeerId::from(id),
match success {
true => "success".to_string(),
false => "failure".to_string(),
}
);
sig_channel.try_send((id.into(), success))?;
}
PairingDelegateRequest::OnPairingRequest {
peer,
method,
displayed_passkey,
responder,
} => {
let address = match &peer.address {
Some(address) => Address::from(address).to_string(),
None => "Unknown Address".to_string(),
};
println!(
"Pairing request from peer: {}",
match &peer.name {
Some(name) => format!("{} ({})", name, address),
None => address,
}
);
let (accept, 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(
accept,
match entered_passkey {
Some(passkey) => passkey,
None => 0u32,
},
);
}
PairingDelegateRequest::OnRemoteKeypress { id, keypress, control_handle: _ } => {
eprintln!("Peer: {} | {:?}", PeerId::from(id), keypress);
}
},
Err(e) => return Err(format_err!("error encountered {:?}", e)),
};
}
Err(format_err!("PairingDelegate channel closed (likely due to pre-existing PairingDelegate)"))
}
#[cfg(test)]
mod tests {
use super::*;
use fidl_fuchsia_bluetooth_sys::PairingDelegateMarker;
use fuchsia_async as fasync;
use futures::channel::mpsc::channel;
#[fuchsia::test]
async fn test_pairing_delegate() {
let (pairing_delegate_client, pairing_delegate_server_stream) =
fidl::endpoints::create_request_stream::<PairingDelegateMarker>();
let (sig_sender, _sig_receiver) = channel(0);
let pairing_server = handle_requests(pairing_delegate_server_stream, sig_sender);
let delegate_server_task = fasync::Task::spawn(async move { pairing_server.await });
std::mem::drop(pairing_delegate_client); let _ = delegate_server_task.await.expect_err("should have returned error");
}
}