1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
use crate::alink::{AlinkRequest, AlinkResponse, ParamsRequest};
use crate::{Error, Result, ThreeTuple};
use regex::Regex;
use rumqttc::{AsyncClient, QoS};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::sync::Arc;
use tokio::sync::mpsc;
use tokio::sync::mpsc::{Receiver, Sender};
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct InitParams {
/// 设备上传文件的名称。限制如下:
/// - 支持数字、英文字母、下划线(_)和英文句点(.)。
/// - 首字符仅支持数字和英文字母。
/// - 长度不超过100字节。
pub file_name: String,
/// 上传文件大小,单位字节。单个文件大小不超过16 MB。
/// 取值为-1时,表示文件大小未知。文件上传完成时,需在上传文件分片的消息中,指定参数isComplete,具体说明,请参见设备上传文件分片。
/// 注意 若fileSize值为-1,不支持设置ficMode和ficValue。
pub file_size: i32,
/// 物联网平台对设备上传同名文件的处理策略。非必填参数,默认为overwrite。
pub conflict_strategy: Option<ConflictStrategy>,
/// 文件的完整性校验模式,目前可取值crc64。非必传参数。
/// 若不传入,在文件上传完成后不校验文件完整性。
/// 若传入,与ficValue同时传入,根据校验模式和校验值校验文件完整性。
pub fic_mode: Option<FicMode>,
/// 文件的完整性校验值,是16位的Hex格式编码的字符串。
/// 非必传参数。若传入,与ficMode同时传入。
pub fic_value: Option<String>,
/// 自定义的设备请求上传文件的任务唯一ID,同一上传任务请求必须对应相同的唯一ID。
/// 传入:用于设备请求上传文件的消息,在重发场景下的幂等性处理。
pub init_uid: Option<String>,
/// 设备上传文件至OSS存储空间的配置参数。非必须参数。
/// 若未指定该参数,表示设备将文件上传至物联网平台的存储空间中。
pub extra_params: Option<InitExtraParams>,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(rename_all = "lowercase")]
pub enum ConflictStrategy {
/// overwrite:覆盖模式。
/// 先删除同名文件,再创建文件的上传任务。
Overwrite,
/// append:文件追加模式。
/// 若同名文件上传未完成,设备端可根据物联网平台返回的文件信息,继续上传文件。
/// 若同名文件上传已完成,创建文件上传任务会失败。设备端可修改文件名称或通过覆盖模式(overwrite)重新请求上传文件。
Append,
/// reject:拒绝模式。
/// 物联网平台拒绝同名文件上传的请求,并返回文件已存在的错误码。
Reject,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(rename_all = "lowercase")]
pub enum FicMode {
Crc64,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(rename_all = "kebab-case")]
pub enum OssOwnerType {
/// iot-platform:表示上传到阿里云物联网平台的OSS存储空间中。
/// 上传的文件会在物联网平台控制台的设备文件列表中展示,且可以通过相关的云端API管理。详细内容,请参见文件管理。
IotPlatform,
/// device-user:表示上传到设备所属用户自己的OSS存储空间中。
/// 上传的文件不会在物联网平台控制台的设备文件列表中展示,也不能通过相关的云端API管理。
/// 文件上传位置为:{ossbucket}/aliyun-iot-device-file/${instanceId}/${productKey}/${serviceId}/${deviceName}/${fileName}。
DeviceUser,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct InitExtraParams {
/// 设备上传文件的目标OSS所有者类型。
pub oss_owner_type: OssOwnerType,
/// 文件上传相关的业务ID。该ID需要在物联网平台控制台预先定义。具体操作,请参见配置设备文件上传至Bucket。
/// 仅ossOwnerType为device-user时,serviceId有效。
pub service_id: String,
/// 文件保存到OSS存储空间携带的标签,最多包含5个。标签定义规则,请参见对象标签。
/// 标签Key不能以2个下划线(_)开头。
/// 仅ossOwnerType为device-user时,fileTag有效。
pub file_tag: Value,
}
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct InitData {
/// 设备上传文件的名称。
pub file_name: String,
/// 本次上传文件任务的标识ID。后续上传文件分片时,需要传递该文件标识ID。
pub upload_id: String,
/// 仅当请求参数conflictStrategy为append,且物联网平台云端存在未完成上传的文件时,返回的已上传文件的大小,单位为字节。
pub offset: Option<usize>,
}
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct SendHeaderParams {
/// 设备请求上传文件时返回的文件上传任务标识ID。
pub upload_id: String,
/// 已上传文件分片的总大小,单位为字节。
pub offset: usize,
/// 当前上传文件分片的大小,单位为字节。
/// 非最后一个分片时,分片大小范围为256 B~131072 B。
/// 最后一个文件分片时,若上传的文件大小已知,则分片大小范围为1 B~131072 B;若上传的文件大小未知,则分片大小范围为0 B~131072 B。
pub b_size: usize,
/// 仅当设备请求上传文件中fileSize为-1,即文件大小未知时,该参数有效,表示当前分片是否是文件的最后一个分片。
/// true:是。此时,物联网平台的云端会校验已上传文件大小是否超过16 MB:
/// 未超过:若文件大小大于0,则文件上传成功。若文件大小为0,则返回文件不能为空的错误信息且删除文件上传任务。
/// 超过:返回文件大小超过16 MB的错误码,并删除已上传的文件。详细说明,请参见设备上传文件相关错误码。
/// false:否。表示不是最后一个文件分片,需继续上传文件。
pub is_complete: Option<bool>,
}
pub type SendHeader = ParamsRequest<SendHeaderParams>;
pub struct SendPayload {
/// 表示请求Header中JSON字符串对应的字节数组长度,必须占位2个字节,高位字节在前,低位字节在后。
/// 例如,Header的JSON字符串使用UTF-8编码转码成字节数组的长度为十进制的87,对应十六进制57,则高位字节为0x00,低位字节为0x57。
header_length: u16,
/// 表示请求Header中JSON字符串对应的字节数组,编码格式为UTF-8。具体内容,请参见下文的“Header的JSON数据格式”。
header: Vec<u8>,
/// 表示当前文件分片的字节数组,字节顺序按照相对于文件头的偏移量从小至大排列。
file_bytes: Vec<u8>,
/// 表示文件分片的校验值,仅支持CRC16/IBM,占位2个字节,低位字节在前,高位字节在后。
/// 例如,文件分片的校验值为0x0809,则低位字节为0x09,高位字节为0x08。
digest: u16,
}
/// 设备上传文件分片
/// 请求Topic:`/sys/${productKey}/${deviceName}/thing/file/upload/mqtt/send`。
impl SendPayload {
pub fn payload(header: SendHeader, file_bytes: &[u8]) -> Result<Vec<u8>> {
log::info!("上传文件头: {:?}", header);
let header = serde_json::to_vec(&header)?;
let header_length = header.len() as u16;
let digest = super::util::crc_ibm(file_bytes);
// let digest2 = super::util::crc_ibm2(file_bytes);
// log::debug!("crc:[{digest:x}] [{digest2:x}] ({})", file_bytes.len());
let mut payload = Vec::new();
payload.extend_from_slice(&header_length.to_be_bytes());
payload.extend_from_slice(&header);
payload.extend_from_slice(&file_bytes);
payload.extend_from_slice(&digest.to_le_bytes());
Ok(payload)
}
}
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct SendReplyData {
/// 本次上传文件任务的标识ID。后续上传文件分片时,需要传递该文件标识ID。
pub upload_id: String,
/// 已上传文件分片的总大小,单位为字节。
pub offset: usize,
/// 当前上传文件分片的大小,单位为字节。
/// 非最后一个分片时,分片大小范围为256 B~131072 B。
/// 最后一个文件分片时,若上传的文件大小已知,则分片大小范围为1 B~131072 B;若上传的文件大小未知,则分片大小范围为0 B~131072 B。
pub b_size: usize,
/// 当上传了最后一个分片数据后,文件上传完成,返回该参数,值为true。
/// 若设备请求上传文件的请求消息中fileSize值大于0,即文件大小已知时,若已上传的文件大小与设备请求上传文件时的文件大小相同,文件被识别为上传完成。
/// 若设备请求上传的请求消息中fileSize值为-1,即文件大小未知时,若文件分片上传请求中isComplete存在且值为true,文件被识别为上传完成。
pub complete: Option<bool>,
/// 文件的完整性校验模式。若请求上传文件时传入了该参数,对应的值仅支持为crc64。
pub fic_mode: Option<FicMode>,
/// 文件上传完成,返回设备请求上传文件时的ficValue值。
pub fic_value_client: Option<String>,
/// 文件上传完成,返回物联网平台云端计算的文件完整性校验值。该值与ficValueClient值相同,表示文件上传完整。
pub fic_value_server: Option<String>,
}
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct UploadId {
/// 设备请求上传文件时返回的文件上传任务标识ID。
pub upload_id: String,
}