almost auths

This commit is contained in:
Alex Janka 2024-02-18 09:36:09 +11:00
parent 0aef7feb32
commit 371937bf1f
8 changed files with 1843 additions and 283 deletions

1098
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -10,3 +10,10 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
thiserror = "1.0" thiserror = "1.0"
log = "0.4" 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"] }

View file

@ -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 std::{collections::HashMap, path::PathBuf};
use characteristics::{
deserialize_characteristic_type, deserialize_service_type, CharacteristicType, ServiceType,
};
use serde::{Deserialize, Serialize};
use thiserror::Error; 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)] #[derive(Debug, Clone)]
pub struct HomekitController { pub struct HomekitController {
client: Client,
devices: HashMap<String, DevicePairingData>, devices: HashMap<String, DevicePairingData>,
} }
@ -17,96 +25,257 @@ impl HomekitController {
pub fn load(pairing_data: PathBuf) -> Result<Self, HomekitError> { pub fn load(pairing_data: PathBuf) -> Result<Self, HomekitError> {
let devices: HashMap<String, DevicePairingData> = let devices: HashMap<String, DevicePairingData> =
serde_json::from_str(&std::fs::read_to_string(pairing_data)?)?; 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)] impl DevicePairingData {
pub struct DevicePairingData { pub async fn connect(&self, client: &Client) -> Result<(), HomekitError> {
#[serde(rename = "AccessoryPairingID")] let key = EphemeralSecret::random();
accessory_pairing_id: String, let pubkey = PublicKey::from(&key);
#[serde(rename = "AccessoryLTPK")] let step1_response = verify_request(
accessory_ltpk: String, &self.accessory_ip,
#[serde(rename = "iOSPairingId")] self.accessory_port,
ios_pairing_id: String, &[
#[serde(rename = "iOSDeviceLTSK")] (
ios_device_ltsk: String, TlvType::State.into(),
#[serde(rename = "iOSDeviceLTPK")] (HomekitState::M1 as u8).encode_value(),
ios_device_ltpk: String, ),
#[serde(rename = "AccessoryIP")] (TlvType::PublicKey.into(), pubkey.as_bytes().encode_value()),
accessory_ip: String, ],
#[serde(rename = "AccessoryPort")] client,
accessory_port: usize, )
#[serde(rename = "Connection")] .await?;
connection: ConnectionType,
#[serde(rename = "accessories")] if step1_response
accessories: Vec<Accessory>, .get(&TlvType::State.into())
.ok_or(HomekitError::TlvNotFound)?[0]
!= (HomekitState::M2 as u8)
{
return Err(HomekitError::StateMismatch);
}
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);
}
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);
}
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)] async fn verify_request(
pub struct Accessory { ip: &str,
#[serde(rename = "aid")] port: usize,
id: usize, data: &[(u8, Vec<u8>)],
services: Vec<AccessoryService>, 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)] async fn get_characteristics(
pub struct AccessoryService { ip: &str,
#[serde(rename = "iid")] port: usize,
id: usize, characteristics: &[String],
#[serde(rename = "type", deserialize_with = "deserialize_service_type")] client: &Client,
service_type: ServiceType, ) -> Result<(), HomekitError> {
primary: bool, let characteristic_request = characteristics.join(",");
hidden: bool, let uri = format!(
characteristics: Vec<ServiceCharacteristic>, "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(())
} }
#[derive(Serialize, Deserialize, Debug, Clone)] pub enum HomekitState {
pub struct ServiceCharacteristic { M1 = 1,
#[serde(rename = "iid")] M2 = 2,
id: usize, M3 = 3,
#[serde(rename = "type", deserialize_with = "deserialize_characteristic_type")] M4 = 4,
characteristic_type: CharacteristicType, M5 = 5,
perms: Vec<String>, M6 = 6,
#[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>,
}
#[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,
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -115,4 +284,52 @@ pub enum HomekitError {
Io(#[from] std::io::Error), Io(#[from] std::io::Error),
#[error("serde_json")] #[error("serde_json")]
SerdeJson(#[from] serde_json::Error), 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
}
} }

View file

@ -239,6 +239,241 @@ pub enum CharacteristicType {
Unknown, 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)] #[derive(Deserialize)]
#[serde(field_identifier)] #[serde(field_identifier)]
enum CharacteristicTypeInner { enum CharacteristicTypeInner {
@ -1057,6 +1292,7 @@ pub enum ServiceType {
TunneledBTLEAccessoryService, TunneledBTLEAccessoryService,
Television, Television,
InputSource, InputSource,
TelevisionSpeaker,
TargetControlManagement, TargetControlManagement,
TargetControl, TargetControl,
AudioStreamManagement, AudioStreamManagement,
@ -1065,6 +1301,83 @@ pub enum ServiceType {
Unknown, 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)] #[derive(Deserialize)]
#[serde(field_identifier)] #[serde(field_identifier)]
pub enum ServiceTypeInner { pub enum ServiceTypeInner {
@ -1192,6 +1505,8 @@ pub enum ServiceTypeInner {
Television, Television,
#[serde(rename = "000000D9-0000-1000-8000-0026BB765291")] #[serde(rename = "000000D9-0000-1000-8000-0026BB765291")]
InputSource, InputSource,
#[serde(rename = "00000113-0000-1000-8000-0026BB765291")]
TelevisionSpeaker,
#[serde(rename = "00000122-0000-1000-8000-0026BB765291")] #[serde(rename = "00000122-0000-1000-8000-0026BB765291")]
TargetControlManagement, TargetControlManagement,
#[serde(rename = "00000125-0000-1000-8000-0026BB765291")] #[serde(rename = "00000125-0000-1000-8000-0026BB765291")]
@ -1276,6 +1591,7 @@ impl From<ServiceTypeInner> for ServiceType {
ServiceTypeInner::TunneledBTLEAccessoryService => Self::TunneledBTLEAccessoryService, ServiceTypeInner::TunneledBTLEAccessoryService => Self::TunneledBTLEAccessoryService,
ServiceTypeInner::Television => Self::Television, ServiceTypeInner::Television => Self::Television,
ServiceTypeInner::InputSource => Self::InputSource, ServiceTypeInner::InputSource => Self::InputSource,
ServiceTypeInner::TelevisionSpeaker => Self::TelevisionSpeaker,
ServiceTypeInner::TargetControlManagement => Self::TargetControlManagement, ServiceTypeInner::TargetControlManagement => Self::TargetControlManagement,
ServiceTypeInner::TargetControl => Self::TargetControl, ServiceTypeInner::TargetControl => Self::TargetControl,
ServiceTypeInner::AudioStreamManagement => Self::AudioStreamManagement, ServiceTypeInner::AudioStreamManagement => Self::AudioStreamManagement,

View 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,
}

View file

@ -1,5 +1,3 @@
use super::Tlv;
pub enum TlvType { pub enum TlvType {
Method, Method,
Identifier, 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)
}
}

View file

@ -2,8 +2,7 @@ mod data_types;
use std::collections::HashMap; use std::collections::HashMap;
#[allow(unused)] pub use data_types::TlvType;
pub use data_types::{TlvData, TlvInteger, TlvType, TlvUnsignedInteger};
use thiserror::Error; 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 tlvs = HashMap::new();
let mut data = data; let mut data = data;
while data.len() > 0 { while !data.is_empty() {
let tlv_len = (data[1] as usize) + 2; let tlv_len = (data[1] as usize) + 2;
let current; let current;
if tlv_len > data.len() {
return Err(TlvError::WrongLength);
}
(current, data) = data.split_at(tlv_len); (current, data) = data.split_at(tlv_len);
if data.len() < 3 { if current.len() < 2 {
return Err(TlvError::TooShort); return Err(TlvError::TooShort);
} }
if data.len() < tlv_len { if current.len() < tlv_len {
return Err(TlvError::WrongLength); return Err(TlvError::WrongLength);
} }
let tlv_type = current[0]; 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); tlvs.insert(tlv_type, tlv_data);
} }
Ok(tlvs) 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)] #[derive(Debug, Error)]
pub enum TlvError { pub enum TlvError {
#[error("too short")] #[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 { pub trait TlvEncodableData {
fn encode_value(&self) -> Vec<u8>; fn encode_value(&self) -> Vec<u8>;
} }

View file

@ -10,6 +10,8 @@ struct Args {
command: Commands, command: Commands,
#[clap(long)] #[clap(long)]
pairing_data: PathBuf, pairing_data: PathBuf,
#[clap(long)]
device_name: String,
} }
#[derive(Subcommand, Debug, Clone)] #[derive(Subcommand, Debug, Clone)]
@ -26,6 +28,8 @@ async fn main() -> Result<(), HomekitError> {
let args = Args::parse(); let args = Args::parse();
if args.pairing_data.is_file() { if args.pairing_data.is_file() {
let controller = HomekitController::load(args.pairing_data)?; let controller = HomekitController::load(args.pairing_data)?;
// println!("{controller:#?}");
controller.connect_to(&args.device_name).await?;
} else { } else {
log::error!("{:?} is not a file!", args.pairing_data) log::error!("{:?} is not a file!", args.pairing_data)
} }