From 371937bf1f75cef9c50136ce45aa09ed823335aa Mon Sep 17 00:00:00 2001 From: Alex Janka Date: Sun, 18 Feb 2024 09:36:09 +1100 Subject: [PATCH] almost auths --- Cargo.lock | 1098 ++++++++++++++++- homekit-controller/Cargo.toml | 7 + homekit-controller/src/lib.rs | 389 ++++-- .../src/{ => pairing_data}/characteristics.rs | 316 +++++ homekit-controller/src/pairing_data/mod.rs | 103 ++ homekit-controller/src/tlv8/data_types.rs | 108 -- homekit-controller/src/tlv8/mod.rs | 101 +- homekit-exporter/src/main.rs | 4 + 8 files changed, 1843 insertions(+), 283 deletions(-) rename homekit-controller/src/{ => pairing_data}/characteristics.rs (70%) create mode 100644 homekit-controller/src/pairing_data/mod.rs diff --git a/Cargo.lock b/Cargo.lock index f89f74a..7fdeacb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -95,12 +105,45 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + [[package]] name = "bytes" version = "1.5.0" @@ -122,6 +165,41 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "clap" version = "4.5.0" @@ -168,6 +246,130 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "platforms", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + [[package]] name = "env_filter" version = "0.1.0" @@ -191,12 +393,155 @@ dependencies = [ "log", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fiat-crypto" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "h2" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + [[package]] name = "heck" version = "0.4.1" @@ -209,14 +554,48 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "homekit-controller" version = "0.1.0" dependencies = [ + "chacha20poly1305", + "ed25519-dalek", + "hex", + "hkdf", "log", + "reqwest", "serde", "serde_json", + "sha2", "thiserror", + "x25519-dalek", ] [[package]] @@ -230,24 +609,151 @@ dependencies = [ "tokio", ] +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + [[package]] name = "itoa" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "js-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + [[package]] name = "lock_api" version = "0.4.11" @@ -270,6 +776,12 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" version = "0.7.2" @@ -290,6 +802,24 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -309,6 +839,62 @@ dependencies = [ "memchr", ] +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -332,12 +918,57 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "pin-project-lite" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "platforms" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "proc-macro2" version = "1.0.78" @@ -356,13 +987,22 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -394,24 +1034,133 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "reqwest" +version = "0.11.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", +] + [[package]] name = "ryu" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" + [[package]] name = "serde" version = "1.0.196" @@ -443,6 +1192,29 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -452,6 +1224,24 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" version = "1.13.1" @@ -468,12 +1258,28 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "strsim" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "2.0.48" @@ -485,6 +1291,45 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "thiserror" version = "1.0.57" @@ -505,6 +1350,21 @@ dependencies = [ "syn", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.36.0" @@ -535,24 +1395,218 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" + +[[package]] +name = "web-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -684,3 +1738,45 @@ name = "windows_x86_64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/homekit-controller/Cargo.toml b/homekit-controller/Cargo.toml index 9da6103..7c98fa5 100644 --- a/homekit-controller/Cargo.toml +++ b/homekit-controller/Cargo.toml @@ -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"] } diff --git a/homekit-controller/src/lib.rs b/homekit-controller/src/lib.rs index 1c8d184..a490294 100644 --- a/homekit-controller/src/lib.rs +++ b/homekit-controller/src/lib.rs @@ -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, } @@ -17,96 +25,257 @@ impl HomekitController { pub fn load(pairing_data: PathBuf) -> Result { let devices: HashMap = 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, +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); + } + + 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 = Hkdf::::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::>() + .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::>() + .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 = + Hkdf::::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 = + Hkdf::::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::>(), + client, + ) + .await?; + Ok(()) + } } -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Accessory { - #[serde(rename = "aid")] - id: usize, - services: Vec, +async fn verify_request( + ip: &str, + port: usize, + data: &[(u8, Vec)], + client: &Client, +) -> Result>, 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)] -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, +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(()) } -#[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, - #[serde(flatten)] - value: Option, - ev: Option, - enc: Option, - #[serde(rename = "maxDataLen")] - max_data_len: Option, - #[serde(rename = "minValue")] - min_value: Option, - #[serde(rename = "maxValue")] - max_value: Option, - #[serde(rename = "minStep")] - min_step: Option, - unit: Option, -} - -#[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, +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 for HomekitError { + fn from(_: chacha20poly1305::Error) -> Self { + Self::Chacha + } +} + +impl From for HomekitError { + fn from(_: hkdf::InvalidLength) -> Self { + Self::Hkdf + } +} + +impl From> for HomekitError { + fn from(_: Box<[u8]>) -> Self { + Self::LengthMismatch + } +} + +impl From> for HomekitError { + fn from(_: Vec) -> Self { + Self::LengthMismatch + } } diff --git a/homekit-controller/src/characteristics.rs b/homekit-controller/src/pairing_data/characteristics.rs similarity index 70% rename from homekit-controller/src/characteristics.rs rename to homekit-controller/src/pairing_data/characteristics.rs index 580589b..c038f30 100644 --- a/homekit-controller/src/characteristics.rs +++ b/homekit-controller/src/pairing_data/characteristics.rs @@ -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 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, diff --git a/homekit-controller/src/pairing_data/mod.rs b/homekit-controller/src/pairing_data/mod.rs new file mode 100644 index 0000000..098f80f --- /dev/null +++ b/homekit-controller/src/pairing_data/mod.rs @@ -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, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Accessory { + #[serde(rename = "aid")] + pub id: usize, + pub services: Vec, +} + +impl Accessory { + pub fn get_service_ids(&self) -> Vec { + 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, +} + +#[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, + #[serde(flatten)] + pub value: Option, + pub ev: Option, + pub enc: Option, + #[serde(rename = "maxDataLen")] + pub max_data_len: Option, + #[serde(rename = "minValue")] + pub min_value: Option, + #[serde(rename = "maxValue")] + pub max_value: Option, + #[serde(rename = "minStep")] + pub min_step: Option, + pub unit: Option, +} + +#[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, +} diff --git a/homekit-controller/src/tlv8/data_types.rs b/homekit-controller/src/tlv8/data_types.rs index d680087..271e20a 100644 --- a/homekit-controller/src/tlv8/data_types.rs +++ b/homekit-controller/src/tlv8/data_types.rs @@ -1,5 +1,3 @@ -use super::Tlv; - pub enum TlvType { Method, Identifier, @@ -43,109 +41,3 @@ impl From for u8 { } } } - -#[allow(unused)] -#[derive(Debug, Clone)] -pub enum TlvData { - Bytes(Vec), - Tlv8(Vec), - 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 { - 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 From for TlvData -where - T: Into, -{ - fn from(value: T) -> Self { - Self::Integer(value.into()) - } -} - -impl From for TlvInteger { - fn from(value: i8) -> Self { - Self::OneByte(value) - } -} - -impl From for TlvInteger { - fn from(value: i16) -> Self { - Self::TwoBytes(value) - } -} - -impl From for TlvInteger { - fn from(value: i32) -> Self { - Self::FourBytes(value) - } -} - -impl From 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 { - 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 for TlvUnsignedInteger { - fn from(value: u8) -> Self { - Self::OneByte(value) - } -} - -impl From for TlvUnsignedInteger { - fn from(value: u16) -> Self { - Self::TwoBytes(value) - } -} - -impl From for TlvUnsignedInteger { - fn from(value: u32) -> Self { - Self::FourBytes(value) - } -} - -impl From for TlvUnsignedInteger { - fn from(value: u64) -> Self { - Self::EightBytes(value) - } -} diff --git a/homekit-controller/src/tlv8/mod.rs b/homekit-controller/src/tlv8/mod.rs index ac6f6a5..ba5f43c 100644 --- a/homekit-controller/src/tlv8/mod.rs +++ b/homekit-controller/src/tlv8/mod.rs @@ -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>, 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), 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>(tlv_type: A, tlv_data: TlvData) -> Self { - Self(tlv_type.into(), tlv_data) - } - - pub fn encode(&self) -> Vec { - 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::>(); - 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 { - 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>(&self, value: T) -> bool { - self.0 == value.into() - } -} - -impl TlvData { - pub fn as_bytes(&self) -> Vec { - 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; } diff --git a/homekit-exporter/src/main.rs b/homekit-exporter/src/main.rs index ebb9ef1..8dfb3f5 100644 --- a/homekit-exporter/src/main.rs +++ b/homekit-exporter/src/main.rs @@ -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) }