aiot 0.10.0

Rust Link SDK (Aliyun IoT) based on Alink Protocol.
Documentation
//! 设备认证(动态注册)。

use crate::util::{auth, rand_string};
use crate::{DeviceAuthInfo, Error, MqttClient, MqttInstance, Result, ThreeTuple};
use log::*;
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc;

pub struct DynamicRegister {
    mqtt: MqttClient,
    rx: mpsc::Receiver<DynamicRegisterResult>,
}

impl DynamicRegister {
    pub fn new_tls(
        host: &str,
        product_key: &str,
        product_secret: &str,
        device_name: &str,
    ) -> Result<Self> {
        let (tx, rx) = mpsc::channel(4);
        let rego = Box::new(DynamicRegisterOptions::new(
            product_key,
            product_secret,
            device_name,
            tx,
        ));
        let username = rego.username();
        let password = rego.password();
        let client_id = rego.client_id();
        let info = DeviceAuthInfo {
            username,
            password,
            client_id,
        };
        let three = ThreeTuple {
            product_key: product_key.to_string(),
            device_name: device_name.to_string(),
            device_secret: "".to_string(),
        };
        let instance = MqttInstance::EndPoint(host.to_string());
        let mut mqtt = MqttClient::new(&three, &info, &instance)?;
        mqtt.enable_tls()?;
        mqtt.executors.push(rego as Box<dyn crate::Executor>);
        Ok(Self { mqtt, rx })
    }

    pub async fn register(mut self) -> Result<DynamicRegisterResult> {
        let mut conn = self.mqtt.connect();
        loop {
            tokio::select! {
                Some(res) = self.rx.recv() => {
                    return Ok(res);
                },
                Ok(n) = conn.poll() => {
                    debug!("Received = {:?}", n);
                },
                else => {
                    return Err(Error::EventLoopError);
                }
            }
        }
    }
}

pub struct DynamicRegisterOptions {
    random: String,
    pub product_key: String,
    pub product_secret: String,
    pub device_name: String,
    pub no_whitelist: bool,
    pub instance_id: Option<String>,
    pub tx: mpsc::Sender<DynamicRegisterResult>,
}

impl DynamicRegisterOptions {
    pub fn new(
        product_key: &str,
        product_secret: &str,
        device_name: &str,
        tx: mpsc::Sender<DynamicRegisterResult>,
    ) -> Self {
        Self {
            product_key: product_key.to_string(),
            product_secret: product_secret.to_string(),
            device_name: device_name.to_string(),
            random: rand_string(4),
            no_whitelist: false,
            instance_id: None,
            tx,
        }
    }

    pub fn username(&self) -> String {
        auth::mqtt::username(&self.product_key, &self.device_name)
    }

    pub fn password(&self) -> String {
        let input = format!(
            "deviceName{}productKey{}random{}",
            self.device_name, self.product_key, self.random
        );
        auth::sign(&input, &self.product_secret)
    }

    pub fn client_id(&self) -> String {
        let auth_type = if self.no_whitelist {
            "regnwl"
        } else {
            "register"
        };
        let instance = if let Some(id) = &self.instance_id {
            format!(",instanceId={}", id)
        } else {
            "".to_string()
        };
        format!(
            "{}.{}|random={},authType={},securemode={},signmethod={}{}|",
            self.device_name,
            self.product_key,
            self.random,
            auth_type,
            2,
            auth::SIGN_METHOD,
            instance
        )
    }
}

#[async_trait::async_trait]
impl crate::Executor for DynamicRegisterOptions {
    async fn execute(&mut self, topic: &str, payload: &[u8]) -> Result<()> {
        if topic == "/ext/register" {
            let data: DeviceInfoWhitelist = serde_json::from_slice(payload)?;
            self.tx
                .send(DynamicRegisterResult::Whitelist(data))
                .await
                .map_err(|_| Error::RepeatRegisterResponse)?;
            Ok(())
        } else if topic == "/ext/regnwl" {
            let data: RecvNoWhitelist = serde_json::from_slice(payload)?;
            let conn_clientid = format!(
                "{}|authType=connwl,securemode=-2,_ss=1,ext=3,_v={}|",
                data.client_id,
                *crate::util::CORE_SDK_VERSION
            );
            let username = self.username();
            let res = DynamicRegisterResult::NoWhitelist(DeviceAuthInfo {
                client_id: conn_clientid,
                username,
                password: data.device_token,
            });
            self.tx
                .send(res)
                .await
                .map_err(|_| Error::RepeatRegisterResponse)?;
            Ok(())
        } else {
            Err(Error::InvalidTopic(topic.to_string()))
        }
    }
}

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct DeviceInfoWhitelist {
    pub device_secret: String,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct RecvNoWhitelist {
    pub client_id: String,
    pub device_token: String,
}

#[derive(Debug, Clone)]
pub enum DynamicRegisterResult {
    Whitelist(DeviceInfoWhitelist),
    NoWhitelist(DeviceAuthInfo),
}