almost auths
This commit is contained in:
parent
0aef7feb32
commit
371937bf1f
1098
Cargo.lock
generated
1098
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -10,3 +10,10 @@ serde = { version = "1.0", features = ["derive"] }
|
|||
serde_json = "1.0"
|
||||
thiserror = "1.0"
|
||||
log = "0.4"
|
||||
reqwest = "0.11"
|
||||
x25519-dalek = { version = "2", features = ["getrandom"] }
|
||||
ed25519-dalek = { version = "2" }
|
||||
chacha20poly1305 = "0.10.1"
|
||||
hkdf = "0.12.4"
|
||||
sha2 = "0.10"
|
||||
hex = { version = "0.4", features = ["serde"] }
|
||||
|
|
|
@ -1,15 +1,23 @@
|
|||
use chacha20poly1305::{aead::generic_array::GenericArray, AeadInPlace, ChaCha20Poly1305, KeyInit};
|
||||
use ed25519_dalek::{Signer, Verifier};
|
||||
use hkdf::Hkdf;
|
||||
use reqwest::{Client, Method};
|
||||
use sha2::Sha512;
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use characteristics::{
|
||||
deserialize_characteristic_type, deserialize_service_type, CharacteristicType, ServiceType,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use tlv8::{TlvEncode, TlvType};
|
||||
use x25519_dalek::{EphemeralSecret, PublicKey};
|
||||
|
||||
mod characteristics;
|
||||
use pairing_data::DevicePairingData;
|
||||
|
||||
use crate::tlv8::{decode, TlvEncodableData};
|
||||
|
||||
mod pairing_data;
|
||||
mod tlv8;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HomekitController {
|
||||
client: Client,
|
||||
devices: HashMap<String, DevicePairingData>,
|
||||
}
|
||||
|
||||
|
@ -17,96 +25,257 @@ impl HomekitController {
|
|||
pub fn load(pairing_data: PathBuf) -> Result<Self, HomekitError> {
|
||||
let devices: HashMap<String, DevicePairingData> =
|
||||
serde_json::from_str(&std::fs::read_to_string(pairing_data)?)?;
|
||||
let client = Client::builder().build()?;
|
||||
Ok(Self { client, devices })
|
||||
}
|
||||
|
||||
Ok(Self { devices })
|
||||
pub async fn connect_to(&self, device_name: &str) -> Result<(), HomekitError> {
|
||||
if let Some(device) = self.devices.get(device_name) {
|
||||
device.connect(&self.client).await
|
||||
} else {
|
||||
Err(HomekitError::NoPairingData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct DevicePairingData {
|
||||
#[serde(rename = "AccessoryPairingID")]
|
||||
accessory_pairing_id: String,
|
||||
#[serde(rename = "AccessoryLTPK")]
|
||||
accessory_ltpk: String,
|
||||
#[serde(rename = "iOSPairingId")]
|
||||
ios_pairing_id: String,
|
||||
#[serde(rename = "iOSDeviceLTSK")]
|
||||
ios_device_ltsk: String,
|
||||
#[serde(rename = "iOSDeviceLTPK")]
|
||||
ios_device_ltpk: String,
|
||||
#[serde(rename = "AccessoryIP")]
|
||||
accessory_ip: String,
|
||||
#[serde(rename = "AccessoryPort")]
|
||||
accessory_port: usize,
|
||||
#[serde(rename = "Connection")]
|
||||
connection: ConnectionType,
|
||||
#[serde(rename = "accessories")]
|
||||
accessories: Vec<Accessory>,
|
||||
impl DevicePairingData {
|
||||
pub async fn connect(&self, client: &Client) -> Result<(), HomekitError> {
|
||||
let key = EphemeralSecret::random();
|
||||
let pubkey = PublicKey::from(&key);
|
||||
let step1_response = verify_request(
|
||||
&self.accessory_ip,
|
||||
self.accessory_port,
|
||||
&[
|
||||
(
|
||||
TlvType::State.into(),
|
||||
(HomekitState::M1 as u8).encode_value(),
|
||||
),
|
||||
(TlvType::PublicKey.into(), pubkey.as_bytes().encode_value()),
|
||||
],
|
||||
client,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if step1_response
|
||||
.get(&TlvType::State.into())
|
||||
.ok_or(HomekitError::TlvNotFound)?[0]
|
||||
!= (HomekitState::M2 as u8)
|
||||
{
|
||||
return Err(HomekitError::StateMismatch);
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Accessory {
|
||||
#[serde(rename = "aid")]
|
||||
id: usize,
|
||||
services: Vec<AccessoryService>,
|
||||
let response_pubkey = step1_response
|
||||
.get(&TlvType::PublicKey.into())
|
||||
.ok_or(HomekitError::TlvNotFound)?;
|
||||
|
||||
let response_encrypted_data = step1_response
|
||||
.get(&TlvType::EncryptedData.into())
|
||||
.ok_or(HomekitError::TlvNotFound)?;
|
||||
|
||||
let accessory_pubkey_bytes: Box<[u8; 32]> =
|
||||
response_pubkey.clone().into_boxed_slice().try_into()?;
|
||||
let accessory_pubkey = x25519_dalek::PublicKey::from(*accessory_pubkey_bytes);
|
||||
let shared_secret = key.diffie_hellman(&accessory_pubkey);
|
||||
|
||||
let hk: Hkdf<Sha512> = Hkdf::<Sha512>::new(
|
||||
Some("Pair-Verify-Encrypt-Salt".as_bytes()),
|
||||
shared_secret.as_bytes(),
|
||||
);
|
||||
let mut session_key = [0; 32];
|
||||
hk.expand("Pair-Verify-Encrypt-Info".as_bytes(), &mut session_key)?;
|
||||
|
||||
let (encrypted, authtag) =
|
||||
response_encrypted_data.split_at((response_encrypted_data.len()) - 16);
|
||||
let mut encrypted = encrypted.to_vec();
|
||||
|
||||
let chacha = ChaCha20Poly1305::new_from_slice(&session_key)?;
|
||||
let nonce: [u8; 12] = [0; 4]
|
||||
.iter()
|
||||
.chain("PV-Msg02".as_bytes())
|
||||
.copied()
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()?;
|
||||
|
||||
chacha.decrypt_in_place_detached(
|
||||
GenericArray::from_slice(&nonce),
|
||||
&[],
|
||||
&mut encrypted,
|
||||
authtag.into(),
|
||||
)?;
|
||||
|
||||
let decrypted = decode(&encrypted)?;
|
||||
|
||||
let accessory_identifier = decrypted
|
||||
.get(&TlvType::Identifier.into())
|
||||
.ok_or(HomekitError::TlvNotFound)?;
|
||||
let accessory_signature = decrypted
|
||||
.get(&TlvType::Signature.into())
|
||||
.ok_or(HomekitError::TlvNotFound)?;
|
||||
|
||||
if accessory_identifier != self.accessory_pairing_id.as_bytes() {
|
||||
return Err(HomekitError::Auth);
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct AccessoryService {
|
||||
#[serde(rename = "iid")]
|
||||
id: usize,
|
||||
#[serde(rename = "type", deserialize_with = "deserialize_service_type")]
|
||||
service_type: ServiceType,
|
||||
primary: bool,
|
||||
hidden: bool,
|
||||
characteristics: Vec<ServiceCharacteristic>,
|
||||
let accessory_ltpk = ed25519_dalek::VerifyingKey::from_bytes(&self.accessory_ltpk)?;
|
||||
let mut accessory_info = accessory_pubkey_bytes.to_vec();
|
||||
accessory_info.extend_from_slice(accessory_identifier);
|
||||
accessory_info.extend_from_slice(pubkey.as_bytes());
|
||||
log::info!("info len: {}", accessory_info.len());
|
||||
|
||||
let accessory_signature_bytes: Box<[u8; 64]> =
|
||||
accessory_signature.clone().into_boxed_slice().try_into()?;
|
||||
accessory_ltpk.verify(
|
||||
&accessory_info,
|
||||
&ed25519_dalek::Signature::from_bytes(&accessory_signature_bytes),
|
||||
)?;
|
||||
|
||||
let ios_device_info = {
|
||||
let mut buf = pubkey.as_bytes().to_vec();
|
||||
buf.extend_from_slice(self.ios_pairing_id.as_bytes());
|
||||
buf.extend_from_slice(pubkey.as_bytes());
|
||||
buf
|
||||
};
|
||||
let signing_key = ed25519_dalek::SigningKey::from_bytes(&self.ios_device_ltsk);
|
||||
let signature = signing_key.sign(&ios_device_info);
|
||||
|
||||
let mut encrypted_tlv = //tlv8::encode(&);
|
||||
([
|
||||
(
|
||||
TlvType::Identifier.into(),
|
||||
self.ios_pairing_id.encode_value(),
|
||||
),
|
||||
(
|
||||
TlvType::Signature.into(),
|
||||
(&signature.to_bytes()).encode_value(),
|
||||
),
|
||||
]).encode();
|
||||
|
||||
let nonce: [u8; 12] = [0; 4]
|
||||
.iter()
|
||||
.chain("PV-Msg03".as_bytes())
|
||||
.copied()
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()?;
|
||||
chacha.encrypt_in_place_detached(
|
||||
GenericArray::from_slice(&nonce),
|
||||
&[],
|
||||
&mut encrypted_tlv,
|
||||
)?;
|
||||
|
||||
let step3_response = verify_request(
|
||||
&self.accessory_ip,
|
||||
self.accessory_port,
|
||||
&[
|
||||
(
|
||||
TlvType::State.into(),
|
||||
(HomekitState::M3 as u8).encode_value(),
|
||||
),
|
||||
(
|
||||
TlvType::EncryptedData.into(),
|
||||
(&encrypted_tlv as &[u8]).encode_value(),
|
||||
),
|
||||
],
|
||||
client,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if step3_response
|
||||
.get(&TlvType::State.into())
|
||||
.ok_or(HomekitError::TlvNotFound)?[0]
|
||||
!= (HomekitState::M4 as u8)
|
||||
{
|
||||
return Err(HomekitError::StateMismatch);
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ServiceCharacteristic {
|
||||
#[serde(rename = "iid")]
|
||||
id: usize,
|
||||
#[serde(rename = "type", deserialize_with = "deserialize_characteristic_type")]
|
||||
characteristic_type: CharacteristicType,
|
||||
perms: Vec<String>,
|
||||
#[serde(flatten)]
|
||||
value: Option<Data>,
|
||||
ev: Option<bool>,
|
||||
enc: Option<bool>,
|
||||
#[serde(rename = "maxDataLen")]
|
||||
max_data_len: Option<usize>,
|
||||
#[serde(rename = "minValue")]
|
||||
min_value: Option<f64>,
|
||||
#[serde(rename = "maxValue")]
|
||||
max_value: Option<f64>,
|
||||
#[serde(rename = "minStep")]
|
||||
min_step: Option<f64>,
|
||||
unit: Option<String>,
|
||||
let hk: Hkdf<Sha512> =
|
||||
Hkdf::<Sha512>::new(Some("Control-Salt".as_bytes()), shared_secret.as_bytes());
|
||||
let mut controller_to_accessory_key = [0; 32];
|
||||
hk.expand(
|
||||
"Control-Write-Encryption-Key".as_bytes(),
|
||||
&mut controller_to_accessory_key,
|
||||
)?;
|
||||
let hk: Hkdf<Sha512> =
|
||||
Hkdf::<Sha512>::new(Some("Control-Salt".as_bytes()), shared_secret.as_bytes());
|
||||
let mut accessory_to_controller_key = [0; 32];
|
||||
hk.expand(
|
||||
"Control-Read-Encryption-Key".as_bytes(),
|
||||
&mut accessory_to_controller_key,
|
||||
)?;
|
||||
|
||||
// now get characteristics
|
||||
get_characteristics(
|
||||
&self.accessory_ip,
|
||||
self.accessory_port,
|
||||
&self
|
||||
.accessories
|
||||
.iter()
|
||||
.flat_map(|v| v.get_service_ids())
|
||||
.collect::<Vec<_>>(),
|
||||
client,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(tag = "format", content = "value")]
|
||||
pub enum Data {
|
||||
#[serde(rename = "string")]
|
||||
String(String),
|
||||
#[serde(rename = "bool")]
|
||||
Bool(bool),
|
||||
#[serde(rename = "data")]
|
||||
Data(String),
|
||||
#[serde(rename = "uint32")]
|
||||
Uint(u32),
|
||||
#[serde(rename = "float")]
|
||||
Float(f64),
|
||||
async fn verify_request(
|
||||
ip: &str,
|
||||
port: usize,
|
||||
data: &[(u8, Vec<u8>)],
|
||||
client: &Client,
|
||||
) -> Result<HashMap<u8, Vec<u8>>, HomekitError> {
|
||||
let uri = format!("http://{ip}:{port}/pair-verify");
|
||||
log::info!("request uri: {uri}");
|
||||
let encoded = data.encode();
|
||||
let request = client
|
||||
.post(uri)
|
||||
.header("Content-Type", "application/pairing+tlv8")
|
||||
.header("Content-Length", encoded.len())
|
||||
.body(encoded)
|
||||
.build()?;
|
||||
|
||||
match client.execute(request).await {
|
||||
Ok(val) => Ok(tlv8::decode(val.bytes().await?.as_ref())?),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||
pub enum ConnectionType {
|
||||
#[serde(rename = "IP")]
|
||||
Ip,
|
||||
#[serde(rename = "BLE")]
|
||||
Ble,
|
||||
#[serde(rename = "ADDITIONAL_PAIRING")]
|
||||
AdditionalPairing,
|
||||
async fn get_characteristics(
|
||||
ip: &str,
|
||||
port: usize,
|
||||
characteristics: &[String],
|
||||
client: &Client,
|
||||
) -> Result<(), HomekitError> {
|
||||
let characteristic_request = characteristics.join(",");
|
||||
let uri = format!(
|
||||
"http://{ip}:{port}/characteristics?id={characteristic_request}&meta=1&perms=1&type=1&ev=1"
|
||||
);
|
||||
let request = client.request(Method::GET, uri).build()?;
|
||||
|
||||
log::info!("request: {request:?}");
|
||||
match client.execute(request).await {
|
||||
Ok(val) => {
|
||||
log::info!("characteristic get request successful:\n{val:#?}");
|
||||
let bytes = val.bytes().await?.to_vec();
|
||||
log::info!(
|
||||
"str: {:?}, bytes: {bytes:?}",
|
||||
String::from_utf8(bytes.clone())
|
||||
);
|
||||
log::info!("parsed: {:#?}", tlv8::decode(&bytes));
|
||||
}
|
||||
|
||||
Err(e) => log::error!("characteristic get request error {e:?}"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub enum HomekitState {
|
||||
M1 = 1,
|
||||
M2 = 2,
|
||||
M3 = 3,
|
||||
M4 = 4,
|
||||
M5 = 5,
|
||||
M6 = 6,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
@ -115,4 +284,52 @@ pub enum HomekitError {
|
|||
Io(#[from] std::io::Error),
|
||||
#[error("serde_json")]
|
||||
SerdeJson(#[from] serde_json::Error),
|
||||
#[error("no device pairing data stored")]
|
||||
NoPairingData,
|
||||
#[error("http transport error")]
|
||||
Http(#[from] reqwest::Error),
|
||||
#[error("tlv error")]
|
||||
Tlv(#[from] tlv8::TlvError),
|
||||
#[error("mismatch with state response")]
|
||||
StateMismatch,
|
||||
#[error("Tlv not found")]
|
||||
TlvNotFound,
|
||||
#[error("length mismatch")]
|
||||
LengthMismatch,
|
||||
#[error("hkdf error")]
|
||||
Hkdf,
|
||||
#[error("invalid length")]
|
||||
InvalidLength(#[from] sha2::digest::InvalidLength),
|
||||
#[error("slice length error")]
|
||||
SliceLength(#[from] std::array::TryFromSliceError),
|
||||
#[error("chacha")]
|
||||
Chacha,
|
||||
#[error("auth mismatch")]
|
||||
Auth,
|
||||
#[error("ed25519")]
|
||||
Ed25519(#[from] ed25519_dalek::ed25519::Error),
|
||||
}
|
||||
|
||||
impl From<chacha20poly1305::Error> for HomekitError {
|
||||
fn from(_: chacha20poly1305::Error) -> Self {
|
||||
Self::Chacha
|
||||
}
|
||||
}
|
||||
|
||||
impl From<hkdf::InvalidLength> for HomekitError {
|
||||
fn from(_: hkdf::InvalidLength) -> Self {
|
||||
Self::Hkdf
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<[u8]>> for HomekitError {
|
||||
fn from(_: Box<[u8]>) -> Self {
|
||||
Self::LengthMismatch
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for HomekitError {
|
||||
fn from(_: Vec<u8>) -> Self {
|
||||
Self::LengthMismatch
|
||||
}
|
||||
}
|
||||
|
|
|
@ -239,6 +239,241 @@ pub enum CharacteristicType {
|
|||
Unknown,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CharacteristicType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::AccessControlLevel => "000000E5-0000-1000-8000-0026BB765291",
|
||||
Self::AccessoryFlags => "000000A6-0000-1000-8000-0026BB765291",
|
||||
Self::ProductData => "00000220-0000-1000-8000-0026BB765291",
|
||||
Self::Active => "000000B0-0000-1000-8000-0026BB765291",
|
||||
Self::AdministratorOnlyAccess => "00000001-0000-1000-8000-0026BB765291",
|
||||
Self::AirParticulateDensity => "00000064-0000-1000-8000-0026BB765291",
|
||||
Self::AirParticulateSize => "00000065-0000-1000-8000-0026BB765291",
|
||||
Self::AirQuality => "00000095-0000-1000-8000-0026BB765291",
|
||||
Self::AudioFeedback => "00000005-0000-1000-8000-0026BB765291",
|
||||
Self::BatteryLevel => "00000068-0000-1000-8000-0026BB765291",
|
||||
Self::Brightness => "00000008-0000-1000-8000-0026BB765291",
|
||||
Self::CarbonDioxideDetected => "00000092-0000-1000-8000-0026BB765291",
|
||||
Self::CarbonDioxideLevel => "00000093-0000-1000-8000-0026BB765291",
|
||||
Self::CarbonDioxidePeakLevel => "00000094-0000-1000-8000-0026BB765291",
|
||||
Self::CarbonMonoxideDetected => "00000069-0000-1000-8000-0026BB765291",
|
||||
Self::CarbonMonoxideLevel => "00000090-0000-1000-8000-0026BB765291",
|
||||
Self::CarbonMonoxidePeakLevel => "00000091-0000-1000-8000-0026BB765291",
|
||||
Self::ChargingState => "0000008F-0000-1000-8000-0026BB765291",
|
||||
Self::ColorTemperature => "000000CE-0000-1000-8000-0026BB765291",
|
||||
Self::ContactSensorState => "0000006A-0000-1000-8000-0026BB765291",
|
||||
Self::CoolingThresholdTemperature => "0000000D-0000-1000-8000-0026BB765291",
|
||||
Self::CurrentAirPurifierState => "000000A9-0000-1000-8000-0026BB765291",
|
||||
Self::CurrentAmbientLightLevel => "0000006B-0000-1000-8000-0026BB765291",
|
||||
Self::CurrentDoorState => "0000000E-0000-1000-8000-0026BB765291",
|
||||
Self::CurrentFanState => "000000AF-0000-1000-8000-0026BB765291",
|
||||
Self::CurrentHeaterCoolerState => "000000B1-0000-1000-8000-0026BB765291",
|
||||
Self::CurrentHeatingCoolingState => "0000000F-0000-1000-8000-0026BB765291",
|
||||
Self::CurrentHorizontalTiltAngle => "0000006C-0000-1000-8000-0026BB765291",
|
||||
Self::CurrentHumidifierDehumidifierState => "000000B3-0000-1000-8000-0026BB765291",
|
||||
Self::CurrentPosition => "0000006D-0000-1000-8000-0026BB765291",
|
||||
Self::CurrentRelativeHumidity => "00000010-0000-1000-8000-0026BB765291",
|
||||
Self::CurrentSlatState => "000000AA-0000-1000-8000-0026BB765291",
|
||||
Self::CurrentTemperature => "00000011-0000-1000-8000-0026BB765291",
|
||||
Self::CurrentTiltAngle => "000000C1-0000-1000-8000-0026BB765291",
|
||||
Self::CurrentVerticalTiltAngle => "0000006E-0000-1000-8000-0026BB765291",
|
||||
Self::DigitalZoom => "0000011D-0000-1000-8000-0026BB765291",
|
||||
Self::FilterChangeIndication => "000000AC-0000-1000-8000-0026BB765291",
|
||||
Self::FilterLifeLevel => "000000AB-0000-1000-8000-0026BB765291",
|
||||
Self::FirmwareRevision => "00000052-0000-1000-8000-0026BB765291",
|
||||
Self::HardwareRevision => "00000053-0000-1000-8000-0026BB765291",
|
||||
Self::HeatingThresholdTemperature => "00000012-0000-1000-8000-0026BB765291",
|
||||
Self::HoldPosition => "0000006F-0000-1000-8000-0026BB765291",
|
||||
Self::Hue => "00000013-0000-1000-8000-0026BB765291",
|
||||
Self::Identify => "00000014-0000-1000-8000-0026BB765291",
|
||||
Self::ImageMirroring => "0000011F-0000-1000-8000-0026BB765291",
|
||||
Self::ImageRotation => "0000011E-0000-1000-8000-0026BB765291",
|
||||
Self::InUse => "000000D2-0000-1000-8000-0026BB765291",
|
||||
Self::IsConfigured => "000000D6-0000-1000-8000-0026BB765291",
|
||||
Self::LeakDetected => "00000070-0000-1000-8000-0026BB765291",
|
||||
Self::LockControlPoint => "00000019-0000-1000-8000-0026BB765291",
|
||||
Self::LockCurrentState => "0000001D-0000-1000-8000-0026BB765291",
|
||||
Self::LockLastKnownAction => "0000001C-0000-1000-8000-0026BB765291",
|
||||
Self::LockManagementAutoSecurityTimeout => "0000001A-0000-1000-8000-0026BB765291",
|
||||
Self::LockPhysicalControls => "000000A7-0000-1000-8000-0026BB765291",
|
||||
Self::LockTargetState => "0000001E-0000-1000-8000-0026BB765291",
|
||||
Self::Logs => "0000001F-0000-1000-8000-0026BB765291",
|
||||
Self::Manufacturer => "00000020-0000-1000-8000-0026BB765291",
|
||||
Self::Model => "00000021-0000-1000-8000-0026BB765291",
|
||||
Self::MotionDetected => "00000022-0000-1000-8000-0026BB765291",
|
||||
Self::Mute => "0000011A-0000-1000-8000-0026BB765291",
|
||||
Self::Name => "00000023-0000-1000-8000-0026BB765291",
|
||||
Self::NightVision => "0000011B-0000-1000-8000-0026BB765291",
|
||||
Self::NitrogenDioxideDensity => "000000C4-0000-1000-8000-0026BB765291",
|
||||
Self::ObstructionDetected => "00000024-0000-1000-8000-0026BB765291",
|
||||
Self::OccupancyDetected => "00000071-0000-1000-8000-0026BB765291",
|
||||
Self::On => "00000025-0000-1000-8000-0026BB765291",
|
||||
Self::OpticalZoom => "0000011C-0000-1000-8000-0026BB765291",
|
||||
Self::OutletInUse => "00000026-0000-1000-8000-0026BB765291",
|
||||
Self::OzoneDensity => "000000C3-0000-1000-8000-0026BB765291",
|
||||
Self::PairSetup => "0000004C-0000-1000-8000-0026BB765291",
|
||||
Self::PairVerify => "0000004E-0000-1000-8000-0026BB765291",
|
||||
Self::PairingFeatures => "0000004F-0000-1000-8000-0026BB765291",
|
||||
Self::PairingPairings => "00000050-0000-1000-8000-0026BB765291",
|
||||
Self::PasswordSetting => "000000E4-0000-1000-8000-0026BB765291",
|
||||
Self::PM10Density => "000000C7-0000-1000-8000-0026BB765291",
|
||||
Self::PM2_5Density => "000000C6-0000-1000-8000-0026BB765291",
|
||||
Self::PositionState => "00000072-0000-1000-8000-0026BB765291",
|
||||
Self::ProgramMode => "000000D1-0000-1000-8000-0026BB765291",
|
||||
Self::ProgrammableSwitchEvent => "00000073-0000-1000-8000-0026BB765291",
|
||||
Self::RelativeHumidityDehumidifierThreshold => "000000C9-0000-1000-8000-0026BB765291",
|
||||
Self::RelativeHumidityHumidifierThreshold => "000000CA-0000-1000-8000-0026BB765291",
|
||||
Self::RemainingDuration => "000000D4-0000-1000-8000-0026BB765291",
|
||||
Self::ResetFilterIndication => "000000AD-0000-1000-8000-0026BB765291",
|
||||
Self::RotationDirection => "00000028-0000-1000-8000-0026BB765291",
|
||||
Self::RotationSpeed => "00000029-0000-1000-8000-0026BB765291",
|
||||
Self::Saturation => "0000002F-0000-1000-8000-0026BB765291",
|
||||
Self::SecuritySystemAlarmType => "0000008E-0000-1000-8000-0026BB765291",
|
||||
Self::SecuritySystemCurrentState => "00000066-0000-1000-8000-0026BB765291",
|
||||
Self::SecuritySystemTargetState => "00000067-0000-1000-8000-0026BB765291",
|
||||
Self::SelectedRTPStreamConfiguration => "00000117-0000-1000-8000-0026BB765291",
|
||||
Self::SerialNumber => "00000030-0000-1000-8000-0026BB765291",
|
||||
Self::ServiceLabelIndex => "000000CB-0000-1000-8000-0026BB765291",
|
||||
Self::ServiceLabelNamespace => "000000CD-0000-1000-8000-0026BB765291",
|
||||
Self::SetDuration => "000000D3-0000-1000-8000-0026BB765291",
|
||||
Self::SetupEndpoints => "00000118-0000-1000-8000-0026BB765291",
|
||||
Self::SlatType => "000000C0-0000-1000-8000-0026BB765291",
|
||||
Self::SmokeDetected => "00000076-0000-1000-8000-0026BB765291",
|
||||
Self::StatusActive => "00000075-0000-1000-8000-0026BB765291",
|
||||
Self::StatusFault => "00000077-0000-1000-8000-0026BB765291",
|
||||
Self::StatusJammed => "00000078-0000-1000-8000-0026BB765291",
|
||||
Self::StatusLowBattery => "00000079-0000-1000-8000-0026BB765291",
|
||||
Self::StatusTampered => "0000007A-0000-1000-8000-0026BB765291",
|
||||
Self::StreamingStatus => "00000120-0000-1000-8000-0026BB765291",
|
||||
Self::SulphurDioxideDensity => "000000C5-0000-1000-8000-0026BB765291",
|
||||
Self::SupportedAudioStreamConfiguration => "00000115-0000-1000-8000-0026BB765291",
|
||||
Self::SupportedRTPConfiguration => "00000116-0000-1000-8000-0026BB765291",
|
||||
Self::SupportedVideoStreamConfiguration => "00000114-0000-1000-8000-0026BB765291",
|
||||
Self::SwingMode => "000000B6-0000-1000-8000-0026BB765291",
|
||||
Self::TargetAirPurifierState => "000000A8-0000-1000-8000-0026BB765291",
|
||||
Self::TargetAirQuality => "000000AE-0000-1000-8000-0026BB765291",
|
||||
Self::TargetDoorState => "00000032-0000-1000-8000-0026BB765291",
|
||||
Self::TargetFanState => "000000BF-0000-1000-8000-0026BB765291",
|
||||
Self::TargetHeaterCoolerState => "000000B2-0000-1000-8000-0026BB765291",
|
||||
Self::TargetHeatingCoolingState => "00000033-0000-1000-8000-0026BB765291",
|
||||
Self::TargetHorizontalTiltAngle => "0000007B-0000-1000-8000-0026BB765291",
|
||||
Self::TargetHumidifierDehumidifierState => "000000B4-0000-1000-8000-0026BB765291",
|
||||
Self::TargetPosition => "0000007C-0000-1000-8000-0026BB765291",
|
||||
Self::TargetRelativeHumidity => "00000034-0000-1000-8000-0026BB765291",
|
||||
Self::TargetSlatState => "000000BE-0000-1000-8000-0026BB765291",
|
||||
Self::TargetTemperature => "00000035-0000-1000-8000-0026BB765291",
|
||||
Self::TargetTiltAngle => "000000C2-0000-1000-8000-0026BB765291",
|
||||
Self::TargetVerticalTiltAngle => "0000007D-0000-1000-8000-0026BB765291",
|
||||
Self::TemperatureDisplayUnits => "00000036-0000-1000-8000-0026BB765291",
|
||||
Self::ValveType => "000000D5-0000-1000-8000-0026BB765291",
|
||||
Self::Version => "00000037-0000-1000-8000-0026BB765291",
|
||||
Self::VOCDensity => "000000C8-0000-1000-8000-0026BB765291",
|
||||
Self::Volume => "00000119-0000-1000-8000-0026BB765291",
|
||||
Self::WaterLevel => "000000B5-0000-1000-8000-0026BB765291",
|
||||
Self::RecordingAudioActive => "00000226-0000-1000-8000-0026BB765291",
|
||||
Self::SupportedCameraRecordingConfiguration => "00000205-0000-1000-8000-0026BB765291",
|
||||
Self::SupportedVideoRecordingConfiguration => "00000206-0000-1000-8000-0026BB765291",
|
||||
Self::SupportedAudioRecordingConfiguration => "00000207-0000-1000-8000-0026BB765291",
|
||||
Self::SelectedCameraRecordingConfiguration => "00000209-0000-1000-8000-0026BB765291",
|
||||
Self::CameraOperatingModeIndicator => "0000021D-0000-1000-8000-0026BB765291",
|
||||
Self::EventSnapshotsActive => "00000223-0000-1000-8000-0026BB765291",
|
||||
Self::DiagonalFieldOfView => "00000224-0000-1000-8000-0026BB765291",
|
||||
Self::HomeKitCameraActive => "0000021B-0000-1000-8000-0026BB765291",
|
||||
Self::ManuallyDisabled => "00000227-0000-1000-8000-0026BB765291",
|
||||
Self::ThirdPartyCameraActive => "0000021C-0000-1000-8000-0026BB765291",
|
||||
Self::PeriodicSnapshotsActive => "00000225-0000-1000-8000-0026BB765291",
|
||||
Self::NetworkClientProfileControl => "0000020C-0000-1000-8000-0026BB765291",
|
||||
Self::NetworkClientStatusControl => "0000020D-0000-1000-8000-0026BB765291",
|
||||
Self::RouterStatus => "0000020E-0000-1000-8000-0026BB765291",
|
||||
Self::SupportedRouterConfiguration => "00000210-0000-1000-8000-0026BB765291",
|
||||
Self::WANConfigurationList => "00000211-0000-1000-8000-0026BB765291",
|
||||
Self::WANStatusList => "00000212-0000-1000-8000-0026BB765291",
|
||||
Self::ManagedNetworkEnable => "00000215-0000-1000-8000-0026BB765291",
|
||||
Self::NetworkAccessViolationControl => "0000021F-0000-1000-8000-0026BB765291",
|
||||
Self::WiFiSatelliteStatus => "0000021E-0000-1000-8000-0026BB765291",
|
||||
Self::WakeConfiguration => "00000222-0000-1000-8000-0026BB765291",
|
||||
Self::SupportedTransferTransportConfiguration => "00000202-0000-1000-8000-0026BB765291",
|
||||
Self::SetupTransferTransport => "00000201-0000-1000-8000-0026BB765291",
|
||||
Self::ActivityInterval => "0000021E-0000-1000-8000-0000023B",
|
||||
Self::CCAEnergyDetectThreshold => "0000021E-0000-1000-8000-00000246",
|
||||
Self::CCASignalDetectThreshold => "0000021E-0000-1000-8000-00000245",
|
||||
Self::CharacteristicValueTransitionControl => "0000021E-0000-1000-8000-00000143",
|
||||
Self::SupportedCharacteristicValueTransitionConfiguration => {
|
||||
"0000021E-0000-1000-8000-00000144"
|
||||
}
|
||||
Self::CurrentTransport => "0000021E-0000-1000-8000-0000022B",
|
||||
Self::DataStreamHAPTransport => "0000021E-0000-1000-8000-00000138",
|
||||
Self::DataStreamHAPTransportInterrupt => "0000021E-0000-1000-8000-00000139",
|
||||
Self::EventRetransmissionMaximum => "0000021E-0000-1000-8000-0000023D",
|
||||
Self::EventTransmissionCounters => "0000021E-0000-1000-8000-0000023E",
|
||||
Self::HeartBeat => "0000021E-0000-1000-8000-0000024A",
|
||||
Self::MACRetransmissionMaximum => "0000021E-0000-1000-8000-00000247",
|
||||
Self::MACTransmissionCounters => "0000021E-0000-1000-8000-00000248",
|
||||
Self::OperatingStateResponse => "0000021E-0000-1000-8000-00000232",
|
||||
Self::Ping => "0000021E-0000-1000-8000-0000023C",
|
||||
Self::ReceiverSensitivity => "0000021E-0000-1000-8000-00000244",
|
||||
Self::ReceivedSignalStrengthIndication => "0000021E-0000-1000-8000-0000023F",
|
||||
Self::SleepInterval => "0000021E-0000-1000-8000-0000023A",
|
||||
Self::SignalToNoiseRatio => "0000021E-0000-1000-8000-00000241",
|
||||
Self::SupportedDiagnosticsSnapshot => "0000021E-0000-1000-8000-00000238",
|
||||
Self::TransmitPower => "0000021E-0000-1000-8000-00000242",
|
||||
Self::TransmitPowerMaximum => "0000021E-0000-1000-8000-00000243",
|
||||
Self::VideoAnalysisActive => "0000021E-0000-1000-8000-00000229",
|
||||
Self::WiFiCapabilities => "0000021E-0000-1000-8000-0000022C",
|
||||
Self::WiFiConfigurationControl => "0000021E-0000-1000-8000-0000022D",
|
||||
Self::AppMatchingIdentifier => "000000A4-0000-1000-8000-0026BB765291",
|
||||
Self::ProgrammableSwitchOutputState => "00000074-0000-1000-8000-0026BB765291",
|
||||
Self::SoftwareRevision => "00000054-0000-1000-8000-0026BB765291",
|
||||
Self::AccessoryIdentifier => "00000057-0000-1000-8000-0026BB765291",
|
||||
Self::Category => "000000A3-0000-1000-8000-0026BB765291",
|
||||
Self::ConfigureBridgedAccessory => "000000A0-0000-1000-8000-0026BB765291",
|
||||
Self::ConfigureBridgedAccessoryStatus => "0000009D-0000-1000-8000-0026BB765291",
|
||||
Self::CurrentTime => "0000009B-0000-1000-8000-0026BB765291",
|
||||
Self::DayoftheWeek => "00000098-0000-1000-8000-0026BB765291",
|
||||
Self::DiscoverBridgedAccessories => "0000009E-0000-1000-8000-0026BB765291",
|
||||
Self::DiscoveredBridgedAccessories => "0000009F-0000-1000-8000-0026BB765291",
|
||||
Self::LinkQuality => "0000009C-0000-1000-8000-0026BB765291",
|
||||
Self::Reachable => "00000063-0000-1000-8000-0026BB765291",
|
||||
Self::RelayControlPoint => "0000005E-0000-1000-8000-0026BB765291",
|
||||
Self::RelayEnabled => "0000005B-0000-1000-8000-0026BB765291",
|
||||
Self::RelayState => "0000005C-0000-1000-8000-0026BB765291",
|
||||
Self::TimeUpdate => "0000009A-0000-1000-8000-0026BB765291",
|
||||
Self::TunnelConnectionTimeout => "00000061-0000-1000-8000-0026BB765291",
|
||||
Self::TunneledAccessoryAdvertising => "00000060-0000-1000-8000-0026BB765291",
|
||||
Self::TunneledAccessoryConnected => "00000059-0000-1000-8000-0026BB765291",
|
||||
Self::TunneledAccessoryStateNumber => "00000058-0000-1000-8000-0026BB765291",
|
||||
Self::ActiveIdentifier => "000000E7-0000-1000-8000-0026BB765291",
|
||||
Self::ConfiguredName => "000000E3-0000-1000-8000-0026BB765291",
|
||||
Self::SleepDiscoveryMode => "000000E8-0000-1000-8000-0026BB765291",
|
||||
Self::ClosedCaptions => "000000DD-0000-1000-8000-0026BB765291",
|
||||
Self::DisplayOrder => "00000136-0000-1000-8000-0026BB765291",
|
||||
Self::CurrentMediaState => "000000E0-0000-1000-8000-0026BB765291",
|
||||
Self::TargetMediaState => "00000137-0000-1000-8000-0026BB765291",
|
||||
Self::PictureMode => "000000E2-0000-1000-8000-0026BB765291",
|
||||
Self::PowerModeSelection => "000000DF-0000-1000-8000-0026BB765291",
|
||||
Self::RemoteKey => "000000E1-0000-1000-8000-0026BB765291",
|
||||
Self::InputSourceType => "000000DB-0000-1000-8000-0026BB765291",
|
||||
Self::InputDeviceType => "000000DC-0000-1000-8000-0026BB765291",
|
||||
Self::Identifier => "000000E6-0000-1000-8000-0026BB765291",
|
||||
Self::CurrentVisibilityState => "00000135-0000-1000-8000-0026BB765291",
|
||||
Self::TargetVisibilityState => "00000134-0000-1000-8000-0026BB765291",
|
||||
Self::VolumeControlType => "000000E9-0000-1000-8000-0026BB765291",
|
||||
Self::VolumeSelector => "000000EA-0000-1000-8000-0026BB765291",
|
||||
Self::TargetControlSupportedConfiguration => "00000123-0000-1000-8000-0026BB765291",
|
||||
Self::TargetControlList => "00000124-0000-1000-8000-0026BB765291",
|
||||
Self::ButtonEvent => "00000126-0000-1000-8000-0026BB765291",
|
||||
Self::SelectedAudioStreamConfiguration => "00000128-0000-1000-8000-0026BB765291",
|
||||
Self::SiriInputType => "00000132-0000-1000-8000-0026BB765291",
|
||||
Self::SupportedDataStreamTransportConfiguration => {
|
||||
"00000130-0000-1000-8000-0026BB765291"
|
||||
}
|
||||
Self::SetupDataStreamTransport => "00000131-0000-1000-8000-0026BB765291",
|
||||
Self::Unknown => "",
|
||||
}
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(field_identifier)]
|
||||
enum CharacteristicTypeInner {
|
||||
|
@ -1057,6 +1292,7 @@ pub enum ServiceType {
|
|||
TunneledBTLEAccessoryService,
|
||||
Television,
|
||||
InputSource,
|
||||
TelevisionSpeaker,
|
||||
TargetControlManagement,
|
||||
TargetControl,
|
||||
AudioStreamManagement,
|
||||
|
@ -1065,6 +1301,83 @@ pub enum ServiceType {
|
|||
Unknown,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ServiceType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::AccessControl => "000000DA-0000-1000-8000-0026BB765291",
|
||||
Self::AccessoryInformation => "0000003E-0000-1000-8000-0026BB765291",
|
||||
Self::AirPurifier => "000000BB-0000-1000-8000-0026BB765291",
|
||||
Self::AirQualitySensor => "0000008D-0000-1000-8000-0026BB765291",
|
||||
Self::BatteryService => "00000096-0000-1000-8000-0026BB765291",
|
||||
Self::CameraRTPStreamManagement => "00000110-0000-1000-8000-0026BB765291",
|
||||
Self::CarbonDioxideSensor => "00000097-0000-1000-8000-0026BB765291",
|
||||
Self::CarbonMonoxideSensor => "0000007F-0000-1000-8000-0026BB765291",
|
||||
Self::ContactSensor => "00000080-0000-1000-8000-0026BB765291",
|
||||
Self::Door => "00000081-0000-1000-8000-0026BB765291",
|
||||
Self::Doorbell => "00000121-0000-1000-8000-0026BB765291",
|
||||
Self::Fan => "00000040-0000-1000-8000-0026BB765291",
|
||||
Self::Fanv2 => "000000B7-0000-1000-8000-0026BB765291",
|
||||
Self::FilterMaintenance => "000000BA-0000-1000-8000-0026BB765291",
|
||||
Self::Faucet => "000000D7-0000-1000-8000-0026BB765291",
|
||||
Self::GarageDoorOpener => "00000041-0000-1000-8000-0026BB765291",
|
||||
Self::HeaterCooler => "000000BC-0000-1000-8000-0026BB765291",
|
||||
Self::HumidifierDehumidifier => "000000BD-0000-1000-8000-0026BB765291",
|
||||
Self::HumiditySensor => "00000082-0000-1000-8000-0026BB765291",
|
||||
Self::IrrigationSystem => "000000CF-0000-1000-8000-0026BB765291",
|
||||
Self::LeakSensor => "00000083-0000-1000-8000-0026BB765291",
|
||||
Self::LightSensor => "00000084-0000-1000-8000-0026BB765291",
|
||||
Self::Lightbulb => "00000043-0000-1000-8000-0026BB765291",
|
||||
Self::LockManagement => "00000044-0000-1000-8000-0026BB765291",
|
||||
Self::LockMechanism => "00000045-0000-1000-8000-0026BB765291",
|
||||
Self::Microphone => "00000112-0000-1000-8000-0026BB765291",
|
||||
Self::MotionSensor => "00000085-0000-1000-8000-0026BB765291",
|
||||
Self::OccupancySensor => "00000086-0000-1000-8000-0026BB765291",
|
||||
Self::Outlet => "00000047-0000-1000-8000-0026BB765291",
|
||||
Self::SecuritySystem => "0000007E-0000-1000-8000-0026BB765291",
|
||||
Self::ServiceLabel => "000000CC-0000-1000-8000-0026BB765291",
|
||||
Self::Slat => "000000B9-0000-1000-8000-0026BB765291",
|
||||
Self::SmokeSensor => "00000087-0000-1000-8000-0026BB765291",
|
||||
Self::SmartSpeaker => "00000228-0000-1000-8000-0026BB765291",
|
||||
Self::Speaker => "00000113-0000-1000-8000-0026BB765291",
|
||||
Self::StatelessProgrammableSwitch => "00000089-0000-1000-8000-0026BB765291",
|
||||
Self::Switch => "00000049-0000-1000-8000-0026BB765291",
|
||||
Self::TemperatureSensor => "0000008A-0000-1000-8000-0026BB765291",
|
||||
Self::Thermostat => "0000004A-0000-1000-8000-0026BB765291",
|
||||
Self::Valve => "000000D0-0000-1000-8000-0026BB765291",
|
||||
Self::Window => "0000008B-0000-1000-8000-0026BB765291",
|
||||
Self::WindowCovering => "0000008C-0000-1000-8000-0026BB765291",
|
||||
Self::CameraOperatingMode => "0000021A-0000-1000-8000-0026BB765291",
|
||||
Self::CameraEventRecordingManagement => "00000204-0000-1000-8000-0026BB765291",
|
||||
Self::WiFiRouter => "0000020A-0000-1000-8000-0026BB765291",
|
||||
Self::WiFiSatellite => "0000020F-0000-1000-8000-0026BB765291",
|
||||
Self::PowerManagement => "00000221-0000-1000-8000-0026BB765291",
|
||||
Self::TransferTransportManagement => "00000203-0000-1000-8000-0026BB765291",
|
||||
Self::AccessoryRuntimeInformation => "00000203-0000-1000-8000-00000239",
|
||||
Self::Diagnostics => "00000203-0000-1000-8000-00000237",
|
||||
Self::WiFiTransport => "00000203-0000-1000-8000-0000022A",
|
||||
Self::CameraControl => "00000111-0000-1000-8000-0026BB765291",
|
||||
Self::StatefulProgrammableSwitch => "00000088-0000-1000-8000-0026BB765291",
|
||||
Self::BridgeConfiguration => "000000A1-0000-1000-8000-0026BB765291",
|
||||
Self::BridgingState => "00000062-0000-1000-8000-0026BB765291",
|
||||
Self::Pairing => "00000055-0000-1000-8000-0026BB765291",
|
||||
Self::ProtocolInformation => "000000A2-0000-1000-8000-0026BB765291",
|
||||
Self::Relay => "0000005A-0000-1000-8000-0026BB765291",
|
||||
Self::TimeInformation => "00000099-0000-1000-8000-0026BB765291",
|
||||
Self::TunneledBTLEAccessoryService => "00000056-0000-1000-8000-0026BB765291",
|
||||
Self::Television => "000000D8-0000-1000-8000-0026BB765291",
|
||||
Self::InputSource => "000000D9-0000-1000-8000-0026BB765291",
|
||||
Self::TelevisionSpeaker => "00000113-0000-1000-8000-0026BB765291",
|
||||
Self::TargetControlManagement => "00000122-0000-1000-8000-0026BB765291",
|
||||
Self::TargetControl => "00000125-0000-1000-8000-0026BB765291",
|
||||
Self::AudioStreamManagement => "00000127-0000-1000-8000-0026BB765291",
|
||||
Self::Siri => "00000133-0000-1000-8000-0026BB765291",
|
||||
Self::DataStreamTransportManagement => "00000129-0000-1000-8000-0026BB765291",
|
||||
Self::Unknown => "",
|
||||
}
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(field_identifier)]
|
||||
pub enum ServiceTypeInner {
|
||||
|
@ -1192,6 +1505,8 @@ pub enum ServiceTypeInner {
|
|||
Television,
|
||||
#[serde(rename = "000000D9-0000-1000-8000-0026BB765291")]
|
||||
InputSource,
|
||||
#[serde(rename = "00000113-0000-1000-8000-0026BB765291")]
|
||||
TelevisionSpeaker,
|
||||
#[serde(rename = "00000122-0000-1000-8000-0026BB765291")]
|
||||
TargetControlManagement,
|
||||
#[serde(rename = "00000125-0000-1000-8000-0026BB765291")]
|
||||
|
@ -1276,6 +1591,7 @@ impl From<ServiceTypeInner> for ServiceType {
|
|||
ServiceTypeInner::TunneledBTLEAccessoryService => Self::TunneledBTLEAccessoryService,
|
||||
ServiceTypeInner::Television => Self::Television,
|
||||
ServiceTypeInner::InputSource => Self::InputSource,
|
||||
ServiceTypeInner::TelevisionSpeaker => Self::TelevisionSpeaker,
|
||||
ServiceTypeInner::TargetControlManagement => Self::TargetControlManagement,
|
||||
ServiceTypeInner::TargetControl => Self::TargetControl,
|
||||
ServiceTypeInner::AudioStreamManagement => Self::AudioStreamManagement,
|
103
homekit-controller/src/pairing_data/mod.rs
Normal file
103
homekit-controller/src/pairing_data/mod.rs
Normal file
|
@ -0,0 +1,103 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use characteristics::{deserialize_characteristic_type, deserialize_service_type};
|
||||
pub use characteristics::{CharacteristicType, ServiceType};
|
||||
|
||||
mod characteristics;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct DevicePairingData {
|
||||
#[serde(rename = "AccessoryPairingID")]
|
||||
pub accessory_pairing_id: String,
|
||||
#[serde(with = "hex::serde", rename = "AccessoryLTPK")]
|
||||
pub accessory_ltpk: [u8; 32],
|
||||
#[serde(rename = "iOSPairingId")]
|
||||
pub ios_pairing_id: String,
|
||||
#[serde(with = "hex::serde", rename = "iOSDeviceLTSK")]
|
||||
pub ios_device_ltsk: [u8; 32],
|
||||
#[serde(with = "hex::serde", rename = "iOSDeviceLTPK")]
|
||||
pub ios_device_ltpk: [u8; 32],
|
||||
#[serde(rename = "AccessoryIP")]
|
||||
pub accessory_ip: String,
|
||||
#[serde(rename = "AccessoryPort")]
|
||||
pub accessory_port: usize,
|
||||
#[serde(rename = "Connection")]
|
||||
pub connection: ConnectionType,
|
||||
#[serde(rename = "accessories")]
|
||||
pub accessories: Vec<Accessory>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Accessory {
|
||||
#[serde(rename = "aid")]
|
||||
pub id: usize,
|
||||
pub services: Vec<AccessoryService>,
|
||||
}
|
||||
|
||||
impl Accessory {
|
||||
pub fn get_service_ids(&self) -> Vec<String> {
|
||||
self.services
|
||||
.iter()
|
||||
.map(|service| format!("{}.{}", self.id, service.id))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct AccessoryService {
|
||||
#[serde(rename = "iid")]
|
||||
pub id: usize,
|
||||
#[serde(rename = "type", deserialize_with = "deserialize_service_type")]
|
||||
service_type: ServiceType,
|
||||
pub primary: bool,
|
||||
pub hidden: bool,
|
||||
pub characteristics: Vec<ServiceCharacteristic>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ServiceCharacteristic {
|
||||
#[serde(rename = "iid")]
|
||||
pub id: usize,
|
||||
#[serde(rename = "type", deserialize_with = "deserialize_characteristic_type")]
|
||||
pub characteristic_type: CharacteristicType,
|
||||
pub perms: Vec<String>,
|
||||
#[serde(flatten)]
|
||||
pub value: Option<Data>,
|
||||
pub ev: Option<bool>,
|
||||
pub enc: Option<bool>,
|
||||
#[serde(rename = "maxDataLen")]
|
||||
pub max_data_len: Option<usize>,
|
||||
#[serde(rename = "minValue")]
|
||||
pub min_value: Option<f64>,
|
||||
#[serde(rename = "maxValue")]
|
||||
pub max_value: Option<f64>,
|
||||
#[serde(rename = "minStep")]
|
||||
pub min_step: Option<f64>,
|
||||
pub unit: Option<String>,
|
||||
}
|
||||
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(tag = "format", content = "value")]
|
||||
pub enum Data {
|
||||
#[serde(rename = "string")]
|
||||
String(String),
|
||||
#[serde(rename = "bool")]
|
||||
Bool(bool),
|
||||
#[serde(rename = "data")]
|
||||
Data(String),
|
||||
#[serde(rename = "uint32")]
|
||||
Uint(u32),
|
||||
#[serde(rename = "float")]
|
||||
Float(f64),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||
pub enum ConnectionType {
|
||||
#[serde(rename = "IP")]
|
||||
Ip,
|
||||
#[serde(rename = "BLE")]
|
||||
Ble,
|
||||
#[serde(rename = "ADDITIONAL_PAIRING")]
|
||||
AdditionalPairing,
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
use super::Tlv;
|
||||
|
||||
pub enum TlvType {
|
||||
Method,
|
||||
Identifier,
|
||||
|
@ -43,109 +41,3 @@ impl From<TlvType> for u8 {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TlvData {
|
||||
Bytes(Vec<u8>),
|
||||
Tlv8(Vec<Tlv>),
|
||||
Integer(TlvInteger),
|
||||
Float(f64),
|
||||
String(String),
|
||||
Uint(TlvUnsignedInteger),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TlvInteger {
|
||||
OneByte(i8),
|
||||
TwoBytes(i16),
|
||||
FourBytes(i32),
|
||||
EightBytes(i64),
|
||||
}
|
||||
|
||||
impl TlvInteger {
|
||||
pub fn to_le_bytes(&self) -> Vec<u8> {
|
||||
match self {
|
||||
TlvInteger::OneByte(v) => v.to_le_bytes().to_vec(),
|
||||
TlvInteger::TwoBytes(v) => v.to_le_bytes().to_vec(),
|
||||
TlvInteger::FourBytes(v) => v.to_le_bytes().to_vec(),
|
||||
TlvInteger::EightBytes(v) => v.to_le_bytes().to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for TlvData
|
||||
where
|
||||
T: Into<TlvInteger>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self::Integer(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i8> for TlvInteger {
|
||||
fn from(value: i8) -> Self {
|
||||
Self::OneByte(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i16> for TlvInteger {
|
||||
fn from(value: i16) -> Self {
|
||||
Self::TwoBytes(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for TlvInteger {
|
||||
fn from(value: i32) -> Self {
|
||||
Self::FourBytes(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for TlvInteger {
|
||||
fn from(value: i64) -> Self {
|
||||
Self::EightBytes(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TlvUnsignedInteger {
|
||||
OneByte(u8),
|
||||
TwoBytes(u16),
|
||||
FourBytes(u32),
|
||||
EightBytes(u64),
|
||||
}
|
||||
|
||||
impl TlvUnsignedInteger {
|
||||
pub fn to_le_bytes(&self) -> Vec<u8> {
|
||||
match self {
|
||||
TlvUnsignedInteger::OneByte(v) => v.to_le_bytes().to_vec(),
|
||||
TlvUnsignedInteger::TwoBytes(v) => v.to_le_bytes().to_vec(),
|
||||
TlvUnsignedInteger::FourBytes(v) => v.to_le_bytes().to_vec(),
|
||||
TlvUnsignedInteger::EightBytes(v) => v.to_le_bytes().to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for TlvUnsignedInteger {
|
||||
fn from(value: u8) -> Self {
|
||||
Self::OneByte(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u16> for TlvUnsignedInteger {
|
||||
fn from(value: u16) -> Self {
|
||||
Self::TwoBytes(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for TlvUnsignedInteger {
|
||||
fn from(value: u32) -> Self {
|
||||
Self::FourBytes(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for TlvUnsignedInteger {
|
||||
fn from(value: u64) -> Self {
|
||||
Self::EightBytes(value)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,7 @@ mod data_types;
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[allow(unused)]
|
||||
pub use data_types::{TlvData, TlvInteger, TlvType, TlvUnsignedInteger};
|
||||
pub use data_types::TlvType;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
|
@ -11,33 +10,34 @@ pub fn decode(data: &[u8]) -> Result<HashMap<u8, Vec<u8>>, TlvError> {
|
|||
let mut tlvs = HashMap::new();
|
||||
let mut data = data;
|
||||
|
||||
while data.len() > 0 {
|
||||
while !data.is_empty() {
|
||||
let tlv_len = (data[1] as usize) + 2;
|
||||
let current;
|
||||
if tlv_len > data.len() {
|
||||
return Err(TlvError::WrongLength);
|
||||
}
|
||||
|
||||
(current, data) = data.split_at(tlv_len);
|
||||
|
||||
if data.len() < 3 {
|
||||
if current.len() < 2 {
|
||||
return Err(TlvError::TooShort);
|
||||
}
|
||||
|
||||
if data.len() < tlv_len {
|
||||
if current.len() < tlv_len {
|
||||
return Err(TlvError::WrongLength);
|
||||
}
|
||||
|
||||
let tlv_type = current[0];
|
||||
let tlv_data = current[2..].to_vec();
|
||||
let tlv_data = if tlv_len == 0 {
|
||||
vec![]
|
||||
} else {
|
||||
current[2..].to_vec()
|
||||
};
|
||||
tlvs.insert(tlv_type, tlv_data);
|
||||
}
|
||||
Ok(tlvs)
|
||||
}
|
||||
|
||||
fn decode_single(data: &[u8]) -> Result<(u8, Vec<u8>), TlvError> {
|
||||
let tlv_type = data[0];
|
||||
let tlv_len = data[1];
|
||||
let data = &data[2..];
|
||||
Ok((tlv_type, if tlv_len == 0 { vec![] } else { data.to_vec() }))
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum TlvError {
|
||||
#[error("too short")]
|
||||
|
@ -88,81 +88,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Tlv(u8, TlvData);
|
||||
|
||||
impl Tlv {
|
||||
pub fn new<A: Into<u8>>(tlv_type: A, tlv_data: TlvData) -> Self {
|
||||
Self(tlv_type.into(), tlv_data)
|
||||
}
|
||||
|
||||
pub fn encode(&self) -> Vec<u8> {
|
||||
let Tlv(tlv_type, data) = self;
|
||||
let bytes = match data {
|
||||
TlvData::Bytes(v) => v.clone(),
|
||||
TlvData::Tlv8(v) => todo!(),
|
||||
TlvData::Integer(v) => v.to_le_bytes(),
|
||||
TlvData::Float(v) => v.to_le_bytes().to_vec(),
|
||||
TlvData::String(v) => v.as_bytes().to_vec(),
|
||||
TlvData::Uint(v) => v.to_le_bytes(),
|
||||
};
|
||||
let length = bytes.len() as u32;
|
||||
let packets = bytes
|
||||
.chunks(255)
|
||||
.enumerate()
|
||||
.flat_map(|(packet_num, data)| {
|
||||
let mut packet = vec![*tlv_type];
|
||||
packet.push((length - 255 * (packet_num as u32)) as u8);
|
||||
packet.extend_from_slice(data);
|
||||
packet
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if packets.is_empty() {
|
||||
let mut first_packet = vec![*tlv_type];
|
||||
first_packet.extend_from_slice(&length.to_le_bytes());
|
||||
first_packet.push(0);
|
||||
first_packet
|
||||
} else {
|
||||
packets
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode_single(data: &[u8]) -> Result<Self, TlvError> {
|
||||
let tlv_type = data[0];
|
||||
let tlv_len = data[1];
|
||||
let data = &data[2..];
|
||||
Ok(Self(
|
||||
tlv_type,
|
||||
TlvData::Bytes(if tlv_len == 0 { vec![] } else { data.to_vec() }),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn tlv_type(&self) -> u8 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn tlv_data(&self) -> &TlvData {
|
||||
&self.1
|
||||
}
|
||||
|
||||
pub fn is_type<T: Into<u8>>(&self, value: T) -> bool {
|
||||
self.0 == value.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TlvData {
|
||||
pub fn as_bytes(&self) -> Vec<u8> {
|
||||
match self {
|
||||
TlvData::Bytes(val) => val.clone(),
|
||||
TlvData::Tlv8(_val) => todo!(),
|
||||
TlvData::Integer(val) => val.to_le_bytes(),
|
||||
TlvData::Float(_val) => todo!(),
|
||||
TlvData::String(val) => val.as_bytes().to_vec(),
|
||||
TlvData::Uint(val) => val.to_le_bytes(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TlvEncodableData {
|
||||
fn encode_value(&self) -> Vec<u8>;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ struct Args {
|
|||
command: Commands,
|
||||
#[clap(long)]
|
||||
pairing_data: PathBuf,
|
||||
#[clap(long)]
|
||||
device_name: String,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
|
@ -26,6 +28,8 @@ async fn main() -> Result<(), HomekitError> {
|
|||
let args = Args::parse();
|
||||
if args.pairing_data.is_file() {
|
||||
let controller = HomekitController::load(args.pairing_data)?;
|
||||
// println!("{controller:#?}");
|
||||
controller.connect_to(&args.device_name).await?;
|
||||
} else {
|
||||
log::error!("{:?} is not a file!", args.pairing_data)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue