linux-rust: add skeleton for other devices
This commit is contained in:
@@ -8,6 +8,8 @@ use tokio::time::{sleep, Instant};
|
||||
use std::collections::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json;
|
||||
use crate::devices::airpods::AirPodsInformation;
|
||||
use crate::devices::enums::{DeviceData, DeviceInformation, DeviceType};
|
||||
use crate::utils::get_devices_path;
|
||||
|
||||
const PSM: u16 = 0x1001;
|
||||
@@ -280,45 +282,11 @@ pub enum AACPEvent {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum DeviceType {
|
||||
AirPods,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LEData {
|
||||
pub struct AirPodsLEKeys {
|
||||
pub irk: String,
|
||||
pub enc_key: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AirPodsInformation {
|
||||
pub name: String,
|
||||
pub model_number: String,
|
||||
pub manufacturer: String,
|
||||
pub serial_number: String,
|
||||
pub version1: String,
|
||||
pub version2: String,
|
||||
pub hardware_revision: String,
|
||||
pub updater_identifier: String,
|
||||
pub left_serial_number: String,
|
||||
pub right_serial_number: String,
|
||||
pub version3: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", content = "data")]
|
||||
pub enum DeviceInformation {
|
||||
AirPods(AirPodsInformation),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DeviceData {
|
||||
pub name: String,
|
||||
pub type_: DeviceType,
|
||||
pub le: LEData,
|
||||
pub information: Option<DeviceInformation>,
|
||||
}
|
||||
|
||||
pub struct AACPManagerState {
|
||||
pub sender: Option<mpsc::Sender<Vec<u8>>>,
|
||||
pub control_command_status_list: Vec<ControlCommandStatus>,
|
||||
@@ -647,7 +615,7 @@ impl AACPManager {
|
||||
strings.push(s.to_string());
|
||||
}
|
||||
}
|
||||
strings.remove(0); // Remove the first empty string as per comment
|
||||
strings.remove(0);
|
||||
let info = AirPodsInformation {
|
||||
name: strings.get(0).cloned().unwrap_or_default(),
|
||||
model_number: strings.get(1).cloned().unwrap_or_default(),
|
||||
@@ -660,6 +628,10 @@ impl AACPManager {
|
||||
left_serial_number: strings.get(8).cloned().unwrap_or_default(),
|
||||
right_serial_number: strings.get(9).cloned().unwrap_or_default(),
|
||||
version3: strings.get(10).cloned().unwrap_or_default(),
|
||||
le_keys: AirPodsLEKeys {
|
||||
irk: "".to_string(),
|
||||
enc_key: "".to_string(),
|
||||
},
|
||||
};
|
||||
let mut state = self.state.lock().await;
|
||||
if let Some(mac) = state.airpods_mac {
|
||||
@@ -715,12 +687,29 @@ impl AACPManager {
|
||||
let device_data = state.devices.entry(mac_str.clone()).or_insert(DeviceData {
|
||||
name: mac_str.clone(),
|
||||
type_: DeviceType::AirPods,
|
||||
le: LEData { irk: "".to_string(), enc_key: "".to_string() },
|
||||
information: None,
|
||||
});
|
||||
match kt {
|
||||
ProximityKeyType::Irk => device_data.le.irk = hex::encode(key_data),
|
||||
ProximityKeyType::EncKey => device_data.le.enc_key = hex::encode(key_data),
|
||||
ProximityKeyType::Irk => {
|
||||
match device_data.information.as_mut() {
|
||||
Some(DeviceInformation::AirPods(info)) => {
|
||||
info.le_keys.irk = hex::encode(key_data);
|
||||
}
|
||||
_ => {
|
||||
error!("Device information is not AirPods for adding LE IRK.");
|
||||
}
|
||||
}
|
||||
}
|
||||
ProximityKeyType::EncKey => {
|
||||
match device_data.information.as_mut() {
|
||||
Some(DeviceInformation::AirPods(info)) => {
|
||||
info.le_keys.enc_key = hex::encode(key_data);
|
||||
}
|
||||
_ => {
|
||||
error!("Device information is not AirPods for adding LE encryption key.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,29 +16,34 @@ const OPCODE_READ_REQUEST: u8 = 0x0A;
|
||||
const OPCODE_WRITE_REQUEST: u8 = 0x12;
|
||||
const OPCODE_HANDLE_VALUE_NTF: u8 = 0x1B;
|
||||
const OPCODE_WRITE_RESPONSE: u8 = 0x13;
|
||||
const RESPONSE_TIMEOUT: u64 = 5000;
|
||||
|
||||
#[repr(u16)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ATTHandles {
|
||||
Transparency = 0x18,
|
||||
LoudSoundReduction = 0x1B,
|
||||
HearingAid = 0x2A,
|
||||
AirPodsTransparency = 0x18,
|
||||
AirPodsLoudSoundReduction = 0x1B,
|
||||
AirPodsHearingAid = 0x2A,
|
||||
NothingEverything = 0x8002,
|
||||
NothingEverythingRead = 0x8005 // for some reason, and not the same as the write handle
|
||||
}
|
||||
|
||||
#[repr(u16)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ATTCCCDHandles {
|
||||
Transparency = ATTHandles::Transparency as u16 + 1,
|
||||
LoudSoundReduction = ATTHandles::LoudSoundReduction as u16 + 1,
|
||||
HearingAid = ATTHandles::HearingAid as u16 + 1,
|
||||
Transparency = ATTHandles::AirPodsTransparency as u16 + 1,
|
||||
LoudSoundReduction = ATTHandles::AirPodsLoudSoundReduction as u16 + 1,
|
||||
HearingAid = ATTHandles::AirPodsHearingAid as u16 + 1,
|
||||
}
|
||||
|
||||
impl From<ATTHandles> for ATTCCCDHandles {
|
||||
fn from(handle: ATTHandles) -> Self {
|
||||
match handle {
|
||||
ATTHandles::Transparency => ATTCCCDHandles::Transparency,
|
||||
ATTHandles::LoudSoundReduction => ATTCCCDHandles::LoudSoundReduction,
|
||||
ATTHandles::HearingAid => ATTCCCDHandles::HearingAid,
|
||||
ATTHandles::AirPodsTransparency => ATTCCCDHandles::Transparency,
|
||||
ATTHandles::AirPodsLoudSoundReduction => ATTCCCDHandles::LoudSoundReduction,
|
||||
ATTHandles::AirPodsHearingAid => ATTCCCDHandles::HearingAid,
|
||||
ATTHandles::NothingEverything => panic!("No CCCD for NothingEverything handle"), // we don't request it
|
||||
ATTHandles::NothingEverythingRead => panic!("No CCD for NothingEverythingRead handle") // it sends notifications without CCCD
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,18 +51,13 @@ impl From<ATTHandles> for ATTCCCDHandles {
|
||||
struct ATTManagerState {
|
||||
sender: Option<mpsc::Sender<Vec<u8>>>,
|
||||
listeners: HashMap<u16, Vec<mpsc::UnboundedSender<Vec<u8>>>>,
|
||||
responses: mpsc::UnboundedReceiver<Vec<u8>>,
|
||||
response_tx: mpsc::UnboundedSender<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl ATTManagerState {
|
||||
fn new() -> Self {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
ATTManagerState {
|
||||
sender: None,
|
||||
listeners: HashMap::new(),
|
||||
responses: rx,
|
||||
response_tx: tx,
|
||||
listeners: HashMap::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,13 +65,18 @@ impl ATTManagerState {
|
||||
#[derive(Clone)]
|
||||
pub struct ATTManager {
|
||||
state: Arc<Mutex<ATTManagerState>>,
|
||||
response_rx: Arc<Mutex<mpsc::UnboundedReceiver<Vec<u8>>>>,
|
||||
response_tx: mpsc::UnboundedSender<Vec<u8>>,
|
||||
tasks: Arc<Mutex<JoinSet<()>>>,
|
||||
}
|
||||
|
||||
impl ATTManager {
|
||||
pub fn new() -> Self {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
ATTManager {
|
||||
state: Arc::new(Mutex::new(ATTManagerState::new())),
|
||||
response_rx: Arc::new(Mutex::new(rx)),
|
||||
response_tx: tx,
|
||||
tasks: Arc::new(Mutex::new(JoinSet::new())),
|
||||
}
|
||||
}
|
||||
@@ -184,11 +189,18 @@ impl ATTManager {
|
||||
}
|
||||
|
||||
async fn read_response(&self) -> Result<Vec<u8>> {
|
||||
let mut state = self.state.lock().await;
|
||||
match tokio::time::timeout(Duration::from_millis(2000), state.responses.recv()).await {
|
||||
debug!("Waiting for response...");
|
||||
let mut rx = self.response_rx.lock().await;
|
||||
match tokio::time::timeout(Duration::from_millis(RESPONSE_TIMEOUT), rx.recv()).await {
|
||||
Ok(Some(resp)) => Ok(resp),
|
||||
Ok(None) => Err(Error::from(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "Response channel closed"))),
|
||||
Err(_) => Err(Error::from(std::io::Error::new(std::io::ErrorKind::TimedOut, "Response timeout"))),
|
||||
Ok(None) => Err(Error::from(std::io::Error::new(
|
||||
std::io::ErrorKind::UnexpectedEof,
|
||||
"Response channel closed"
|
||||
))),
|
||||
Err(_) => Err(Error::from(std::io::Error::new(
|
||||
std::io::ErrorKind::TimedOut,
|
||||
"Response timeout"
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,10 +229,11 @@ async fn recv_thread(manager: ATTManager, sp: Arc<SeqPacket>) {
|
||||
let _ = listener.send(value.clone());
|
||||
}
|
||||
}
|
||||
} else if data[0] == OPCODE_WRITE_RESPONSE {
|
||||
let _ = manager.response_tx.send(vec![]);
|
||||
} else {
|
||||
// Response
|
||||
let state = manager.state.lock().await;
|
||||
let _ = state.response_tx.send(data[1..].to_vec());
|
||||
let _ = manager.response_tx.send(data[1..].to_vec());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use std::io::Error;
|
||||
use bluer::Adapter;
|
||||
use log::debug;
|
||||
|
||||
pub(crate) async fn find_connected_airpods(adapter: &bluer::Adapter) -> bluer::Result<bluer::Device> {
|
||||
pub(crate) async fn find_connected_airpods(adapter: &Adapter) -> bluer::Result<bluer::Device> {
|
||||
let target_uuid = uuid::Uuid::parse_str("74ec2172-0bad-4d01-8f77-997b2be0722a").unwrap();
|
||||
|
||||
let addrs = adapter.device_addresses().await?;
|
||||
@@ -17,4 +19,23 @@ pub(crate) async fn find_connected_airpods(adapter: &bluer::Adapter) -> bluer::R
|
||||
}
|
||||
}
|
||||
Err(bluer::Error::from(Error::new(std::io::ErrorKind::NotFound, "No connected AirPods found")))
|
||||
}
|
||||
|
||||
pub async fn find_other_managed_devices(adapter: &Adapter, managed_macs: Vec<String>) -> bluer::Result<Vec<bluer::Device>> {
|
||||
let addrs = adapter.device_addresses().await?;
|
||||
let mut devices = Vec::new();
|
||||
for addr in addrs {
|
||||
let device = adapter.device(addr)?;
|
||||
let device_mac = device.address().to_string();
|
||||
let connected = device.is_connected().await.unwrap_or(false);
|
||||
debug!("Checking device: {}, connected: {}", device_mac, connected);
|
||||
if connected && managed_macs.contains(&device_mac) {
|
||||
debug!("Found managed device: {}", device_mac);
|
||||
devices.push(device);
|
||||
}
|
||||
}
|
||||
if !devices.is_empty() {
|
||||
return Ok(devices);
|
||||
}
|
||||
Err(bluer::Error::from(Error::new(std::io::ErrorKind::NotFound, "No other managed devices found")))
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::cmp::PartialEq;
|
||||
use bluer::monitor::{Monitor, MonitorEvent, Pattern};
|
||||
use bluer::{Address, Session};
|
||||
use aes::Aes128;
|
||||
@@ -14,7 +13,7 @@ use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use crate::bluetooth::aacp::BatteryStatus;
|
||||
use crate::ui::tray::MyTray;
|
||||
use crate::bluetooth::aacp::{DeviceData, DeviceType};
|
||||
use crate::devices::enums::{DeviceData, DeviceInformation, DeviceType};
|
||||
use crate::utils::{get_devices_path, get_preferences_path, ah};
|
||||
|
||||
fn decrypt(key: &[u8; 16], data: &[u8; 16]) -> [u8; 16] {
|
||||
@@ -43,14 +42,6 @@ fn verify_rpa(addr: &str, irk: &[u8; 16]) -> bool {
|
||||
hash == computed_hash
|
||||
}
|
||||
|
||||
impl PartialEq for DeviceType {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(DeviceType::AirPods, DeviceType::AirPods) => true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start_le_monitor(tray_handle: Option<ksni::Handle<MyTray>>) -> bluer::Result<()> {
|
||||
let session = Session::new().await?;
|
||||
let adapter = session.default_adapter().await?;
|
||||
@@ -107,15 +98,17 @@ pub async fn start_le_monitor(tray_handle: Option<ksni::Handle<MyTray>>) -> blue
|
||||
let mut found_mac = None;
|
||||
for (airpods_mac, device_data) in &all_devices {
|
||||
if device_data.type_ == DeviceType::AirPods {
|
||||
if let Ok(irk_bytes) = hex::decode(&device_data.le.irk) {
|
||||
if irk_bytes.len() == 16 {
|
||||
let irk: [u8; 16] = irk_bytes.as_slice().try_into().unwrap();
|
||||
debug!("Verifying RPA {} for airpods MAC {} with IRK {}", addr_str, airpods_mac, device_data.le.irk);
|
||||
if verify_rpa(&addr_str, &irk) {
|
||||
info!("Matched our device ({}) with the irk for {}", addr, airpods_mac);
|
||||
verified_macs.insert(addr, airpods_mac.clone());
|
||||
found_mac = Some(airpods_mac.clone());
|
||||
break;
|
||||
if let Some(DeviceInformation::AirPods(info)) = &device_data.information {
|
||||
if let Ok(irk_bytes) = hex::decode(&info.le_keys.irk) {
|
||||
if irk_bytes.len() == 16 {
|
||||
let irk: [u8; 16] = irk_bytes.as_slice().try_into().unwrap();
|
||||
debug!("Verifying RPA {} for airpods MAC {} with IRK {}", addr_str, airpods_mac, info.le_keys.irk);
|
||||
if verify_rpa(&addr_str, &irk) {
|
||||
info!("Matched our device ({}) with the irk for {}", addr, airpods_mac);
|
||||
verified_macs.insert(addr, airpods_mac.clone());
|
||||
found_mac = Some(airpods_mac.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,8 +126,8 @@ pub async fn start_le_monitor(tray_handle: Option<ksni::Handle<MyTray>>) -> blue
|
||||
|
||||
if let Some(ref mac) = matched_airpods_mac {
|
||||
if let Some(device_data) = all_devices.get(mac) {
|
||||
if !device_data.le.enc_key.is_empty() {
|
||||
if let Ok(enc_key_bytes) = hex::decode(&device_data.le.enc_key) {
|
||||
if let Some(DeviceInformation::AirPods(info)) = &device_data.information {
|
||||
if let Ok(enc_key_bytes) = hex::decode(&info.le_keys.enc_key) {
|
||||
if enc_key_bytes.len() == 16 {
|
||||
matched_enc_key = Some(enc_key_bytes.as_slice().try_into().unwrap());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use crate::bluetooth::aacp::AACPManager;
|
||||
use crate::bluetooth::att::ATTManager;
|
||||
|
||||
pub enum BluetoothManager {
|
||||
AACP(Arc<AACPManager>),
|
||||
ATT(Arc<ATTManager>),
|
||||
}
|
||||
|
||||
pub struct DeviceManagers {
|
||||
att: Option<Arc<ATTManager>>,
|
||||
aacp: Option<Arc<AACPManager>>,
|
||||
}
|
||||
|
||||
impl DeviceManagers {
|
||||
fn new() -> Self {
|
||||
Self { att: None, aacp: None }
|
||||
}
|
||||
|
||||
fn with_aacp(aacp: AACPManager) -> Self {
|
||||
Self { att: None, aacp: Some(Arc::new(aacp)) }
|
||||
}
|
||||
|
||||
fn with_att(att: ATTManager) -> Self {
|
||||
Self { att: Some(Arc::new(att)), aacp: None }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BluetoothDevices {
|
||||
devices: HashMap<String, DeviceManagers>,
|
||||
}
|
||||
|
||||
impl BluetoothDevices {
|
||||
fn new() -> Self {
|
||||
Self { devices: HashMap::new() }
|
||||
}
|
||||
|
||||
fn add_aacp(&mut self, mac: String, manager: AACPManager) {
|
||||
self.devices
|
||||
.entry(mac)
|
||||
.or_insert_with(DeviceManagers::new)
|
||||
.aacp = Some(Arc::new(manager));
|
||||
}
|
||||
|
||||
fn add_att(&mut self, mac: String, manager: ATTManager) {
|
||||
self.devices
|
||||
.entry(mac)
|
||||
.or_insert_with(DeviceManagers::new)
|
||||
.att = Some(Arc::new(manager));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
pub(crate) mod discovery;
|
||||
pub mod aacp;
|
||||
pub mod att;
|
||||
pub mod le;
|
||||
pub mod le;
|
||||
pub mod managers;
|
||||
Reference in New Issue
Block a user