mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-02-01 15:49:10 +00:00
linux-rust: add ble skeleton
This commit is contained in:
67
linux-rust/Cargo.lock
generated
67
linux-rust/Cargo.lock
generated
@@ -24,6 +24,17 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
@@ -304,6 +315,16 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.50"
|
||||
@@ -365,6 +386,15 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
@@ -405,6 +435,16 @@ version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "custom_debug"
|
||||
version = "0.6.2"
|
||||
@@ -793,6 +833,16 @@ dependencies = [
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
@@ -931,6 +981,15 @@ dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "interpolate_name"
|
||||
version = "0.2.4"
|
||||
@@ -1095,10 +1154,12 @@ name = "librepods-rust"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ab_glyph",
|
||||
"aes",
|
||||
"bluer",
|
||||
"clap",
|
||||
"dbus",
|
||||
"env_logger",
|
||||
"futures",
|
||||
"hex",
|
||||
"image",
|
||||
"imageproc",
|
||||
@@ -2232,6 +2293,12 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
|
||||
@@ -20,6 +20,8 @@ ab_glyph = "0.2.32"
|
||||
clap = { version = "4.5.50", features = ["derive"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
aes = "0.8.4"
|
||||
futures = "0.3.31"
|
||||
|
||||
[profile.release]
|
||||
opt-level = "s"
|
||||
|
||||
@@ -8,12 +8,19 @@ use tokio::time::{sleep, Instant};
|
||||
use std::collections::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const PSM: u16 = 0x1001;
|
||||
const CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
const POLL_INTERVAL: Duration = Duration::from_millis(200);
|
||||
const HEADER_BYTES: [u8; 4] = [0x04, 0x00, 0x04, 0x00];
|
||||
|
||||
fn get_proximity_keys_path() -> PathBuf {
|
||||
let data_dir = std::env::var("XDG_DATA_HOME")
|
||||
.unwrap_or_else(|_| format!("{}/.local/share", std::env::var("HOME").unwrap_or_default()));
|
||||
PathBuf::from(data_dir).join("librepods").join("proximity_keys.json")
|
||||
}
|
||||
|
||||
pub mod opcodes {
|
||||
pub const SET_FEATURE_FLAGS: u8 = 0x4D;
|
||||
pub const REQUEST_NOTIFICATIONS: u8 = 0x0F;
|
||||
@@ -253,7 +260,7 @@ struct AACPManagerState {
|
||||
|
||||
impl AACPManagerState {
|
||||
fn new() -> Self {
|
||||
let proximity_keys = std::fs::read_to_string("proximity_keys.json")
|
||||
let proximity_keys = std::fs::read_to_string(get_proximity_keys_path())
|
||||
.ok()
|
||||
.and_then(|s| serde_json::from_str(&s).ok())
|
||||
.unwrap_or_default();
|
||||
@@ -565,9 +572,16 @@ impl AACPManager {
|
||||
state.proximity_keys.insert(kt, key_data.clone());
|
||||
}
|
||||
}
|
||||
// Persist to file
|
||||
|
||||
let json = serde_json::to_string(&state.proximity_keys).unwrap();
|
||||
if let Err(e) = tokio::fs::write("proximity_keys.json", json).await {
|
||||
let path = get_proximity_keys_path();
|
||||
if let Some(parent) = path.parent() {
|
||||
if let Err(e) = tokio::fs::create_dir_all(&parent).await {
|
||||
error!("Failed to create directory for proximity keys: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let Err(e) = tokio::fs::write(&path, json).await {
|
||||
error!("Failed to save proximity keys: {}", e);
|
||||
}
|
||||
if let Some(ref tx) = state.event_tx {
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
use bluer::monitor::{Monitor, MonitorEvent, Pattern, RssiSamplingPeriod};
|
||||
use bluer::{Address, Session};
|
||||
use aes::Aes128;
|
||||
use aes::cipher::{BlockEncrypt, KeyInit};
|
||||
use aes::cipher::generic_array::GenericArray;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use log::{info, error, debug};
|
||||
use serde_json;
|
||||
use crate::bluetooth::aacp::ProximityKeyType;
|
||||
use futures::StreamExt;
|
||||
use hex;
|
||||
use std::time::Duration;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn get_proximity_keys_path() -> PathBuf {
|
||||
let data_dir = std::env::var("XDG_DATA_HOME")
|
||||
.unwrap_or_else(|_| format!("{}/.local/share", std::env::var("HOME").unwrap_or_default()));
|
||||
PathBuf::from(data_dir).join("librepods").join("proximity_keys.json")
|
||||
}
|
||||
|
||||
fn e(key: &[u8; 16], data: &[u8; 16]) -> [u8; 16] {
|
||||
let mut swapped_key = *key;
|
||||
swapped_key.reverse();
|
||||
let mut swapped_data = *data;
|
||||
swapped_data.reverse();
|
||||
let cipher = Aes128::new(&GenericArray::from(swapped_key));
|
||||
let mut block = GenericArray::from(swapped_data);
|
||||
cipher.encrypt_block(&mut block);
|
||||
let mut result: [u8; 16] = block.into();
|
||||
result.reverse();
|
||||
result
|
||||
}
|
||||
|
||||
fn ah(k: &[u8; 16], r: &[u8; 3]) -> [u8; 3] {
|
||||
let mut r_padded = [0u8; 16];
|
||||
r_padded[..3].copy_from_slice(r);
|
||||
let encrypted = e(k, &r_padded);
|
||||
let mut hash = [0u8; 3];
|
||||
hash.copy_from_slice(&encrypted[..3]);
|
||||
hash
|
||||
}
|
||||
|
||||
fn verify_rpa(addr: &str, irk: &[u8; 16]) -> bool {
|
||||
let rpa: Vec<u8> = addr.split(':')
|
||||
.map(|s| u8::from_str_radix(s, 16).unwrap())
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.rev()
|
||||
.collect();
|
||||
if rpa.len() != 6 {
|
||||
return false;
|
||||
}
|
||||
let prand_slice = &rpa[3..6];
|
||||
let prand: [u8; 3] = prand_slice.try_into().unwrap();
|
||||
let hash_slice = &rpa[0..3];
|
||||
let hash: [u8; 3] = hash_slice.try_into().unwrap();
|
||||
let computed_hash = ah(irk, &prand);
|
||||
hash == computed_hash
|
||||
}
|
||||
|
||||
pub async fn start_le_monitor() -> bluer::Result<()> {
|
||||
let session = Session::new().await?;
|
||||
let adapter = session.default_adapter().await?;
|
||||
adapter.set_powered(true).await?;
|
||||
|
||||
let proximity_keys: HashMap<ProximityKeyType, Vec<u8>> = std::fs::read_to_string(get_proximity_keys_path())
|
||||
.ok()
|
||||
.and_then(|s| serde_json::from_str(&s).ok())
|
||||
.unwrap_or_default();
|
||||
let irk = proximity_keys.get(&ProximityKeyType::Irk)
|
||||
.and_then(|v| if v.len() == 16 { Some(<[u8; 16]>::try_from(v.as_slice()).unwrap()) } else { None });
|
||||
let mut verified_macs: HashSet<Address> = HashSet::new();
|
||||
|
||||
let pattern = Pattern {
|
||||
data_type: 0xFF, // Manufacturer specific data
|
||||
start_position: 0,
|
||||
content: vec![0x4C, 0x00], // Apple manufacturer ID (76) in LE
|
||||
};
|
||||
|
||||
let mm = adapter.monitor().await?;
|
||||
let mut monitor_handle = mm
|
||||
.register(Monitor {
|
||||
monitor_type: bluer::monitor::Type::OrPatterns,
|
||||
rssi_low_threshold: None,
|
||||
rssi_high_threshold: None,
|
||||
rssi_low_timeout: None,
|
||||
rssi_high_timeout: None,
|
||||
rssi_sampling_period: Some(RssiSamplingPeriod::Period(Duration::from_millis(500))),
|
||||
patterns: Some(vec![pattern]),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
|
||||
while let Some(mevt) = monitor_handle.next().await {
|
||||
if let MonitorEvent::DeviceFound(devid) = mevt {
|
||||
let dev = adapter.device(devid.device)?;
|
||||
let addr = dev.address();
|
||||
let addr_str = addr.to_string();
|
||||
|
||||
if !verified_macs.contains(&addr) {
|
||||
if let Some(irk) = &irk {
|
||||
if verify_rpa(&addr_str, irk) {
|
||||
verified_macs.insert(addr);
|
||||
info!("matched our device ({}) with the irk", addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if verified_macs.contains(&addr) {
|
||||
let mut events = dev.events().await?;
|
||||
tokio::spawn(async move {
|
||||
while let Some(ev) = events.next().await {
|
||||
match ev {
|
||||
bluer::DeviceEvent::PropertyChanged(prop) => {
|
||||
match prop {
|
||||
bluer::DeviceProperty::ManufacturerData(data) => {
|
||||
info!("Manufacturer data from {}: {:?}", addr_str, data.iter().map(|(k, v)| (k, hex::encode(v))).collect::<HashMap<_, _>>());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ use bluer::Address;
|
||||
use ksni::TrayMethods;
|
||||
use crate::ui::tray::MyTray;
|
||||
use clap::Parser;
|
||||
use crate::bluetooth::le::start_le_monitor;
|
||||
|
||||
#[derive(Parser)]
|
||||
struct Args {
|
||||
@@ -58,6 +59,14 @@ async fn main() -> bluer::Result<()> {
|
||||
let adapter = session.default_adapter().await?;
|
||||
adapter.set_powered(true).await?;
|
||||
|
||||
// Start LE monitor for Apple devices
|
||||
tokio::spawn(async {
|
||||
info!("Starting LE monitor...");
|
||||
if let Err(e) = start_le_monitor().await {
|
||||
log::error!("LE monitor error: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
info!("Listening for new connections.");
|
||||
|
||||
info!("Checking for connected devices...");
|
||||
@@ -79,7 +88,7 @@ async fn main() -> bluer::Result<()> {
|
||||
if !path.contains("/org/bluez/hci") || !path.contains("/dev_") {
|
||||
return true;
|
||||
}
|
||||
debug!("PropertiesChanged signal for path: {}", path);
|
||||
// debug!("PropertiesChanged signal for path: {}", path);
|
||||
let Ok((iface, changed, _)) = msg.read3::<String, HashMap<String, Variant<Box<dyn RefArg>>>, Vec<String>>() else {
|
||||
return true;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user