Initial commit
This commit is contained in:
parent
021859b544
commit
d53ee8ab5c
19
Cargo.toml
Normal file
19
Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "miband8-hr"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
aes = "0.8.4"
|
||||||
|
btleplug = "0.11.6"
|
||||||
|
ccm = "0.5.0"
|
||||||
|
futures = "0.3.31"
|
||||||
|
hex = "0.4.3"
|
||||||
|
hmac = "0.12.1"
|
||||||
|
protobuf = "3.7.1"
|
||||||
|
rand = "0.8.5"
|
||||||
|
rand_core = "0.6.4"
|
||||||
|
rosc = "0.10.1"
|
||||||
|
sha2 = "0.10.8"
|
||||||
|
tokio = {version="1.41.0", features = ["full"]}
|
||||||
|
uuid = "1.11.0"
|
||||||
14
src/constants.rs
Normal file
14
src/constants.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
// https://github.com/VladKolerts/miband4/blob/master/constants.js
|
||||||
|
// https://github.com/vshymanskyy/miband-js/blob/master/src/miband.js
|
||||||
|
|
||||||
|
pub const PAYLOAD_ACK: [u8;4] = [0x0,0x0,0x3,0x0];
|
||||||
|
|
||||||
|
pub const MIBAND8: &str = "0000fe95-0000-1000-8000-00805f9b34fb";
|
||||||
|
|
||||||
|
pub const READ: &str = "00000051-0000-1000-8000-00805f9b34fb";
|
||||||
|
pub const WRITE: &str = "00000052-0000-1000-8000-00805f9b34fb";
|
||||||
|
pub const ACTIVITY_DATA: &str = "00000053-0000-1000-8000-00805f9b34fb";
|
||||||
|
pub const DATA_UPLOAD: &str = "00000055-0000-1000-8000-00805f9b34fb";
|
||||||
|
|
||||||
|
pub const AUTH_COMMAND: &[u8] = &[0x00, 0x00, 0x02, 0x02, 0x08, 0x01, 0x10, 0x1a, 0x1a, 0x15, 0xf2, 0x01, 0x12, 0x0a, 0x10];
|
||||||
54
src/main.rs
Normal file
54
src/main.rs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
mod constants;
|
||||||
|
mod miband8;
|
||||||
|
mod xiaomi;
|
||||||
|
mod protobuf_decoder;
|
||||||
|
|
||||||
|
use std::{net::{SocketAddrV4, UdpSocket}, str::FromStr};
|
||||||
|
use rosc::{encoder, OscMessage, OscPacket, OscType};
|
||||||
|
use tokio::{sync::watch, task};
|
||||||
|
|
||||||
|
const SEND_TO_CHAT: bool = false;
|
||||||
|
const SEND_ADDR: &str = "10.42.0.2:9000";
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let send_addr: SocketAddrV4 = SocketAddrV4::from_str(SEND_ADDR).unwrap();
|
||||||
|
|
||||||
|
|
||||||
|
let sock = UdpSocket::bind(SocketAddrV4::from_str("0.0.0.0:9000").unwrap()).unwrap();
|
||||||
|
|
||||||
|
let (tx, mut rx) = watch::channel::<u8>(0);
|
||||||
|
|
||||||
|
let mut band = miband8::Miband8::new("f60b07bd3739018d7ee61d866c1eb01d".to_owned());
|
||||||
|
let e = band.init().await;
|
||||||
|
println!("{:?}", e);
|
||||||
|
task::spawn(async move {
|
||||||
|
band.continuously_measure_hr(tx).await;
|
||||||
|
});
|
||||||
|
while rx.changed().await.is_ok() {
|
||||||
|
let hr = rx.borrow_and_update().clone();
|
||||||
|
println!("Got heartrate: {}", hr);
|
||||||
|
|
||||||
|
if SEND_TO_CHAT {
|
||||||
|
let msg_buf = encoder::encode(&OscPacket::Message(OscMessage {
|
||||||
|
addr: "/chatbox/input".to_string(),
|
||||||
|
args: vec![OscType::String(format!("<3 {hr} bpm").to_string()), rosc::OscType::Bool(true),rosc::OscType::Bool(false)],
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
sock.send_to(&msg_buf, send_addr).unwrap();
|
||||||
|
}
|
||||||
|
let param_names = vec!["/avatar/parameters/VF101_beat", "/avatar/parameters/VF142_beat", "/avatar/parameters/VF145_beat"];
|
||||||
|
for param in param_names {
|
||||||
|
let msg_buf = encoder::encode(&OscPacket::Message(OscMessage {
|
||||||
|
addr: param.to_string(),
|
||||||
|
args: vec![OscType::Float((hr as f32 - 127.0)/ 127.0)],
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
sock.send_to(&msg_buf, send_addr).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
333
src/miband8.rs
Normal file
333
src/miband8.rs
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::{error::Error, time::Duration, vec};
|
||||||
|
|
||||||
|
use btleplug::{api::{Characteristic, Service,Central, Manager as _, Peripheral as _, ScanFilter, WriteType}, platform::{self, Adapter, Peripheral}};
|
||||||
|
|
||||||
|
use futures::StreamExt;
|
||||||
|
use hmac::{Hmac, Mac};
|
||||||
|
|
||||||
|
use rand::Rng;
|
||||||
|
use tokio::sync::watch::Sender;
|
||||||
|
use tokio::time;
|
||||||
|
use uuid::uuid;
|
||||||
|
|
||||||
|
use aes::Aes128;
|
||||||
|
use sha2::Sha256;
|
||||||
|
use ccm::{aead::{generic_array::GenericArray, Aead,KeyInit as ccm_key},consts::{U4, U12},Ccm};
|
||||||
|
|
||||||
|
use crate::constants::{self, AUTH_COMMAND};
|
||||||
|
use crate::protobuf_decoder::{self, Value};
|
||||||
|
|
||||||
|
type HmacSha256 = Hmac<Sha256>;
|
||||||
|
type Aes128Ccm = Ccm<Aes128, U4, U12>;
|
||||||
|
|
||||||
|
pub struct Miband8 {
|
||||||
|
dev: Option<Peripheral>,
|
||||||
|
central: Option<Adapter>,
|
||||||
|
auth_key: String,
|
||||||
|
services: HashMap<String, Service>,
|
||||||
|
chars: HashMap<String, Characteristic>,
|
||||||
|
nonce: Vec<u8>,
|
||||||
|
encryption_nonce: Vec<u8>,
|
||||||
|
encryption_key: Vec<u8>,
|
||||||
|
decryption_nonce: Vec<u8>,
|
||||||
|
decryption_key: Vec<u8>,
|
||||||
|
handle: u8
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Miband8 {
|
||||||
|
pub fn new(key: String) -> Self {
|
||||||
|
Miband8 {
|
||||||
|
dev: None,
|
||||||
|
central: None,
|
||||||
|
auth_key: key,
|
||||||
|
services: HashMap::new(),
|
||||||
|
chars: HashMap::new(),
|
||||||
|
nonce: [0;16].to_vec(),
|
||||||
|
encryption_nonce: vec![],
|
||||||
|
encryption_key: vec![],
|
||||||
|
decryption_nonce: vec![],
|
||||||
|
decryption_key: vec![],
|
||||||
|
handle: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn init(&mut self) -> Result<(), Box<dyn Error>> {
|
||||||
|
let manager = platform::Manager::new().await?;
|
||||||
|
|
||||||
|
// get the first bluetooth adapter
|
||||||
|
let adapters = manager.adapters().await?;
|
||||||
|
let central = adapters.into_iter().nth(0);
|
||||||
|
if central.is_none() {
|
||||||
|
return Err("No bluetooth adapter found")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.central = Some(central.unwrap());
|
||||||
|
|
||||||
|
// start scanning for devices
|
||||||
|
self.central.as_ref().unwrap()
|
||||||
|
.start_scan(ScanFilter {
|
||||||
|
services: vec![uuid!(constants::MIBAND8)],
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
|
||||||
|
time::sleep(Duration::from_secs(5)).await;
|
||||||
|
|
||||||
|
let mut band_op: Option<Peripheral> = None;
|
||||||
|
for p in self.central.as_ref().unwrap().peripherals().await? {
|
||||||
|
if p.properties()
|
||||||
|
.await?
|
||||||
|
.unwrap()
|
||||||
|
.local_name
|
||||||
|
.iter()
|
||||||
|
.any(|name| name.contains("Xiaomi Smart Band 8"))
|
||||||
|
{
|
||||||
|
band_op = Some(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if band_op.is_none() {
|
||||||
|
return Err("No band found")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let band = band_op.unwrap();
|
||||||
|
|
||||||
|
println!("Connecting to GATT");
|
||||||
|
band.connect().await?;
|
||||||
|
println!("Connected to GATT");
|
||||||
|
|
||||||
|
// discover services and characteristics
|
||||||
|
band.discover_services().await?;
|
||||||
|
println!("Discovered services");
|
||||||
|
|
||||||
|
self.dev = Some(band.clone());
|
||||||
|
|
||||||
|
let chars = band.services();
|
||||||
|
self.services.insert(
|
||||||
|
"miband8".to_owned(),
|
||||||
|
chars
|
||||||
|
.iter()
|
||||||
|
.find(|c| c.uuid == uuid!(constants::MIBAND8))
|
||||||
|
.unwrap().to_owned(),
|
||||||
|
);
|
||||||
|
println!("Services initialized");
|
||||||
|
|
||||||
|
|
||||||
|
let miband8_chars = &self.services
|
||||||
|
.get("miband8")
|
||||||
|
.unwrap()
|
||||||
|
.characteristics;
|
||||||
|
|
||||||
|
self.chars.insert(
|
||||||
|
"activity_data".to_owned(),
|
||||||
|
miband8_chars.iter()
|
||||||
|
.find(|c| c.uuid == uuid!(constants::ACTIVITY_DATA))
|
||||||
|
.unwrap().to_owned(),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.chars.insert(
|
||||||
|
"read".to_owned(),
|
||||||
|
miband8_chars.iter()
|
||||||
|
.find(|c| c.uuid == uuid!(constants::READ))
|
||||||
|
.unwrap().to_owned(),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.chars.insert(
|
||||||
|
"write".to_owned(),
|
||||||
|
miband8_chars.iter()
|
||||||
|
.find(|c| c.uuid == uuid!(constants::WRITE))
|
||||||
|
.unwrap().to_owned(),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.chars.insert(
|
||||||
|
"data_upload".to_owned(),
|
||||||
|
miband8_chars.iter()
|
||||||
|
.find(|c| c.uuid == uuid!(constants::DATA_UPLOAD))
|
||||||
|
.unwrap().to_owned(),
|
||||||
|
);
|
||||||
|
println!("Characteristics initialized");
|
||||||
|
|
||||||
|
band.subscribe(&self.chars["write"]).await?;
|
||||||
|
band.subscribe(&self.chars["read"]).await?;
|
||||||
|
band.subscribe(&self.chars["activity_data"]).await?;
|
||||||
|
band.subscribe(&self.chars["data_upload"]).await?;
|
||||||
|
|
||||||
|
// Send AUTH step 1: auth_command + nonce
|
||||||
|
rand::thread_rng().fill(&mut self.nonce[..]);
|
||||||
|
band.write(&self.chars.get("write").unwrap(), &[AUTH_COMMAND, &self.nonce.clone()].concat(), WriteType::WithoutResponse).await?;
|
||||||
|
|
||||||
|
let mut events = band.notifications().await?;
|
||||||
|
while let Some(event) = events.next().await {
|
||||||
|
if event.uuid == uuid!(constants::WRITE) && event.value == constants::PAYLOAD_ACK {
|
||||||
|
println!("Got ack");
|
||||||
|
// TODO: wait for ack before sending another command
|
||||||
|
}
|
||||||
|
if event.uuid == uuid!(constants::READ) && event.value[5] == 0x01 {
|
||||||
|
self.ack().await?;
|
||||||
|
match event.value[7] {
|
||||||
|
// NONCE
|
||||||
|
0x1A => {
|
||||||
|
println!("Got watch nonce");
|
||||||
|
let next_packet: Vec<u8> = self.handle_watch_nonce(&event.value).await?;
|
||||||
|
band.write(&self.chars.get("write").unwrap(), &next_packet, WriteType::WithoutResponse).await?;
|
||||||
|
}
|
||||||
|
// Auth success
|
||||||
|
0x1B => {
|
||||||
|
println!("Authenticated!!");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
unk => {
|
||||||
|
println!("Unknown command subtype: {:2X?}", unk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ack(&mut self) -> Result<(), btleplug::Error> {
|
||||||
|
self.dev.as_ref().unwrap().write(&self.chars.get("read").unwrap(), &constants::PAYLOAD_ACK, WriteType::WithoutResponse).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_handle(&mut self) -> u8{
|
||||||
|
self.handle = self.handle.wrapping_add(1);
|
||||||
|
self.handle
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn continuously_measure_hr(&mut self, channel_tx: Sender<u8>) -> u8 {
|
||||||
|
let command: Vec<u8> = vec![0x08, 0x08, 0x10, 0x2d];
|
||||||
|
let handle = self.get_handle();
|
||||||
|
let mut command_enc = vec![0x00, 0x00, 0x02, 0x01, handle, 0x00];
|
||||||
|
command_enc.append(&mut self.encrypt(self.encryption_key.clone(), &mut self.encryption_nonce.clone(), command, handle));
|
||||||
|
self.dev.as_ref().unwrap().write(&self.chars.get("write").unwrap(), &command_enc, WriteType::WithoutResponse).await.unwrap();
|
||||||
|
|
||||||
|
let mut events2 = self.dev.as_ref().unwrap().notifications().await.unwrap();
|
||||||
|
while let Some(event) = events2.next().await {
|
||||||
|
self.ack().await.unwrap();
|
||||||
|
let decrypted = self.decrypt(event.value[4..event.value.len()].to_vec());
|
||||||
|
if decrypted.len() > 8 {
|
||||||
|
let parsed = protobuf_decoder::Decoder::decode(&decrypted[..decrypted.len()-8].to_vec());
|
||||||
|
if let Some(Value::PROTOBUF(activity_update)) = parsed.values.get(&10) { // 10 ID for RealTimeStats
|
||||||
|
if let Some(Value::INT(heartrate)) = activity_update.values.get(&4) { // 4 ID heartRate
|
||||||
|
channel_tx.send(*heartrate as u8).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_watch_nonce(&mut self, notification_value: &Vec<u8>) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
|
let watch_nonce: Vec<u8> = notification_value[15..31].to_vec();
|
||||||
|
let watch_hmac: Vec<u8> = notification_value[33..65].to_vec();
|
||||||
|
|
||||||
|
let step2hmac = self.compute_auth_step3_hmac(watch_nonce.clone()).await.unwrap();
|
||||||
|
|
||||||
|
self.decryption_key = step2hmac[0..16].to_vec();
|
||||||
|
self.encryption_key = step2hmac[16..32].to_vec();
|
||||||
|
self.decryption_nonce = step2hmac[32..36].to_vec();
|
||||||
|
self.decryption_nonce.append(&mut vec![0x00;8]);
|
||||||
|
|
||||||
|
let encryption_nonce: &[u8] = &step2hmac[36..40];
|
||||||
|
|
||||||
|
|
||||||
|
let mut mac = <HmacSha256 as hmac::Mac>::new_from_slice(&self.decryption_key)
|
||||||
|
.expect("HMAC can take key of any size");
|
||||||
|
|
||||||
|
let mut secret_key: Vec<u8> = vec![];
|
||||||
|
secret_key.append(&mut watch_nonce.clone());
|
||||||
|
secret_key.append(&mut self.nonce.clone());
|
||||||
|
|
||||||
|
mac.update(&secret_key.as_slice());
|
||||||
|
let tmp = mac.finalize().into_bytes();
|
||||||
|
let result = tmp.as_slice();
|
||||||
|
|
||||||
|
if result != watch_hmac {
|
||||||
|
return Err("Watch hmac mismatch")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut next_packet: Vec<u8> = vec![0x00, 0x00, 0x02, 0x02, 0x08, 0x01, 0x10, 0x1b, 0x1a, 0x42, 0x82, 0x02, 0x3f, 0x0a, 0x20, 0xf2, 0x10, 0xa9, 0xc9, 0xb4, 0x2f, 0x35, 0x28, 0x7d, 0xa0, 0x29, 0x28, 0x41, 0xad, 0x79, 0x50, 0x06, 0x4e, 0x2b, 0x2d, 0x11, 0x6f, 0x0f, 0x1b, 0x59, 0x11, 0xa7, 0x93, 0x21, 0xed, 0x0a, 0xf9, 0x12, 0x1b, 0x8d, 0xfd, 0xb9, 0x42, 0x24, 0xf2, 0xc2, 0xff, 0x09, 0xdd, 0x12, 0x71, 0xb0, 0x9d, 0xc7, 0x36, 0x3c, 0xa6, 0x43, 0xd1, 0xaf, 0x20, 0xd6, 0x47, 0x04, 0x54, 0x9c];
|
||||||
|
|
||||||
|
let mut mac = <HmacSha256 as hmac::Mac>::new_from_slice(self.encryption_key.as_slice())
|
||||||
|
.expect("HMAC can take key of any size");
|
||||||
|
let mut secret_key: Vec<u8> = vec![];
|
||||||
|
|
||||||
|
secret_key.append(&mut self.nonce.clone());
|
||||||
|
secret_key.append(&mut watch_nonce.clone());
|
||||||
|
|
||||||
|
mac.update(secret_key.as_slice());
|
||||||
|
|
||||||
|
let hmac_key = mac.finalize().into_bytes();
|
||||||
|
let hmac_key_bytes = hmac_key.as_slice();
|
||||||
|
|
||||||
|
for (i,byte) in hmac_key_bytes.iter().enumerate() {
|
||||||
|
next_packet[i+15] = *byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
let device_info: Vec<u8> = vec![0x08, 0x00, 0x15, 0x00, 0x00, 0x04, 0x42, 0x1a, 0x07, 0x52, 0x4d, 0x58, 0x33, 0x33, 0x37, 0x30, 0x20, 0xe0, 0x01, 0x2a, 0x02, 0x45, 0x4e];
|
||||||
|
|
||||||
|
self.encryption_nonce = encryption_nonce.to_vec();
|
||||||
|
let encrypted_device_info = self.encrypt(self.encryption_key.clone(), &mut self.encryption_nonce.clone(), device_info, 0);
|
||||||
|
|
||||||
|
for (i,byte) in encrypted_device_info.iter().enumerate() {
|
||||||
|
next_packet[i+49] = *byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(next_packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn compute_auth_step3_hmac(&mut self, watch_nonce: Vec<u8>) -> Result<[u8;64], Box<dyn Error>> {
|
||||||
|
|
||||||
|
let mut secret_key: Vec<u8> = vec![];
|
||||||
|
secret_key.append(&mut self.nonce.clone());
|
||||||
|
secret_key.append(&mut watch_nonce.clone());
|
||||||
|
let mut mac = <HmacSha256 as hmac::Mac>::new_from_slice(secret_key.as_slice())
|
||||||
|
.expect("HMAC can take key of any size");
|
||||||
|
mac.update(hex::decode(self.auth_key.clone()).unwrap().as_slice());
|
||||||
|
|
||||||
|
let hmac_key = mac.finalize().into_bytes();
|
||||||
|
let hmac_key_bytes = hmac_key.as_slice();
|
||||||
|
|
||||||
|
let mut output: [u8;64] = [0;64];
|
||||||
|
let mut tmp: &[u8] = &[0;0];
|
||||||
|
let mut b: u8 = 1;
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
|
||||||
|
let mut tmp2;
|
||||||
|
while i < output.len(){
|
||||||
|
mac = <HmacSha256 as hmac::Mac>::new_from_slice(hmac_key_bytes)
|
||||||
|
.expect("HMAC can take key of any size");
|
||||||
|
let mut a: Vec<u8> = vec![];
|
||||||
|
|
||||||
|
a.append(&mut tmp.to_vec());
|
||||||
|
a.append(&mut "miwear-auth".as_bytes().to_vec());
|
||||||
|
a.append(&mut vec![b]);
|
||||||
|
mac.update(a.as_slice());
|
||||||
|
tmp2 = mac.finalize().into_bytes();
|
||||||
|
tmp = tmp2.as_slice();
|
||||||
|
for j in tmp {
|
||||||
|
output[i] = *j;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
b += 1;
|
||||||
|
}
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encrypt(&mut self, key: Vec<u8>, nonce: &mut Vec<u8>, payload: Vec<u8>, i: u8) -> Vec<u8> {
|
||||||
|
nonce.append(&mut vec![0x00,0x00,0x00,0x00,i,0x00,0x00,0x00]);
|
||||||
|
let cipher = Aes128Ccm::new(GenericArray::from_slice(&key.as_slice()));
|
||||||
|
let nonce = GenericArray::from_slice(&nonce); // 12-bytes; unique per message
|
||||||
|
let ciphertext = cipher.encrypt(nonce, payload.as_slice()).unwrap();
|
||||||
|
ciphertext
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypt(&mut self, payload: Vec<u8>) -> Vec<u8> {
|
||||||
|
let cipher = Aes128Ccm::new(GenericArray::from_slice(self.decryption_key.as_slice()));
|
||||||
|
let nonce = GenericArray::from_slice(&self.decryption_nonce); // 12-bytes; unique per message
|
||||||
|
let ciphertext = cipher.encrypt(nonce, payload.as_slice()).unwrap();
|
||||||
|
ciphertext
|
||||||
|
}
|
||||||
|
}
|
||||||
112
src/protobuf_decoder.rs
Normal file
112
src/protobuf_decoder.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
use core::str;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum WireType {
|
||||||
|
VARINT = 1,
|
||||||
|
I64 = 2,
|
||||||
|
LEN = 3,
|
||||||
|
SGROUP = 4,
|
||||||
|
EGROUP = 5,
|
||||||
|
I32 = 6,
|
||||||
|
UNKNOWN = 7
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for WireType {
|
||||||
|
fn from(orig: u8) -> Self {
|
||||||
|
match orig {
|
||||||
|
1 => WireType::VARINT,
|
||||||
|
2 => WireType::I64,
|
||||||
|
3 => WireType::LEN,
|
||||||
|
4 => WireType::SGROUP,
|
||||||
|
5 => WireType::EGROUP,
|
||||||
|
6 => WireType::I32,
|
||||||
|
_ => WireType::UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Value {
|
||||||
|
INT(u16),
|
||||||
|
STRING(String),
|
||||||
|
PROTOBUF(Protobuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Protobuf {
|
||||||
|
pub values: HashMap<u8, Value>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Decoder {}
|
||||||
|
|
||||||
|
impl Decoder {
|
||||||
|
pub fn decode(input: &Vec<u8>) -> Protobuf {
|
||||||
|
let mut hashmap: HashMap<u8, Value> = HashMap::new();
|
||||||
|
// println!("Huja: {:02x?}", input);
|
||||||
|
|
||||||
|
if input.len() < 1 {
|
||||||
|
// Empty message
|
||||||
|
return Protobuf { values: hashmap };
|
||||||
|
}
|
||||||
|
|
||||||
|
let variant_key = input[0] & 0b01111111;
|
||||||
|
let field_number = variant_key >> 3;
|
||||||
|
let mut iter = input[2..].iter();
|
||||||
|
|
||||||
|
// Citing the documentation: "You now know that the first number in the stream is always a varint key"
|
||||||
|
if (input[1] >> 7) == 1 {
|
||||||
|
let a: u16 = input[1] as u16;
|
||||||
|
let b: u16 = *iter.next().unwrap() as u16;
|
||||||
|
hashmap.insert(field_number, Value::INT(((b & 0b01111111) << 7) | (a & 0b01111111)));
|
||||||
|
}else {
|
||||||
|
hashmap.insert(field_number, Value::INT(input[1] as u16));
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(mut value) = iter.next() {
|
||||||
|
let variant_key = value & 0b01111111;
|
||||||
|
let wire_type: WireType = WireType::from((variant_key & 0b111) + 1);
|
||||||
|
let field_number = variant_key >> 3;
|
||||||
|
|
||||||
|
// print!("{}:{:?}", field_number, wire_type);
|
||||||
|
|
||||||
|
value = iter.next().unwrap();
|
||||||
|
match wire_type {
|
||||||
|
WireType::VARINT => {
|
||||||
|
if (value >> 7) == 1 {
|
||||||
|
// print!( " e");
|
||||||
|
let a: &u16 = &(*value as u16);
|
||||||
|
let b: &u16 = &(*iter.next().unwrap() as u16);
|
||||||
|
// println!(" value: {}", ((b & 0b01111111) << 7) | (a & 0b01111111));
|
||||||
|
hashmap.insert(field_number, Value::INT(((b & 0b01111111) << 7) | (a & 0b01111111)));
|
||||||
|
}else {
|
||||||
|
// println!(" value: {}", value);
|
||||||
|
hashmap.insert(field_number, Value::INT(*value as u16));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
WireType::LEN => {
|
||||||
|
let tmp = iter.clone().take(*value as usize).map(|x| *x).collect::<Vec<u8>>();
|
||||||
|
if *value == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
iter.nth(*value as usize - 1 );
|
||||||
|
let huj = str::from_utf8(tmp.as_slice());
|
||||||
|
if huj.is_err() {
|
||||||
|
hashmap.insert(field_number,Value::PROTOBUF(Self::decode(&tmp[1..].to_vec())));
|
||||||
|
}else {
|
||||||
|
hashmap.insert(field_number,Value::STRING(huj.unwrap().to_string()));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
WireType::UNKNOWN => {
|
||||||
|
// println!(" unknown skipping");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// println!(" unimplemented: {:?}", wire_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Protobuf { values: hashmap }
|
||||||
|
}
|
||||||
|
}
|
||||||
29681
src/xiaomi.rs
Normal file
29681
src/xiaomi.rs
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user