├── .github
├── scripts
│ └── da_monitor.sh
└── workflows
│ ├── audit.yml
│ └── ci.yml
├── .gitignore
├── .vscode
└── settings.json
├── Cargo.toml
├── README.md
├── mqttrust
├── Cargo.toml
└── src
│ ├── encoding
│ ├── mod.rs
│ └── v4
│ │ ├── connect.rs
│ │ ├── decoder.rs
│ │ ├── encoder.rs
│ │ ├── mod.rs
│ │ ├── packet.rs
│ │ ├── publish.rs
│ │ ├── subscribe.rs
│ │ └── utils.rs
│ ├── fmt.rs
│ └── lib.rs
└── mqttrust_core
├── Cargo.toml
├── examples
├── aws_device_advisor.rs
├── common
│ ├── clock.rs
│ ├── credentials.rs
│ ├── mod.rs
│ └── network.rs
├── echo.rs
└── secrets
│ ├── .gitignore
│ ├── identity.pfx
│ └── root-ca.pem
└── src
├── client.rs
├── eventloop.rs
├── fmt.rs
├── lib.rs
├── max_payload.rs
├── options.rs
├── packet.rs
└── state.rs
/.github/scripts/da_monitor.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 | #
3 | # This script is written in bash in conjunction with the AWS CLI so
4 | # you can be aware of the APIs in use in a simplified way and optimize
5 | # the run in the programming language of your choice.
6 | #
7 |
8 | STATUS_PASS=PASS
9 | STATUS_FAIL=FAIL
10 | STATUS_RUNNING=RUNNING
11 | STATUS_PENDING=PENDING
12 | STATUS_STOPPING=STOPPING
13 | STATUS_STOPPED=STOPPED
14 | STATUS_PASS_WITH_WARNINGS=PASS_WITH_WARNINGS
15 | STATUS_ERROR=ERROR
16 |
17 | # The suite definition ID should be set in the AWS CodeBuild environment.
18 | suite_definition_id=$1
19 |
20 | # The suite should be run prior to this script being invoked.
21 | suite_run_id=$2
22 |
23 | # The PID of the binary being tested.
24 | pid=$3
25 |
26 | STATUS_FILE=/tmp/myapp-run-$$.status
27 | IN_PROGRESS=1
28 | MONITOR_STATUS=0
29 | function report_status {
30 |
31 | number_groups=$(jq -r ".testResult.groups | length" ${STATUS_FILE})
32 |
33 | echo NUMBER TEST GROUPS: ${number_groups}
34 |
35 | for gn in $(seq 0 $((number_groups-1))); do
36 | number_tests=$(jq -r ".testResult.groups[$gn].tests | length" ${STATUS_FILE})
37 | echo GROUP $((gn+1)) NUMBER OF TESTS: ${number_tests}
38 |
39 | for tcn in $(seq 0 $((number_tests-1))); do
40 | tcname=$(jq -r ".testResult.groups[$gn].tests[$tcn].testCaseDefinitionName" ${STATUS_FILE})
41 | tcstatus=$(jq -r ".testResult.groups[$gn].tests[$tcn].status" ${STATUS_FILE})
42 | echo ${tcname} ${tcstatus}
43 | done
44 | done
45 | }
46 |
47 | while test ${IN_PROGRESS} == 1; do
48 | # Fetch the current status and stash in /tmp so we can use it throughout the status fetch process.
49 |
50 | aws iotdeviceadvisor get-suite-run \
51 | --suite-definition-id ${suite_definition_id} \
52 | --suite-run-id ${suite_run_id} --output json > ${STATUS_FILE}
53 |
54 | # Identify the overall test status. If FAIL or PASS, emit the status
55 | # and exit here with the related error code (PASS=0, FAIL=1).
56 | # Otherwise continue and provide overall test group and test case
57 | # status.
58 |
59 | overall_status=$(jq -r ".status" ${STATUS_FILE})
60 |
61 | echo OVERALL STATUS: ${overall_status}
62 |
63 | report_status
64 |
65 | if test x"${overall_status}" == x${STATUS_FAIL}; then
66 | MONITOR_STATUS=1
67 | IN_PROGRESS=0
68 | elif test x"${overall_status}" == x${STATUS_PASS}; then
69 | MONITOR_STATUS=0
70 | IN_PROGRESS=0
71 | elif test x"${overall_status}" == x${STATUS_STOPPING}; then
72 | MONITOR_STATUS=1
73 | IN_PROGRESS=0
74 | elif test x"${overall_status}" == x${STATUS_STOPPED}; then
75 | MONITOR_STATUS=1
76 | IN_PROGRESS=0
77 | elif { ps -p $pid > /dev/null; }; [ "$?" = 1 ]; then
78 | echo Binary is not running any more?
79 |
80 | MONITOR_STATUS=1
81 | IN_PROGRESS=0
82 | else
83 | echo Sleeping 10 seconds for the next status.
84 | sleep 10
85 | fi
86 | done
87 | rm ${STATUS_FILE}
88 | exit ${MONITOR_STATUS}
--------------------------------------------------------------------------------
/.github/workflows/audit.yml:
--------------------------------------------------------------------------------
1 | name: Security audit
2 | on:
3 | push:
4 | paths:
5 | - '**/Cargo.toml'
6 | - '**/Cargo.lock'
7 | jobs:
8 | security_audit:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v1
12 | - uses: actions-rs/audit-check@v1
13 | with:
14 | token: ${{ secrets.GITHUB_TOKEN }}
15 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 |
9 | jobs:
10 | cancel_previous_runs:
11 | name: Cancel previous runs
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: styfle/cancel-workflow-action@0.4.1
15 | with:
16 | access_token: ${{ secrets.GITHUB_TOKEN }}
17 |
18 | test:
19 | name: Test
20 | runs-on: ubuntu-latest
21 | steps:
22 | - name: Checkout source code
23 | uses: actions/checkout@v2
24 |
25 | - name: Install Rust
26 | uses: actions-rs/toolchain@v1
27 | with:
28 | profile: minimal
29 | toolchain: stable
30 | target: thumbv7m-none-eabi
31 | override: true
32 |
33 | - name: Build
34 | uses: actions-rs/cargo@v1
35 | with:
36 | command: build
37 | args: --all --target thumbv7m-none-eabi
38 |
39 | - name: Test
40 | uses: actions-rs/cargo@v1
41 | with:
42 | command: test
43 | args: --lib --features "log"
44 | integration:
45 | name: Integration Test
46 | runs-on: ubuntu-latest
47 | needs: test
48 | steps:
49 | - name: Checkout source code
50 | uses: actions/checkout@v2
51 |
52 | - name: Install Rust
53 | uses: actions-rs/toolchain@v1
54 | with:
55 | profile: minimal
56 | toolchain: stable
57 | target: thumbv7m-none-eabi
58 | override: true
59 |
60 | - name: Run integration test
61 | uses: actions-rs/cargo@v1
62 | with:
63 | command: run
64 | args: --features=log --example echo
65 |
66 | # device_advisor:
67 | # name: AWS IoT Device Advisor
68 | # runs-on: ubuntu-latest
69 | # needs: test
70 | # env:
71 | # AWS_EC2_METADATA_DISABLED: true
72 | # AWS_DEFAULT_REGION: ${{ secrets.MGMT_AWS_DEFAULT_REGION }}
73 | # AWS_ACCESS_KEY_ID: ${{ secrets.MGMT_AWS_ACCESS_KEY_ID }}
74 | # AWS_SECRET_ACCESS_KEY: ${{ secrets.MGMT_AWS_SECRET_ACCESS_KEY }}
75 | # SUITE_ID: greb3uy2wtq3
76 | # THING_ARN: arn:aws:iot:eu-west-1:274906834921:thing/mqttrust
77 | # CERTIFICATE_ARN: arn:aws:iot:eu-west-1:274906834921:cert/e7280d8d316b58da3058037a2c1730d9eb15de50e96f4d47e54ea655266b76db
78 | # steps:
79 | # - name: Checkout
80 | # uses: actions/checkout@v1
81 |
82 | # - name: Install Rust
83 | # uses: actions-rs/toolchain@v1
84 | # with:
85 | # profile: minimal
86 | # toolchain: stable
87 | # override: true
88 |
89 | # - name: Get AWS_HOSTNAME
90 | # id: hostname
91 | # run: |
92 | # hostname=$(aws iotdeviceadvisor get-endpoint --output text --query endpoint)
93 | # ret=$?
94 | # echo "::set-output name=AWS_HOSTNAME::$hostname"
95 | # exit $ret
96 |
97 | # - name: Build test binary
98 | # uses: actions-rs/cargo@v1
99 | # env:
100 | # AWS_HOSTNAME: ${{ steps.hostname.outputs.AWS_HOSTNAME }}
101 | # with:
102 | # command: build
103 | # args: --features=log --example aws_device_advisor --release
104 |
105 | # - name: Start test suite
106 | # id: test_suite
107 | # run: |
108 | # suite_id=$(aws iotdeviceadvisor start-suite-run --suite-definition-id ${{ env.SUITE_ID }} --suite-run-configuration "primaryDevice={thingArn=${{ env.THING_ARN }},certificateArn=${{ env.CERTIFICATE_ARN }}}" --output text --query suiteRunId)
109 | # ret=$?
110 | # echo "::set-output name=SUITE_RUN_ID::$suite_id"
111 | # exit $ret
112 |
113 | # - name: Execute test binary
114 | # id: binary
115 | # env:
116 | # DEVICE_ADVISOR_PASSWORD: ${{ secrets.DEVICE_ADVISOR_PASSWORD }}
117 | # RUST_LOG: trace
118 | # run: |
119 | # nohup ./target/release/examples/aws_device_advisor > device_advisor_integration.log &
120 | # echo "::set-output name=PID::$!"
121 |
122 | # - name: Monitor test run
123 | # run: |
124 | # chmod +x ./.github/scripts/da_monitor.sh
125 | # echo ${{ env.SUITE_ID }} ${{ steps.test_suite.outputs.SUITE_RUN_ID }} ${{ steps.binary.outputs.PID }}
126 | # ./.github/scripts/da_monitor.sh ${{ env.SUITE_ID }} ${{ steps.test_suite.outputs.SUITE_RUN_ID }} ${{ steps.binary.outputs.PID }}
127 |
128 | # - name: Kill test binary process
129 | # if: ${{ always() }}
130 | # run: kill ${{ steps.binary.outputs.PID }} || true
131 |
132 | # - name: Log binary output
133 | # if: ${{ always() }}
134 | # run: cat device_advisor_integration.log
135 |
136 | # - name: Stop test suite
137 | # if: ${{ failure() }}
138 | # run: aws iotdeviceadvisor stop-suite-run --suite-definition-id ${{ env.SUITE_ID }} --suite-run-id ${{ steps.test_suite.outputs.SUITE_RUN_ID }}
139 |
140 | rustfmt:
141 | name: rustfmt
142 | runs-on: ubuntu-latest
143 | steps:
144 | - name: Checkout source code
145 | uses: actions/checkout@v2
146 |
147 | - name: Install Rust
148 | uses: actions-rs/toolchain@v1
149 | with:
150 | profile: minimal
151 | toolchain: nightly
152 | override: true
153 | components: rustfmt
154 |
155 | - name: Run rustfmt
156 | uses: actions-rs/cargo@v1
157 | with:
158 | command: fmt
159 | args: --all -- --check --verbose
160 |
161 | # tomlfmt:
162 | # name: tomlfmt
163 | # runs-on: ubuntu-latest
164 | # steps:
165 | # - name: Checkout source code
166 | # uses: actions/checkout@v2
167 |
168 | # - name: Install Rust
169 | # uses: actions-rs/toolchain@v1
170 | # with:
171 | # profile: minimal
172 | # toolchain: nightly
173 | # override: true
174 |
175 | # - name: Install tomlfmt
176 | # uses: actions-rs/install@v0.1
177 | # with:
178 | # crate: cargo-tomlfmt
179 | # version: latest
180 | # use-tool-cache: true
181 |
182 | # - name: Run Tomlfmt
183 | # uses: actions-rs/cargo@v1
184 | # with:
185 | # command: tomlfmt
186 | # args: --dryrun
187 |
188 | clippy:
189 | name: clippy
190 | runs-on: ubuntu-latest
191 | env:
192 | CLIPPY_PARAMS: -W clippy::all -W clippy::pedantic -W clippy::nursery -W clippy::cargo
193 | steps:
194 | - name: Checkout source code
195 | uses: actions/checkout@v2
196 |
197 | - name: Install Rust
198 | uses: actions-rs/toolchain@v1
199 | with:
200 | profile: minimal
201 | toolchain: stable
202 | override: true
203 | components: clippy
204 |
205 | - name: Run clippy
206 | uses: actions-rs/clippy-check@v1
207 | with:
208 | token: ${{ secrets.GITHUB_TOKEN }}
209 | args: --features "log" -- ${{ env.CLIPPY_PARAMS }}
210 | # grcov:
211 | # name: Coverage
212 | # runs-on: ubuntu-latest
213 | # steps:
214 | # - name: Checkout source code
215 | # uses: actions/checkout@v2
216 |
217 | # - name: Install Rust
218 | # uses: actions-rs/toolchain@v1
219 | # with:
220 | # profile: minimal
221 | # toolchain: nightly
222 | # target: thumbv7m-none-eabi
223 | # override: true
224 |
225 | # - name: Install grcov
226 | # uses: actions-rs/cargo@v1
227 | # # uses: actions-rs/install@v0.1
228 | # with:
229 | # # crate: grcov
230 | # # version: latest
231 | # # use-tool-cache: true
232 | # command: install
233 | # args: grcov --git https://github.com/mozilla/grcov
234 |
235 | # - name: Test
236 | # uses: actions-rs/cargo@v1
237 | # with:
238 | # command: test
239 | # args: --lib --no-fail-fast --features "log"
240 | # env:
241 | # CARGO_INCREMENTAL: "0"
242 | # RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Coverflow-checks=off -Cpanic=unwind -Zpanic_abort_tests"
243 | # RUSTDOCFLAGS: "-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Coverflow-checks=off -Cpanic=unwind -Zpanic_abort_tests"
244 |
245 | # - name: Generate coverage data
246 | # id: grcov
247 | # # uses: actions-rs/grcov@v0.1
248 | # run: |
249 | # grcov target/debug/ \
250 | # --branch \
251 | # --llvm \
252 | # --source-dir . \
253 | # --output-file lcov.info \
254 | # --ignore='/**' \
255 | # --ignore='C:/**' \
256 | # --ignore='../**' \
257 | # --ignore-not-existing \
258 | # --excl-line "#\\[derive\\(" \
259 | # --excl-br-line "(#\\[derive\\()|(debug_assert)" \
260 | # --excl-start "#\\[cfg\\(test\\)\\]" \
261 | # --excl-br-start "#\\[cfg\\(test\\)\\]" \
262 | # --commit-sha ${{ github.sha }} \
263 | # --service-job-id ${{ github.job }} \
264 | # --service-name "GitHub Actions" \
265 | # --service-number ${{ github.run_id }}
266 | # - name: Upload coverage as artifact
267 | # uses: actions/upload-artifact@v2
268 | # with:
269 | # name: lcov.info
270 | # # path: ${{ steps.grcov.outputs.report }}
271 | # path: lcov.info
272 |
273 | # - name: Upload coverage to codecov.io
274 | # uses: codecov/codecov-action@v1
275 | # with:
276 | # # file: ${{ steps.grcov.outputs.report }}
277 | # file: lcov.info
278 | # fail_ci_if_error: true
279 | docs:
280 | name: Documentation
281 | runs-on: ubuntu-latest
282 | steps:
283 | - name: Checkout source code
284 | uses: actions/checkout@v2
285 | with:
286 | persist-credentials: false
287 |
288 | - name: Install Rust
289 | uses: actions-rs/toolchain@v1
290 | with:
291 | profile: minimal
292 | toolchain: nightly
293 | override: true
294 |
295 | - name: Build documentation
296 | uses: actions-rs/cargo@v1
297 | with:
298 | command: doc
299 | args: --verbose --no-deps
300 |
301 | # - name: Finalize documentation
302 | # run: |
303 | # CRATE_NAME=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]' | cut -f2 -d"/")
304 | # echo "" > target/doc/index.html
305 | # touch target/doc/.nojekyll
306 | # - name: Upload as artifact
307 | # uses: actions/upload-artifact@v2
308 | # with:
309 | # name: Documentation
310 | # path: target/doc
311 |
312 | # - name: Deploy
313 | # uses: JamesIves/github-pages-deploy-action@releases/v3
314 | # with:
315 | # ACCESS_TOKEN: ${{ secrets.GH_PAT }}
316 | # BRANCH: gh-pages
317 | # FOLDER: target/doc
318 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/*.rs.bk
2 | .#*
3 | .gdb_history
4 | Cargo.lock
5 | target/
6 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | // override the default setting (`cargo check --all-targets`) which produces the following error
3 | // "can't find crate for `test`" when the default compilation target is a no_std target
4 | // with these changes RA will call `cargo check --bins` on save
5 | "rust-analyzer.checkOnSave.allTargets": false,
6 | "rust-analyzer.cargo.target": "x86_64-unknown-linux-gnu",
7 | "rust-analyzer.diagnostics.disabled": [
8 | "unresolved-import"
9 | ],
10 | "rust-analyzer.cargo.features": ["log"]
11 | }
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = [
3 | "mqttrust",
4 | "mqttrust_core",
5 | ]
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MQTT Client for Embedded devices
2 |
3 | > no_std, no_alloc crate implementing secure MQTT Client capabilities.
4 |
5 | ![Test][test]
6 | [![Code coverage][codecov-badge]][codecov]
7 | ![No Std][no-std-badge]
8 | [![Crates.io Version][crates-io-badge]][crates-io]
9 | [![Crates.io Downloads][crates-io-download-badge]][crates-io-download]
10 |
11 | This crate is highly inspired by the great work in [rumqttc](https://github.com/bytebeamio/rumqtt/tree/master/rumqttc).
12 |
13 | ## Tests
14 |
15 | > The crate is covered by tests. These tests can be run by `cargo test --tests --all-features`, and are run by the CI on every push to master.
16 |
17 | ## License
18 |
19 | Licensed under either of
20 |
21 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
22 | http://www.apache.org/licenses/LICENSE-2.0)
23 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
24 |
25 | at your option.
26 |
27 | ### Contribution
28 |
29 | Unless you explicitly state otherwise, any contribution intentionally submitted
30 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
31 | dual licensed as above, without any additional terms or conditions.
32 |
33 |
34 |
35 | [test]: https://github.com/BlackbirdHQ/mqttrust/workflows/Test/badge.svg
36 | [no-std-badge]: https://img.shields.io/badge/no__std-yes-blue
37 | [codecov-badge]: https://codecov.io/gh/BlackbirdHQ/mqttrust/branch/master/graph/badge.svg
38 | [codecov]: https://codecov.io/gh/BlackbirdHQ/mqttrust
39 | [crates-io]: https://crates.io/crates/mqttrust
40 | [crates-io-badge]: https://img.shields.io/crates/v/mqttrust.svg?maxAge=3600
41 | [crates-io-download]: https://crates.io/crates/mqttrust
42 | [crates-io-download-badge]: https://img.shields.io/crates/d/mqttrust.svg?maxAge=3600
43 |
44 |
--------------------------------------------------------------------------------
/mqttrust/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "mqttrust"
3 | version = "0.6.0"
4 | authors = ["Mathias Koch "]
5 | description = "MQTT Client "
6 | readme = "../README.md"
7 | keywords = ["mqtt", "no-std"]
8 | categories = ["embedded", "no-std"]
9 | license = "MIT OR Apache-2.0"
10 | repository = "https://github.com/BlackbirdHQ/mqttrust"
11 | edition = "2018"
12 | documentation = "https://docs.rs/mqttrust"
13 |
14 | [lib]
15 | name = "mqttrust"
16 |
17 | [badges]
18 | maintenance = { status = "actively-developed" }
19 |
20 | [dependencies]
21 | heapless = { version = "^0.7" }
22 | serde = { version = "1.0", features = ["derive"], optional = true }
23 |
24 | log = { version = "^0.4", default-features = false, optional = true }
25 | defmt = { version = "^0.3", optional = true }
26 |
27 | [features]
28 | default = []
29 |
30 | defmt-impl = ["defmt", "heapless/defmt-impl"]
31 |
32 | derive = ["serde", "heapless/serde"]
33 |
--------------------------------------------------------------------------------
/mqttrust/src/encoding/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod v4;
2 |
--------------------------------------------------------------------------------
/mqttrust/src/encoding/v4/connect.rs:
--------------------------------------------------------------------------------
1 | use super::{decoder::*, encoder::*, *};
2 |
3 | /// Protocol version.
4 | ///
5 | /// Sent in [`Connect`] packet.
6 | ///
7 | /// [`Connect`]: struct.Connect.html
8 | #[derive(Debug, Clone, Copy, PartialEq, Eq)]
9 | pub enum Protocol {
10 | /// [MQTT 3.1.1] is the most commonly implemented version.
11 | ///
12 | /// [MQTT 3.1.1]: https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html
13 | /// [MQTT 5]: https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html
14 | MQTT311,
15 | /// MQIsdp, aka SCADA are pre-standardisation names of MQTT. It should mostly conform to MQTT
16 | /// 3.1.1, but you should watch out for implementation discrepancies.
17 | MQIsdp,
18 | }
19 | impl Protocol {
20 | pub(crate) fn new(name: &str, level: u8) -> Result {
21 | match (name, level) {
22 | ("MQIsdp", 3) => Ok(Protocol::MQIsdp),
23 | ("MQTT", 4) => Ok(Protocol::MQTT311),
24 | _ => Err(Error::InvalidProtocol(name.into(), level)),
25 | }
26 | }
27 | pub(crate) fn from_buffer(buf: &[u8], offset: &mut usize) -> Result {
28 | let protocol_name = read_str(buf, offset)?;
29 | let protocol_level = buf[*offset];
30 | *offset += 1;
31 |
32 | Protocol::new(protocol_name, protocol_level)
33 | }
34 | pub(crate) fn to_buffer(&self, buf: &mut [u8], offset: &mut usize) -> Result {
35 | match self {
36 | Protocol::MQTT311 => {
37 | let slice = &[0u8, 4, b'M', b'Q', b'T', b'T', 4];
38 | for &byte in slice {
39 | write_u8(buf, offset, byte)?;
40 | }
41 | Ok(slice.len())
42 | }
43 | Protocol::MQIsdp => {
44 | let slice = &[0u8, 4, b'M', b'Q', b'i', b's', b'd', b'p', 4];
45 | for &byte in slice {
46 | write_u8(buf, offset, byte)?;
47 | }
48 | Ok(slice.len())
49 | }
50 | }
51 | }
52 | }
53 |
54 | /// Message that the server should publish when the client disconnects.
55 | ///
56 | /// Sent by the client in the [Connect] packet. [MQTT 3.1.3.3].
57 | ///
58 | /// [Connect]: struct.Connect.html
59 | /// [MQTT 3.1.3.3]: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718031
60 | #[derive(Debug, Clone, PartialEq)]
61 | pub struct LastWill<'a> {
62 | pub topic: &'a str,
63 | pub message: &'a [u8],
64 | pub qos: QoS,
65 | pub retain: bool,
66 | }
67 |
68 | /// Sucess value of a [Connack] packet.
69 | ///
70 | /// See [MQTT 3.2.2.3] for interpretations.
71 | ///
72 | /// [Connack]: struct.Connack.html
73 | /// [MQTT 3.2.2.3]: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718035
74 | #[derive(Debug, Clone, Copy, PartialEq)]
75 | #[cfg_attr(feature = "defmt-impl", derive(defmt::Format))]
76 | pub enum ConnectReturnCode {
77 | Accepted,
78 | RefusedProtocolVersion,
79 | RefusedIdentifierRejected,
80 | ServerUnavailable,
81 | BadUsernamePassword,
82 | NotAuthorized,
83 | }
84 | impl ConnectReturnCode {
85 | fn as_u8(&self) -> u8 {
86 | match *self {
87 | ConnectReturnCode::Accepted => 0,
88 | ConnectReturnCode::RefusedProtocolVersion => 1,
89 | ConnectReturnCode::RefusedIdentifierRejected => 2,
90 | ConnectReturnCode::ServerUnavailable => 3,
91 | ConnectReturnCode::BadUsernamePassword => 4,
92 | ConnectReturnCode::NotAuthorized => 5,
93 | }
94 | }
95 | pub(crate) fn from_u8(byte: u8) -> Result {
96 | match byte {
97 | 0 => Ok(ConnectReturnCode::Accepted),
98 | 1 => Ok(ConnectReturnCode::RefusedProtocolVersion),
99 | 2 => Ok(ConnectReturnCode::RefusedIdentifierRejected),
100 | 3 => Ok(ConnectReturnCode::ServerUnavailable),
101 | 4 => Ok(ConnectReturnCode::BadUsernamePassword),
102 | 5 => Ok(ConnectReturnCode::NotAuthorized),
103 | n => Err(Error::InvalidConnectReturnCode(n)),
104 | }
105 | }
106 | }
107 |
108 | /// Connect packet ([MQTT 3.1]).
109 | ///
110 | /// [MQTT 3.1]: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718028
111 | #[derive(Debug, Clone, PartialEq)]
112 | pub struct Connect<'a> {
113 | pub protocol: Protocol,
114 | pub keep_alive: u16,
115 | pub client_id: &'a str,
116 | pub clean_session: bool,
117 | pub last_will: Option>,
118 | pub username: Option<&'a str>,
119 | pub password: Option<&'a [u8]>,
120 | }
121 |
122 | /// Connack packet ([MQTT 3.2]).
123 | ///
124 | /// [MQTT 3.2]: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718033
125 | #[derive(Debug, Clone, Copy, PartialEq)]
126 | pub struct Connack {
127 | pub session_present: bool,
128 | pub code: ConnectReturnCode,
129 | }
130 |
131 | impl<'a> Connect<'a> {
132 | pub(crate) fn from_buffer(buf: &'a [u8], offset: &mut usize) -> Result {
133 | let protocol = Protocol::from_buffer(buf, offset)?;
134 |
135 | let connect_flags = buf[*offset];
136 | let keep_alive = ((buf[*offset + 1] as u16) << 8) | buf[*offset + 2] as u16;
137 | *offset += 3;
138 |
139 | let client_id = read_str(buf, offset)?;
140 |
141 | let last_will = if connect_flags & 0b100 != 0 {
142 | let will_topic = read_str(buf, offset)?;
143 | let will_message = read_bytes(buf, offset)?;
144 | let will_qod = QoS::from_u8((connect_flags & 0b11000) >> 3)?;
145 | Some(LastWill {
146 | topic: will_topic,
147 | message: will_message,
148 | qos: will_qod,
149 | retain: (connect_flags & 0b00100000) != 0,
150 | })
151 | } else {
152 | None
153 | };
154 |
155 | let username = if connect_flags & 0b10000000 != 0 {
156 | Some(read_str(buf, offset)?)
157 | } else {
158 | None
159 | };
160 |
161 | let password = if connect_flags & 0b01000000 != 0 {
162 | Some(read_bytes(buf, offset)?)
163 | } else {
164 | None
165 | };
166 |
167 | let clean_session = (connect_flags & 0b10) != 0;
168 |
169 | Ok(Connect {
170 | protocol,
171 | keep_alive,
172 | client_id,
173 | clean_session,
174 | last_will,
175 | username,
176 | password,
177 | })
178 | }
179 |
180 | pub(crate) fn len(&self) -> usize {
181 | let mut length: usize = 6 + 1 + 1; // NOTE: protocol_name(6) + protocol_level(1) + flags(1);
182 | length += 2 + self.client_id.len();
183 | length += 2; // keep alive
184 | if let Some(username) = self.username {
185 | length += username.len();
186 | length += 2;
187 | };
188 | if let Some(password) = self.password {
189 | length += password.len();
190 | length += 2;
191 | };
192 | if let Some(last_will) = &self.last_will {
193 | length += last_will.message.len();
194 | length += last_will.topic.len();
195 | length += 4;
196 | };
197 | length
198 | }
199 |
200 | pub(crate) fn to_buffer(&self, buf: &mut [u8], offset: &mut usize) -> Result {
201 | let header: u8 = 0b00010000;
202 | let mut connect_flags: u8 = 0b00000000;
203 | if self.clean_session {
204 | connect_flags |= 0b10;
205 | };
206 | if self.username.is_some() {
207 | connect_flags |= 0b10000000;
208 | };
209 | if self.password.is_some() {
210 | connect_flags |= 0b01000000;
211 | };
212 | if let Some(last_will) = &self.last_will {
213 | connect_flags |= 0b00000100;
214 | connect_flags |= last_will.qos.as_u8() << 3;
215 | if last_will.retain {
216 | connect_flags |= 0b00100000;
217 | };
218 | };
219 | let length = self.len();
220 | check_remaining(buf, offset, length + 1)?;
221 |
222 | // NOTE: putting data into buffer.
223 | write_u8(buf, offset, header)?;
224 |
225 | let write_len = write_length(buf, offset, length)? + 1;
226 | self.protocol.to_buffer(buf, offset)?;
227 |
228 | write_u8(buf, offset, connect_flags)?;
229 | write_u16(buf, offset, self.keep_alive)?;
230 |
231 | write_string(buf, offset, self.client_id)?;
232 |
233 | if let Some(last_will) = &self.last_will {
234 | write_string(buf, offset, last_will.topic)?;
235 | write_bytes(buf, offset, &last_will.message)?;
236 | };
237 |
238 | if let Some(username) = self.username {
239 | write_string(buf, offset, username)?;
240 | };
241 | if let Some(password) = self.password {
242 | write_bytes(buf, offset, password)?;
243 | };
244 | // NOTE: END
245 | Ok(write_len)
246 | }
247 | }
248 |
249 | impl Connack {
250 | pub(crate) fn from_buffer(buf: &[u8], offset: &mut usize) -> Result {
251 | let flags = buf[*offset];
252 | let return_code = buf[*offset + 1];
253 | *offset += 2;
254 | Ok(Connack {
255 | session_present: (flags & 0b1 == 1),
256 | code: ConnectReturnCode::from_u8(return_code)?,
257 | })
258 | }
259 | pub(crate) fn to_buffer(&self, buf: &mut [u8], offset: &mut usize) -> Result {
260 | check_remaining(buf, offset, 4)?;
261 | let header: u8 = 0b00100000;
262 | let length: u8 = 2;
263 | let mut flags: u8 = 0b00000000;
264 | if self.session_present {
265 | flags |= 0b1;
266 | };
267 | let rc = self.code.as_u8();
268 | write_u8(buf, offset, header)?;
269 | write_u8(buf, offset, length)?;
270 | write_u8(buf, offset, flags)?;
271 | write_u8(buf, offset, rc)?;
272 | Ok(4)
273 | }
274 | }
275 |
--------------------------------------------------------------------------------
/mqttrust/src/encoding/v4/decoder.rs:
--------------------------------------------------------------------------------
1 | use super::*;
2 |
3 | pub fn decode_slice(buf: &[u8]) -> Result