1
0
mirror of https://github.com/actix/actix-extras.git synced 2025-09-02 13:06:38 +02:00

add MQTT and AMQP packages

This commit is contained in:
Rob Ede
2020-09-28 02:59:57 +01:00
parent e3da3094f0
commit 7de64899b4
85 changed files with 20566 additions and 7 deletions

24
actix-mqtt/CHANGES.md Executable file
View File

@@ -0,0 +1,24 @@
# Changes
## Unreleased - 2020-xx-xx
## 0.2.3 - 2020-03-10
* Add server handshake timeout
## 0.2.2 - 2020-02-04
* Fix server keep-alive impl
## 0.2.1 - 2019-12-25
* Allow to specify multi-pattern for topics
## 0.2.0 - 2019-12-11
* Migrate to `std::future`
* Support publish with QoS 1
## 0.1.0 - 2019-09-25
* Initial release

38
actix-mqtt/Cargo.toml Executable file
View File

@@ -0,0 +1,38 @@
[package]
name = "actix-mqtt"
version = "0.2.3"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "MQTT v3.1.1 Client/Server framework"
documentation = "https://docs.rs/actix-mqtt"
repository = "https://github.com/actix/actix-extras.git"
categories = ["network-programming"]
keywords = ["MQTT", "IoT", "messaging"]
license = "MIT OR Apache-2.0"
edition = "2018"
[dependencies]
mqtt-codec = "0.3.0"
actix-codec = "0.2.0"
actix-service = "1.0.1"
actix-utils = "1.0.4"
actix-router = "0.2.2"
actix-ioframe = "0.4.1"
actix-rt = "1.0.0"
derive_more = "0.99.2"
bytes = "0.5.3"
either = "1.5.2"
futures = "0.3.1"
pin-project = "0.4.6"
log = "0.4"
bytestring = "0.1.2"
serde = "1.0"
serde_json = "1.0"
uuid = { version = "0.8", features = ["v4"] }
[dev-dependencies]
env_logger = "0.6"
actix-connect = "1.0.1"
actix-server = "1.0.0"
actix-testing = "1.0.0"
actix-rt = "1.0.0"

201
actix-mqtt/LICENSE-APACHE Executable file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2019-NOW Nikolay Kim
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

25
actix-mqtt/LICENSE-MIT Executable file
View File

@@ -0,0 +1,25 @@
Copyright (c) 2019 Nikolay Kim
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

1
actix-mqtt/README.md Executable file
View File

@@ -0,0 +1 @@
# MQTT 3.1.1 Client/Server framework [![Build Status](https://travis-ci.org/actix/actix-mqtt.svg?branch=master)](https://travis-ci.org/actix/actix-mqtt) [![codecov](https://codecov.io/gh/actix/actix-mqtt/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-mqtt) [![crates.io](https://meritbadge.herokuapp.com/actix-mqtt)](https://crates.io/crates/actix-mqtt) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

17
actix-mqtt/codec/CHANGES.md Executable file
View File

@@ -0,0 +1,17 @@
# Changes
## Unreleased - 2020-xx-xx
## 0.3.0 - 2019-12-11
* Use `bytestring` instead of string
* Upgrade actix-codec to 0.2.0
## 0.2.0 - 2019-09-09
* Remove `Packet::Empty`
* Add `max frame size` config
## 0.1.0 - 2019-06-18
* Initial release

21
actix-mqtt/codec/Cargo.toml Executable file
View File

@@ -0,0 +1,21 @@
[package]
name = "mqtt-codec"
version = "0.3.0"
authors = [
"Max Gortman <mgortman@microsoft.com>",
"Nikolay Kim <fafhrd91@gmail.com>",
"Flier Lu <flier.lu@gmail.com>",
]
description = "MQTT v3.1.1 Codec"
documentation = "https://docs.rs/mqtt-codec"
repository = "https://github.com/actix/actix-mqtt.git"
readme = "README.md"
keywords = ["MQTT", "IoT", "messaging"]
license = "MIT/Apache-2.0"
edition = "2018"
[dependencies]
bitflags = "1.0"
bytes = "0.5.2"
bytestring = "0.1.0"
actix-codec = "0.2.0"

201
actix-mqtt/codec/LICENSE-APACHE Executable file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2019-NOW Nikolay Kim
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

25
actix-mqtt/codec/LICENSE-MIT Executable file
View File

@@ -0,0 +1,25 @@
Copyright (c) 2019 Nikolay Kim
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

3
actix-mqtt/codec/README.md Executable file
View File

@@ -0,0 +1,3 @@
# MQTT v3.1 Codec
MQTT v3.1 Codec implementation

View File

@@ -0,0 +1,616 @@
use std::convert::TryFrom;
use std::num::{NonZeroU16, NonZeroU32};
use bytes::{Buf, Bytes};
use bytestring::ByteString;
use crate::error::ParseError;
use crate::packet::*;
use crate::proto::*;
use super::{ConnectAckFlags, ConnectFlags, FixedHeader, WILL_QOS_SHIFT};
pub(crate) fn read_packet(mut src: Bytes, header: FixedHeader) -> Result<Packet, ParseError> {
match header.packet_type {
CONNECT => decode_connect_packet(&mut src),
CONNACK => decode_connect_ack_packet(&mut src),
PUBLISH => decode_publish_packet(&mut src, header),
PUBACK => Ok(Packet::PublishAck {
packet_id: NonZeroU16::parse(&mut src)?,
}),
PUBREC => Ok(Packet::PublishReceived {
packet_id: NonZeroU16::parse(&mut src)?,
}),
PUBREL => Ok(Packet::PublishRelease {
packet_id: NonZeroU16::parse(&mut src)?,
}),
PUBCOMP => Ok(Packet::PublishComplete {
packet_id: NonZeroU16::parse(&mut src)?,
}),
SUBSCRIBE => decode_subscribe_packet(&mut src),
SUBACK => decode_subscribe_ack_packet(&mut src),
UNSUBSCRIBE => decode_unsubscribe_packet(&mut src),
UNSUBACK => Ok(Packet::UnsubscribeAck {
packet_id: NonZeroU16::parse(&mut src)?,
}),
PINGREQ => Ok(Packet::PingRequest),
PINGRESP => Ok(Packet::PingResponse),
DISCONNECT => Ok(Packet::Disconnect),
_ => Err(ParseError::UnsupportedPacketType),
}
}
macro_rules! check_flag {
($flags:expr, $flag:expr) => {
($flags & $flag.bits()) == $flag.bits()
};
}
macro_rules! ensure {
($cond:expr, $e:expr) => {
if !($cond) {
return Err($e);
}
};
($cond:expr, $fmt:expr, $($arg:tt)+) => {
if !($cond) {
return Err($fmt, $($arg)+);
}
};
}
pub fn decode_variable_length(src: &[u8]) -> Result<Option<(usize, usize)>, ParseError> {
if let Some((len, consumed, more)) = src
.iter()
.enumerate()
.scan((0, true), |state, (idx, x)| {
if !state.1 || idx > 3 {
return None;
}
state.0 += ((x & 0x7F) as usize) << (idx * 7);
state.1 = x & 0x80 != 0;
Some((state.0, idx + 1, state.1))
})
.last()
{
ensure!(!more || consumed < 4, ParseError::InvalidLength);
return Ok(Some((len, consumed)));
}
Ok(None)
}
fn decode_connect_packet(src: &mut Bytes) -> Result<Packet, ParseError> {
ensure!(src.remaining() >= 10, ParseError::InvalidLength);
let len = src.get_u16();
ensure!(
len == 4 && &src.bytes()[0..4] == b"MQTT",
ParseError::InvalidProtocol
);
src.advance(4);
let level = src.get_u8();
ensure!(
level == DEFAULT_MQTT_LEVEL,
ParseError::UnsupportedProtocolLevel
);
let flags = src.get_u8();
ensure!((flags & 0x01) == 0, ParseError::ConnectReservedFlagSet);
let keep_alive = src.get_u16();
let client_id = decode_utf8_str(src)?;
ensure!(
!client_id.is_empty() || check_flag!(flags, ConnectFlags::CLEAN_SESSION),
ParseError::InvalidClientId
);
let topic = if check_flag!(flags, ConnectFlags::WILL) {
Some(decode_utf8_str(src)?)
} else {
None
};
let message = if check_flag!(flags, ConnectFlags::WILL) {
Some(decode_length_bytes(src)?)
} else {
None
};
let username = if check_flag!(flags, ConnectFlags::USERNAME) {
Some(decode_utf8_str(src)?)
} else {
None
};
let password = if check_flag!(flags, ConnectFlags::PASSWORD) {
Some(decode_length_bytes(src)?)
} else {
None
};
let last_will = if let Some(topic) = topic {
Some(LastWill {
qos: QoS::from((flags & ConnectFlags::WILL_QOS.bits()) >> WILL_QOS_SHIFT),
retain: check_flag!(flags, ConnectFlags::WILL_RETAIN),
topic,
message: message.unwrap(),
})
} else {
None
};
Ok(Packet::Connect(Connect {
protocol: Protocol::MQTT(level),
clean_session: check_flag!(flags, ConnectFlags::CLEAN_SESSION),
keep_alive,
client_id,
last_will,
username,
password,
}))
}
fn decode_connect_ack_packet(src: &mut Bytes) -> Result<Packet, ParseError> {
ensure!(src.remaining() >= 2, ParseError::InvalidLength);
let flags = src.get_u8();
ensure!(
(flags & 0b1111_1110) == 0,
ParseError::ConnAckReservedFlagSet
);
let return_code = src.get_u8();
Ok(Packet::ConnectAck {
session_present: check_flag!(flags, ConnectAckFlags::SESSION_PRESENT),
return_code: ConnectCode::from(return_code),
})
}
fn decode_publish_packet(src: &mut Bytes, header: FixedHeader) -> Result<Packet, ParseError> {
let topic = decode_utf8_str(src)?;
let qos = QoS::from((header.packet_flags & 0b0110) >> 1);
let packet_id = if qos == QoS::AtMostOnce {
None
} else {
Some(NonZeroU16::parse(src)?)
};
let len = src.remaining();
let payload = src.split_to(len);
Ok(Packet::Publish(Publish {
dup: (header.packet_flags & 0b1000) == 0b1000,
qos,
retain: (header.packet_flags & 0b0001) == 0b0001,
topic,
packet_id,
payload,
}))
}
fn decode_subscribe_packet(src: &mut Bytes) -> Result<Packet, ParseError> {
let packet_id = NonZeroU16::parse(src)?;
let mut topic_filters = Vec::new();
while src.remaining() > 0 {
let topic = decode_utf8_str(src)?;
ensure!(src.remaining() >= 1, ParseError::InvalidLength);
let qos = QoS::from(src.get_u8() & 0x03);
topic_filters.push((topic, qos));
}
Ok(Packet::Subscribe {
packet_id,
topic_filters,
})
}
fn decode_subscribe_ack_packet(src: &mut Bytes) -> Result<Packet, ParseError> {
let packet_id = NonZeroU16::parse(src)?;
let status = src
.bytes()
.iter()
.map(|code| {
if *code == 0x80 {
SubscribeReturnCode::Failure
} else {
SubscribeReturnCode::Success(QoS::from(code & 0x03))
}
})
.collect();
Ok(Packet::SubscribeAck { packet_id, status })
}
fn decode_unsubscribe_packet(src: &mut Bytes) -> Result<Packet, ParseError> {
let packet_id = NonZeroU16::parse(src)?;
let mut topic_filters = Vec::new();
while src.remaining() > 0 {
topic_filters.push(decode_utf8_str(src)?);
}
Ok(Packet::Unsubscribe {
packet_id,
topic_filters,
})
}
fn decode_length_bytes(src: &mut Bytes) -> Result<Bytes, ParseError> {
let len: u16 = NonZeroU16::parse(src)?.into();
ensure!(src.remaining() >= len as usize, ParseError::InvalidLength);
Ok(src.split_to(len as usize))
}
fn decode_utf8_str(src: &mut Bytes) -> Result<ByteString, ParseError> {
Ok(ByteString::try_from(decode_length_bytes(src)?)?)
}
pub(crate) trait ByteBuf: Buf {
fn inner_mut(&mut self) -> &mut Bytes;
}
impl ByteBuf for Bytes {
fn inner_mut(&mut self) -> &mut Bytes {
self
}
}
impl ByteBuf for bytes::buf::ext::Take<&mut Bytes> {
fn inner_mut(&mut self) -> &mut Bytes {
self.get_mut()
}
}
pub(crate) trait Parse: Sized {
fn parse<B: ByteBuf>(src: &mut B) -> Result<Self, ParseError>;
}
impl Parse for bool {
fn parse<B: ByteBuf>(src: &mut B) -> Result<Self, ParseError> {
ensure!(src.has_remaining(), ParseError::InvalidLength); // expected more data within the field
let v = src.get_u8();
ensure!(v <= 0x1, ParseError::MalformedPacket); // value is invalid
Ok(v == 0x1)
}
}
impl Parse for u16 {
fn parse<B: ByteBuf>(src: &mut B) -> Result<Self, ParseError> {
ensure!(src.remaining() >= 2, ParseError::InvalidLength);
Ok(src.get_u16())
}
}
impl Parse for u32 {
fn parse<B: ByteBuf>(src: &mut B) -> Result<Self, ParseError> {
ensure!(src.remaining() >= 4, ParseError::InvalidLength); // expected more data within the field
let val = src.get_u32();
Ok(val)
}
}
impl Parse for NonZeroU32 {
fn parse<B: ByteBuf>(src: &mut B) -> Result<Self, ParseError> {
let val = NonZeroU32::new(u32::parse(src)?).ok_or(ParseError::MalformedPacket)?;
Ok(val)
}
}
impl Parse for NonZeroU16 {
fn parse<B: ByteBuf>(src: &mut B) -> Result<Self, ParseError> {
Ok(NonZeroU16::new(u16::parse(src)?).ok_or(ParseError::MalformedPacket)?)
}
}
impl Parse for Bytes {
fn parse<B: ByteBuf>(src: &mut B) -> Result<Self, ParseError> {
let len = u16::parse(src)? as usize;
ensure!(src.remaining() >= len, ParseError::InvalidLength);
Ok(src.inner_mut().split_to(len))
}
}
pub(crate) type ByteStr = ByteString;
impl Parse for ByteStr {
fn parse<B: ByteBuf>(src: &mut B) -> Result<Self, ParseError> {
let bytes = Bytes::parse(src)?;
Ok(ByteString::try_from(bytes)?)
}
}
impl Parse for (ByteStr, ByteStr) {
fn parse<B: ByteBuf>(src: &mut B) -> Result<Self, ParseError> {
let key = ByteStr::parse(src)?;
let val = ByteStr::parse(src)?;
Ok((key, val))
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! assert_decode_packet (
($bytes:expr, $res:expr) => {{
let fixed = $bytes.as_ref()[0];
let (_len, consumned) = decode_variable_length(&$bytes[1..]).unwrap().unwrap();
let hdr = FixedHeader {
packet_type: fixed >> 4,
packet_flags: fixed & 0xF,
remaining_length: $bytes.len() - consumned - 1,
};
let cur = Bytes::from_static(&$bytes[consumned + 1..]);
assert_eq!(read_packet(cur, hdr), Ok($res));
}};
);
fn packet_id(v: u16) -> NonZeroU16 {
NonZeroU16::new(v).unwrap()
}
#[test]
fn test_decode_variable_length() {
macro_rules! assert_variable_length (
($bytes:expr, $res:expr) => {{
assert_eq!(decode_variable_length($bytes), Ok(Some($res)));
}};
($bytes:expr, $res:expr, $rest:expr) => {{
assert_eq!(decode_variable_length($bytes), Ok(Some($res)));
}};
);
assert_variable_length!(b"\x7f\x7f", (127, 1), b"\x7f");
//assert_eq!(decode_variable_length(b"\xff\xff\xff"), Ok(None));
assert_eq!(
decode_variable_length(b"\xff\xff\xff\xff\xff\xff"),
Err(ParseError::InvalidLength)
);
assert_variable_length!(b"\x00", (0, 1));
assert_variable_length!(b"\x7f", (127, 1));
assert_variable_length!(b"\x80\x01", (128, 2));
assert_variable_length!(b"\xff\x7f", (16383, 2));
assert_variable_length!(b"\x80\x80\x01", (16384, 3));
assert_variable_length!(b"\xff\xff\x7f", (2097151, 3));
assert_variable_length!(b"\x80\x80\x80\x01", (2097152, 4));
assert_variable_length!(b"\xff\xff\xff\x7f", (268435455, 4));
}
// #[test]
// fn test_decode_header() {
// assert_eq!(
// decode_header(b"\x20\x7f"),
// Done(
// &b""[..],
// FixedHeader {
// packet_type: CONNACK,
// packet_flags: 0,
// remaining_length: 127,
// }
// )
// );
// assert_eq!(
// decode_header(b"\x3C\x82\x7f"),
// Done(
// &b""[..],
// FixedHeader {
// packet_type: PUBLISH,
// packet_flags: 0x0C,
// remaining_length: 16258,
// }
// )
// );
// assert_eq!(decode_header(b"\x20"), Incomplete(Needed::Unknown));
// }
#[test]
fn test_decode_connect_packets() {
assert_eq!(
decode_connect_packet(&mut Bytes::from_static(
b"\x00\x04MQTT\x04\xC0\x00\x3C\x00\x0512345\x00\x04user\x00\x04pass"
)),
Ok(Packet::Connect(Connect {
protocol: Protocol::MQTT(4),
clean_session: false,
keep_alive: 60,
client_id: ByteString::try_from(Bytes::from_static(b"12345")).unwrap(),
last_will: None,
username: Some(ByteString::try_from(Bytes::from_static(b"user")).unwrap()),
password: Some(Bytes::from(&b"pass"[..])),
}))
);
assert_eq!(
decode_connect_packet(&mut Bytes::from_static(
b"\x00\x04MQTT\x04\x14\x00\x3C\x00\x0512345\x00\x05topic\x00\x07message"
)),
Ok(Packet::Connect(Connect {
protocol: Protocol::MQTT(4),
clean_session: false,
keep_alive: 60,
client_id: ByteString::try_from(Bytes::from_static(b"12345")).unwrap(),
last_will: Some(LastWill {
qos: QoS::ExactlyOnce,
retain: false,
topic: ByteString::try_from(Bytes::from_static(b"topic")).unwrap(),
message: Bytes::from(&b"message"[..]),
}),
username: None,
password: None,
}))
);
assert_eq!(
decode_connect_packet(&mut Bytes::from_static(b"\x00\x02MQ00000000000000000000")),
Err(ParseError::InvalidProtocol),
);
assert_eq!(
decode_connect_packet(&mut Bytes::from_static(b"\x00\x10MQ00000000000000000000")),
Err(ParseError::InvalidProtocol),
);
assert_eq!(
decode_connect_packet(&mut Bytes::from_static(b"\x00\x04MQAA00000000000000000000")),
Err(ParseError::InvalidProtocol),
);
assert_eq!(
decode_connect_packet(&mut Bytes::from_static(
b"\x00\x04MQTT\x0300000000000000000000"
)),
Err(ParseError::UnsupportedProtocolLevel),
);
assert_eq!(
decode_connect_packet(&mut Bytes::from_static(
b"\x00\x04MQTT\x04\xff00000000000000000000"
)),
Err(ParseError::ConnectReservedFlagSet)
);
assert_eq!(
decode_connect_ack_packet(&mut Bytes::from_static(b"\x01\x04")),
Ok(Packet::ConnectAck {
session_present: true,
return_code: ConnectCode::BadUserNameOrPassword
})
);
assert_eq!(
decode_connect_ack_packet(&mut Bytes::from_static(b"\x03\x04")),
Err(ParseError::ConnAckReservedFlagSet)
);
assert_decode_packet!(
b"\x20\x02\x01\x04",
Packet::ConnectAck {
session_present: true,
return_code: ConnectCode::BadUserNameOrPassword,
}
);
assert_decode_packet!(b"\xe0\x00", Packet::Disconnect);
}
#[test]
fn test_decode_publish_packets() {
//assert_eq!(
// decode_publish_packet(b"\x00\x05topic\x12\x34"),
// Done(&b""[..], ("topic".to_owned(), 0x1234))
//);
assert_decode_packet!(
b"\x3d\x0D\x00\x05topic\x43\x21data",
Packet::Publish(Publish {
dup: true,
retain: true,
qos: QoS::ExactlyOnce,
topic: ByteString::try_from(Bytes::from_static(b"topic")).unwrap(),
packet_id: Some(packet_id(0x4321)),
payload: Bytes::from_static(b"data"),
})
);
assert_decode_packet!(
b"\x30\x0b\x00\x05topicdata",
Packet::Publish(Publish {
dup: false,
retain: false,
qos: QoS::AtMostOnce,
topic: ByteString::try_from(Bytes::from_static(b"topic")).unwrap(),
packet_id: None,
payload: Bytes::from_static(b"data"),
})
);
assert_decode_packet!(
b"\x40\x02\x43\x21",
Packet::PublishAck {
packet_id: packet_id(0x4321),
}
);
assert_decode_packet!(
b"\x50\x02\x43\x21",
Packet::PublishReceived {
packet_id: packet_id(0x4321),
}
);
assert_decode_packet!(
b"\x60\x02\x43\x21",
Packet::PublishRelease {
packet_id: packet_id(0x4321),
}
);
assert_decode_packet!(
b"\x70\x02\x43\x21",
Packet::PublishComplete {
packet_id: packet_id(0x4321),
}
);
}
#[test]
fn test_decode_subscribe_packets() {
let p = Packet::Subscribe {
packet_id: packet_id(0x1234),
topic_filters: vec![
(
ByteString::try_from(Bytes::from_static(b"test")).unwrap(),
QoS::AtLeastOnce,
),
(
ByteString::try_from(Bytes::from_static(b"filter")).unwrap(),
QoS::ExactlyOnce,
),
],
};
assert_eq!(
decode_subscribe_packet(&mut Bytes::from_static(
b"\x12\x34\x00\x04test\x01\x00\x06filter\x02"
)),
Ok(p.clone())
);
assert_decode_packet!(b"\x82\x12\x12\x34\x00\x04test\x01\x00\x06filter\x02", p);
let p = Packet::SubscribeAck {
packet_id: packet_id(0x1234),
status: vec![
SubscribeReturnCode::Success(QoS::AtLeastOnce),
SubscribeReturnCode::Failure,
SubscribeReturnCode::Success(QoS::ExactlyOnce),
],
};
assert_eq!(
decode_subscribe_ack_packet(&mut Bytes::from_static(b"\x12\x34\x01\x80\x02")),
Ok(p.clone())
);
assert_decode_packet!(b"\x90\x05\x12\x34\x01\x80\x02", p);
let p = Packet::Unsubscribe {
packet_id: packet_id(0x1234),
topic_filters: vec![
ByteString::try_from(Bytes::from_static(b"test")).unwrap(),
ByteString::try_from(Bytes::from_static(b"filter")).unwrap(),
],
};
assert_eq!(
decode_unsubscribe_packet(&mut Bytes::from_static(
b"\x12\x34\x00\x04test\x00\x06filter"
)),
Ok(p.clone())
);
assert_decode_packet!(b"\xa2\x10\x12\x34\x00\x04test\x00\x06filter", p);
assert_decode_packet!(
b"\xb0\x02\x43\x21",
Packet::UnsubscribeAck {
packet_id: packet_id(0x4321),
}
);
}
#[test]
fn test_decode_ping_packets() {
assert_decode_packet!(b"\xc0\x00", Packet::PingRequest);
assert_decode_packet!(b"\xd0\x00", Packet::PingResponse);
}
}

View File

@@ -0,0 +1,446 @@
use bytes::{BufMut, BytesMut};
use crate::packet::*;
use crate::proto::*;
use super::{ConnectFlags, WILL_QOS_SHIFT};
pub fn write_packet(packet: &Packet, dst: &mut BytesMut, content_size: usize) {
write_fixed_header(packet, dst, content_size);
write_content(packet, dst);
}
pub fn get_encoded_size(packet: &Packet) -> usize {
match *packet {
Packet::Connect ( ref connect ) => {
match *connect {
Connect {ref last_will, ref client_id, ref username, ref password, ..} =>
{
// Protocol Name + Protocol Level + Connect Flags + Keep Alive
let mut n = 2 + 4 + 1 + 1 + 2;
// Client Id
n += 2 + client_id.len();
// Will Topic + Will Message
if let Some(LastWill { ref topic, ref message, .. }) = *last_will {
n += 2 + topic.len() + 2 + message.len();
}
if let Some(ref s) = *username {
n += 2 + s.len();
}
if let Some(ref s) = *password {
n += 2 + s.len();
}
n
}
}
}
Packet::Publish( Publish{ qos, ref topic, ref payload, .. }) => {
// Topic + Packet Id + Payload
if qos == QoS::AtLeastOnce || qos == QoS::ExactlyOnce {
4 + topic.len() + payload.len()
} else {
2 + topic.len() + payload.len()
}
}
Packet::ConnectAck { .. } | // Flags + Return Code
Packet::PublishAck { .. } | // Packet Id
Packet::PublishReceived { .. } | // Packet Id
Packet::PublishRelease { .. } | // Packet Id
Packet::PublishComplete { .. } | // Packet Id
Packet::UnsubscribeAck { .. } => 2, // Packet Id
Packet::Subscribe { ref topic_filters, .. } => {
2 + topic_filters.iter().fold(0, |acc, &(ref filter, _)| acc + 2 + filter.len() + 1)
}
Packet::SubscribeAck { ref status, .. } => 2 + status.len(),
Packet::Unsubscribe { ref topic_filters, .. } => {
2 + topic_filters.iter().fold(0, |acc, filter| acc + 2 + filter.len())
}
Packet::PingRequest | Packet::PingResponse | Packet::Disconnect => 0,
}
}
#[inline]
fn write_fixed_header(packet: &Packet, dst: &mut BytesMut, content_size: usize) {
dst.put_u8((packet.packet_type() << 4) | packet.packet_flags());
write_variable_length(content_size, dst);
}
fn write_content(packet: &Packet, dst: &mut BytesMut) {
match *packet {
Packet::Connect(ref connect) => match *connect {
Connect {
protocol,
clean_session,
keep_alive,
ref last_will,
ref client_id,
ref username,
ref password,
} => {
write_slice(protocol.name().as_bytes(), dst);
let mut flags = ConnectFlags::empty();
if username.is_some() {
flags |= ConnectFlags::USERNAME;
}
if password.is_some() {
flags |= ConnectFlags::PASSWORD;
}
if let Some(LastWill { qos, retain, .. }) = *last_will {
flags |= ConnectFlags::WILL;
if retain {
flags |= ConnectFlags::WILL_RETAIN;
}
let b: u8 = qos as u8;
flags |= ConnectFlags::from_bits_truncate(b << WILL_QOS_SHIFT);
}
if clean_session {
flags |= ConnectFlags::CLEAN_SESSION;
}
dst.put_slice(&[protocol.level(), flags.bits()]);
dst.put_u16(keep_alive);
write_slice(client_id.as_bytes(), dst);
if let Some(LastWill {
ref topic,
ref message,
..
}) = *last_will
{
write_slice(topic.as_bytes(), dst);
write_slice(&message, dst);
}
if let Some(ref s) = *username {
write_slice(s.as_bytes(), dst);
}
if let Some(ref s) = *password {
write_slice(s, dst);
}
}
},
Packet::ConnectAck {
session_present,
return_code,
} => {
dst.put_slice(&[if session_present { 0x01 } else { 0x00 }, return_code as u8]);
}
Packet::Publish(Publish {
qos,
ref topic,
packet_id,
ref payload,
..
}) => {
write_slice(topic.as_bytes(), dst);
if qos == QoS::AtLeastOnce || qos == QoS::ExactlyOnce {
dst.put_u16(packet_id.unwrap().into());
}
dst.put(payload.as_ref());
}
Packet::PublishAck { packet_id }
| Packet::PublishReceived { packet_id }
| Packet::PublishRelease { packet_id }
| Packet::PublishComplete { packet_id }
| Packet::UnsubscribeAck { packet_id } => {
dst.put_u16(packet_id.into());
}
Packet::Subscribe {
packet_id,
ref topic_filters,
} => {
dst.put_u16(packet_id.into());
for &(ref filter, qos) in topic_filters {
write_slice(filter.as_ref(), dst);
dst.put_slice(&[qos as u8]);
}
}
Packet::SubscribeAck {
packet_id,
ref status,
} => {
dst.put_u16(packet_id.into());
let buf: Vec<u8> = status
.iter()
.map(|s| {
if let SubscribeReturnCode::Success(qos) = *s {
qos as u8
} else {
0x80
}
})
.collect();
dst.put_slice(&buf);
}
Packet::Unsubscribe {
packet_id,
ref topic_filters,
} => {
dst.put_u16(packet_id.into());
for filter in topic_filters {
write_slice(filter.as_ref(), dst);
}
}
Packet::PingRequest | Packet::PingResponse | Packet::Disconnect => {}
}
}
#[inline]
fn write_slice(r: &[u8], dst: &mut BytesMut) {
dst.put_u16(r.len() as u16);
dst.put_slice(r);
}
#[inline]
fn write_variable_length(size: usize, dst: &mut BytesMut) {
// todo: verify at higher level
// if size > MAX_VARIABLE_LENGTH {
// Err(Error::new(ErrorKind::Other, "out of range"))
if size <= 127 {
dst.put_u8(size as u8);
} else if size <= 16383 {
// 127 + 127 << 7
dst.put_slice(&[((size % 128) | 0x80) as u8, (size >> 7) as u8]);
} else if size <= 2_097_151 {
// 127 + 127 << 7 + 127 << 14
dst.put_slice(&[
((size % 128) | 0x80) as u8,
(((size >> 7) % 128) | 0x80) as u8,
(size >> 14) as u8,
]);
} else {
dst.put_slice(&[
((size % 128) | 0x80) as u8,
(((size >> 7) % 128) | 0x80) as u8,
(((size >> 14) % 128) | 0x80) as u8,
(size >> 21) as u8,
]);
}
}
#[cfg(test)]
mod tests {
use bytes::Bytes;
use bytestring::ByteString;
use std::num::NonZeroU16;
use super::*;
fn packet_id(v: u16) -> NonZeroU16 {
NonZeroU16::new(v).unwrap()
}
#[test]
fn test_encode_variable_length() {
let mut v = BytesMut::new();
write_variable_length(123, &mut v);
assert_eq!(v, [123].as_ref());
v.clear();
write_variable_length(129, &mut v);
assert_eq!(v, b"\x81\x01".as_ref());
v.clear();
write_variable_length(16383, &mut v);
assert_eq!(v, b"\xff\x7f".as_ref());
v.clear();
write_variable_length(2097151, &mut v);
assert_eq!(v, b"\xff\xff\x7f".as_ref());
v.clear();
write_variable_length(268435455, &mut v);
assert_eq!(v, b"\xff\xff\xff\x7f".as_ref());
// assert!(v.write_variable_length(MAX_VARIABLE_LENGTH + 1).is_err())
}
#[test]
fn test_encode_fixed_header() {
let mut v = BytesMut::new();
let p = Packet::PingRequest;
assert_eq!(get_encoded_size(&p), 0);
write_fixed_header(&p, &mut v, 0);
assert_eq!(v, b"\xc0\x00".as_ref());
v.clear();
let p = Packet::Publish(Publish {
dup: true,
retain: true,
qos: QoS::ExactlyOnce,
topic: ByteString::from_static("topic"),
packet_id: Some(packet_id(0x4321)),
payload: (0..255).collect::<Vec<u8>>().into(),
});
assert_eq!(get_encoded_size(&p), 264);
write_fixed_header(&p, &mut v, 264);
assert_eq!(v, b"\x3d\x88\x02".as_ref());
}
macro_rules! assert_packet {
($p:expr, $data:expr) => {
let mut v = BytesMut::with_capacity(1024);
write_packet(&$p, &mut v, get_encoded_size($p));
assert_eq!(v.len(), $data.len());
assert_eq!(v, &$data[..]);
// assert_eq!(read_packet($data.cursor()).unwrap(), (&b""[..], $p));
};
}
#[test]
fn test_encode_connect_packets() {
assert_packet!(
&Packet::Connect(Connect {
protocol: Protocol::MQTT(4),
clean_session: false,
keep_alive: 60,
client_id: ByteString::from_static("12345"),
last_will: None,
username: Some(ByteString::from_static("user")),
password: Some(Bytes::from_static(b"pass")),
}),
&b"\x10\x1D\x00\x04MQTT\x04\xC0\x00\x3C\x00\
\x0512345\x00\x04user\x00\x04pass"[..]
);
assert_packet!(
&Packet::Connect(Connect {
protocol: Protocol::MQTT(4),
clean_session: false,
keep_alive: 60,
client_id: ByteString::from_static("12345"),
last_will: Some(LastWill {
qos: QoS::ExactlyOnce,
retain: false,
topic: ByteString::from_static("topic"),
message: Bytes::from_static(b"message"),
}),
username: None,
password: None,
}),
&b"\x10\x21\x00\x04MQTT\x04\x14\x00\x3C\x00\
\x0512345\x00\x05topic\x00\x07message"[..]
);
assert_packet!(&Packet::Disconnect, b"\xe0\x00");
}
#[test]
fn test_encode_publish_packets() {
assert_packet!(
&Packet::Publish(Publish {
dup: true,
retain: true,
qos: QoS::ExactlyOnce,
topic: ByteString::from_static("topic"),
packet_id: Some(packet_id(0x4321)),
payload: Bytes::from_static(b"data"),
}),
b"\x3d\x0D\x00\x05topic\x43\x21data"
);
assert_packet!(
&Packet::Publish(Publish {
dup: false,
retain: false,
qos: QoS::AtMostOnce,
topic: ByteString::from_static("topic"),
packet_id: None,
payload: Bytes::from_static(b"data"),
}),
b"\x30\x0b\x00\x05topicdata"
);
}
#[test]
fn test_encode_subscribe_packets() {
assert_packet!(
&Packet::Subscribe {
packet_id: packet_id(0x1234),
topic_filters: vec![
(ByteString::from_static("test"), QoS::AtLeastOnce),
(ByteString::from_static("filter"), QoS::ExactlyOnce)
],
},
b"\x82\x12\x12\x34\x00\x04test\x01\x00\x06filter\x02"
);
assert_packet!(
&Packet::SubscribeAck {
packet_id: packet_id(0x1234),
status: vec![
SubscribeReturnCode::Success(QoS::AtLeastOnce),
SubscribeReturnCode::Failure,
SubscribeReturnCode::Success(QoS::ExactlyOnce)
],
},
b"\x90\x05\x12\x34\x01\x80\x02"
);
assert_packet!(
&Packet::Unsubscribe {
packet_id: packet_id(0x1234),
topic_filters: vec![
ByteString::from_static("test"),
ByteString::from_static("filter"),
],
},
b"\xa2\x10\x12\x34\x00\x04test\x00\x06filter"
);
assert_packet!(
&Packet::UnsubscribeAck {
packet_id: packet_id(0x4321)
},
b"\xb0\x02\x43\x21"
);
}
#[test]
fn test_encode_ping_packets() {
assert_packet!(&Packet::PingRequest, b"\xc0\x00");
assert_packet!(&Packet::PingResponse, b"\xd0\x00");
}
}

161
actix-mqtt/codec/src/codec/mod.rs Executable file
View File

@@ -0,0 +1,161 @@
use actix_codec::{Decoder, Encoder};
use bytes::{buf::Buf, BytesMut};
use crate::error::ParseError;
use crate::proto::QoS;
use crate::{Packet, Publish};
mod decode;
mod encode;
use self::decode::*;
use self::encode::*;
bitflags! {
pub struct ConnectFlags: u8 {
const USERNAME = 0b1000_0000;
const PASSWORD = 0b0100_0000;
const WILL_RETAIN = 0b0010_0000;
const WILL_QOS = 0b0001_1000;
const WILL = 0b0000_0100;
const CLEAN_SESSION = 0b0000_0010;
}
}
pub const WILL_QOS_SHIFT: u8 = 3;
bitflags! {
pub struct ConnectAckFlags: u8 {
const SESSION_PRESENT = 0b0000_0001;
}
}
#[derive(Debug)]
pub struct Codec {
state: DecodeState,
max_size: usize,
}
#[derive(Debug, Clone, Copy)]
enum DecodeState {
FrameHeader,
Frame(FixedHeader),
}
impl Codec {
/// Create `Codec` instance
pub fn new() -> Self {
Codec {
state: DecodeState::FrameHeader,
max_size: 0,
}
}
/// Set max inbound frame size.
///
/// If max size is set to `0`, size is unlimited.
/// By default max size is set to `0`
pub fn max_size(mut self, size: usize) -> Self {
self.max_size = size;
self
}
}
impl Default for Codec {
fn default() -> Self {
Self::new()
}
}
impl Decoder for Codec {
type Item = Packet;
type Error = ParseError;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, ParseError> {
loop {
match self.state {
DecodeState::FrameHeader => {
if src.len() < 2 {
return Ok(None);
}
let fixed = src.as_ref()[0];
match decode_variable_length(&src.as_ref()[1..])? {
Some((remaining_length, consumed)) => {
// check max message size
if self.max_size != 0 && self.max_size < remaining_length {
return Err(ParseError::MaxSizeExceeded);
}
src.advance(consumed + 1);
self.state = DecodeState::Frame(FixedHeader {
packet_type: fixed >> 4,
packet_flags: fixed & 0xF,
remaining_length,
});
// todo: validate remaining_length against max frame size config
if src.len() < remaining_length {
// todo: subtract?
src.reserve(remaining_length); // extend receiving buffer to fit the whole frame -- todo: too eager?
return Ok(None);
}
}
None => {
return Ok(None);
}
}
}
DecodeState::Frame(fixed) => {
if src.len() < fixed.remaining_length {
return Ok(None);
}
let packet_buf = src.split_to(fixed.remaining_length);
let packet = read_packet(packet_buf.freeze(), fixed)?;
self.state = DecodeState::FrameHeader;
src.reserve(2);
return Ok(Some(packet));
}
}
}
}
}
impl Encoder for Codec {
type Item = Packet;
type Error = ParseError;
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), ParseError> {
if let Packet::Publish(Publish { qos, packet_id, .. }) = item {
if (qos == QoS::AtLeastOnce || qos == QoS::ExactlyOnce) && packet_id.is_none() {
return Err(ParseError::PacketIdRequired);
}
}
let content_size = get_encoded_size(&item);
dst.reserve(content_size + 5);
write_packet(&item, dst, content_size);
Ok(())
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub(crate) struct FixedHeader {
/// MQTT Control Packet type
pub packet_type: u8,
/// Flags specific to each MQTT Control Packet type
pub packet_flags: u8,
/// the number of bytes remaining within the current packet,
/// including data in the variable header and the payload.
pub remaining_length: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_max_size() {
let mut codec = Codec::new().max_size(5);
let mut buf = BytesMut::new();
buf.extend_from_slice(b"\0\x09");
assert_eq!(codec.decode(&mut buf), Err(ParseError::MaxSizeExceeded));
}
}

57
actix-mqtt/codec/src/error.rs Executable file
View File

@@ -0,0 +1,57 @@
use std::{io, str};
#[derive(Debug)]
pub enum ParseError {
InvalidProtocol,
InvalidLength,
MalformedPacket,
UnsupportedProtocolLevel,
ConnectReservedFlagSet,
ConnAckReservedFlagSet,
InvalidClientId,
UnsupportedPacketType,
PacketIdRequired,
MaxSizeExceeded,
IoError(io::Error),
Utf8Error(str::Utf8Error),
}
impl PartialEq for ParseError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(ParseError::InvalidProtocol, ParseError::InvalidProtocol) => true,
(ParseError::InvalidLength, ParseError::InvalidLength) => true,
(ParseError::UnsupportedProtocolLevel, ParseError::UnsupportedProtocolLevel) => {
true
}
(ParseError::ConnectReservedFlagSet, ParseError::ConnectReservedFlagSet) => true,
(ParseError::ConnAckReservedFlagSet, ParseError::ConnAckReservedFlagSet) => true,
(ParseError::InvalidClientId, ParseError::InvalidClientId) => true,
(ParseError::UnsupportedPacketType, ParseError::UnsupportedPacketType) => true,
(ParseError::PacketIdRequired, ParseError::PacketIdRequired) => true,
(ParseError::MaxSizeExceeded, ParseError::MaxSizeExceeded) => true,
(ParseError::MalformedPacket, ParseError::MalformedPacket) => true,
(ParseError::IoError(_), _) => false,
(ParseError::Utf8Error(_), _) => false,
_ => false,
}
}
}
impl From<io::Error> for ParseError {
fn from(err: io::Error) -> Self {
ParseError::IoError(err)
}
}
impl From<str::Utf8Error> for ParseError {
fn from(err: str::Utf8Error) -> Self {
ParseError::Utf8Error(err)
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum TopicError {
InvalidTopic,
InvalidLevel,
}

22
actix-mqtt/codec/src/lib.rs Executable file
View File

@@ -0,0 +1,22 @@
#[macro_use]
extern crate bitflags;
extern crate bytestring;
mod error;
#[macro_use]
mod topic;
#[macro_use]
mod proto;
mod codec;
mod packet;
pub use self::codec::Codec;
pub use self::error::{ParseError, TopicError};
pub use self::packet::{Connect, ConnectCode, LastWill, Packet, Publish, SubscribeReturnCode};
pub use self::proto::{Protocol, QoS};
pub use self::topic::{Level, Topic};
// http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
pub const TCP_PORT: u16 = 1883;
pub const SSL_PORT: u16 = 8883;

251
actix-mqtt/codec/src/packet.rs Executable file
View File

@@ -0,0 +1,251 @@
use bytes::Bytes;
use bytestring::ByteString;
use std::num::NonZeroU16;
use crate::proto::{Protocol, QoS};
#[repr(u8)]
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
/// Connect Return Code
pub enum ConnectCode {
/// Connection accepted
ConnectionAccepted = 0,
/// Connection Refused, unacceptable protocol version
UnacceptableProtocolVersion = 1,
/// Connection Refused, identifier rejected
IdentifierRejected = 2,
/// Connection Refused, Server unavailable
ServiceUnavailable = 3,
/// Connection Refused, bad user name or password
BadUserNameOrPassword = 4,
/// Connection Refused, not authorized
NotAuthorized = 5,
/// Reserved
Reserved = 6,
}
const_enum!(ConnectCode: u8);
impl ConnectCode {
pub fn reason(self) -> &'static str {
match self {
ConnectCode::ConnectionAccepted => "Connection Accepted",
ConnectCode::UnacceptableProtocolVersion => {
"Connection Refused, unacceptable protocol version"
}
ConnectCode::IdentifierRejected => "Connection Refused, identifier rejected",
ConnectCode::ServiceUnavailable => "Connection Refused, Server unavailable",
ConnectCode::BadUserNameOrPassword => {
"Connection Refused, bad user name or password"
}
ConnectCode::NotAuthorized => "Connection Refused, not authorized",
_ => "Connection Refused",
}
}
}
#[derive(Debug, PartialEq, Clone)]
/// Connection Will
pub struct LastWill {
/// the QoS level to be used when publishing the Will Message.
pub qos: QoS,
/// the Will Message is to be Retained when it is published.
pub retain: bool,
/// the Will Topic
pub topic: ByteString,
/// defines the Application Message that is to be published to the Will Topic
pub message: Bytes,
}
#[derive(Debug, PartialEq, Clone)]
/// Connect packet content
pub struct Connect {
/// mqtt protocol version
pub protocol: Protocol,
/// the handling of the Session state.
pub clean_session: bool,
/// a time interval measured in seconds.
pub keep_alive: u16,
/// Will Message be stored on the Server and associated with the Network Connection.
pub last_will: Option<LastWill>,
/// identifies the Client to the Server.
pub client_id: ByteString,
/// username can be used by the Server for authentication and authorization.
pub username: Option<ByteString>,
/// password can be used by the Server for authentication and authorization.
pub password: Option<Bytes>,
}
#[derive(Debug, PartialEq, Clone)]
/// Publish message
pub struct Publish {
/// this might be re-delivery of an earlier attempt to send the Packet.
pub dup: bool,
pub retain: bool,
/// the level of assurance for delivery of an Application Message.
pub qos: QoS,
/// the information channel to which payload data is published.
pub topic: ByteString,
/// only present in PUBLISH Packets where the QoS level is 1 or 2.
pub packet_id: Option<NonZeroU16>,
/// the Application Message that is being published.
pub payload: Bytes,
}
#[derive(Debug, PartialEq, Copy, Clone)]
/// Subscribe Return Code
pub enum SubscribeReturnCode {
Success(QoS),
Failure,
}
#[derive(Debug, PartialEq, Clone)]
/// MQTT Control Packets
pub enum Packet {
/// Client request to connect to Server
Connect(Connect),
/// Connect acknowledgment
ConnectAck {
/// enables a Client to establish whether the Client and Server have a consistent view
/// about whether there is already stored Session state.
session_present: bool,
return_code: ConnectCode,
},
/// Publish message
Publish(Publish),
/// Publish acknowledgment
PublishAck {
/// Packet Identifier
packet_id: NonZeroU16,
},
/// Publish received (assured delivery part 1)
PublishReceived {
/// Packet Identifier
packet_id: NonZeroU16,
},
/// Publish release (assured delivery part 2)
PublishRelease {
/// Packet Identifier
packet_id: NonZeroU16,
},
/// Publish complete (assured delivery part 3)
PublishComplete {
/// Packet Identifier
packet_id: NonZeroU16,
},
/// Client subscribe request
Subscribe {
/// Packet Identifier
packet_id: NonZeroU16,
/// the list of Topic Filters and QoS to which the Client wants to subscribe.
topic_filters: Vec<(ByteString, QoS)>,
},
/// Subscribe acknowledgment
SubscribeAck {
packet_id: NonZeroU16,
/// corresponds to a Topic Filter in the SUBSCRIBE Packet being acknowledged.
status: Vec<SubscribeReturnCode>,
},
/// Unsubscribe request
Unsubscribe {
/// Packet Identifier
packet_id: NonZeroU16,
/// the list of Topic Filters that the Client wishes to unsubscribe from.
topic_filters: Vec<ByteString>,
},
/// Unsubscribe acknowledgment
UnsubscribeAck {
/// Packet Identifier
packet_id: NonZeroU16,
},
/// PING request
PingRequest,
/// PING response
PingResponse,
/// Client is disconnecting
Disconnect,
}
impl Packet {
#[inline]
/// MQTT Control Packet type
pub fn packet_type(&self) -> u8 {
match *self {
Packet::Connect { .. } => CONNECT,
Packet::ConnectAck { .. } => CONNACK,
Packet::Publish { .. } => PUBLISH,
Packet::PublishAck { .. } => PUBACK,
Packet::PublishReceived { .. } => PUBREC,
Packet::PublishRelease { .. } => PUBREL,
Packet::PublishComplete { .. } => PUBCOMP,
Packet::Subscribe { .. } => SUBSCRIBE,
Packet::SubscribeAck { .. } => SUBACK,
Packet::Unsubscribe { .. } => UNSUBSCRIBE,
Packet::UnsubscribeAck { .. } => UNSUBACK,
Packet::PingRequest => PINGREQ,
Packet::PingResponse => PINGRESP,
Packet::Disconnect => DISCONNECT,
}
}
/// Flags specific to each MQTT Control Packet type
pub fn packet_flags(&self) -> u8 {
match *self {
Packet::Publish(Publish {
dup, qos, retain, ..
}) => {
let mut b = qos as u8;
b <<= 1;
if dup {
b |= 0b1000;
}
if retain {
b |= 0b0001;
}
b
}
Packet::PublishRelease { .. }
| Packet::Subscribe { .. }
| Packet::Unsubscribe { .. } => 0b0010,
_ => 0,
}
}
}
impl From<Connect> for Packet {
fn from(val: Connect) -> Packet {
Packet::Connect(val)
}
}
impl From<Publish> for Packet {
fn from(val: Publish) -> Packet {
Packet::Publish(val)
}
}
pub const CONNECT: u8 = 1;
pub const CONNACK: u8 = 2;
pub const PUBLISH: u8 = 3;
pub const PUBACK: u8 = 4;
pub const PUBREC: u8 = 5;
pub const PUBREL: u8 = 6;
pub const PUBCOMP: u8 = 7;
pub const SUBSCRIBE: u8 = 8;
pub const SUBACK: u8 = 9;
pub const UNSUBSCRIBE: u8 = 10;
pub const UNSUBACK: u8 = 11;
pub const PINGREQ: u8 = 12;
pub const PINGRESP: u8 = 13;
pub const DISCONNECT: u8 = 14;

64
actix-mqtt/codec/src/proto.rs Executable file
View File

@@ -0,0 +1,64 @@
#[macro_export]
macro_rules! const_enum {
($name:ty : $repr:ty) => {
impl ::std::convert::From<$repr> for $name {
fn from(u: $repr) -> Self {
unsafe { ::std::mem::transmute(u) }
}
}
};
}
pub const DEFAULT_MQTT_LEVEL: u8 = 4;
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Protocol {
MQTT(u8),
}
impl Protocol {
pub fn name(self) -> &'static str {
match self {
Protocol::MQTT(_) => "MQTT",
}
}
pub fn level(self) -> u8 {
match self {
Protocol::MQTT(level) => level,
}
}
}
impl Default for Protocol {
fn default() -> Self {
Protocol::MQTT(DEFAULT_MQTT_LEVEL)
}
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// Quality of Service levels
pub enum QoS {
/// At most once delivery
///
/// The message is delivered according to the capabilities of the underlying network.
/// No response is sent by the receiver and no retry is performed by the sender.
/// The message arrives at the receiver either once or not at all.
AtMostOnce = 0,
/// At least once delivery
///
/// This quality of service ensures that the message arrives at the receiver at least once.
/// A QoS 1 PUBLISH Packet has a Packet Identifier in its variable header
/// and is acknowledged by a PUBACK Packet.
AtLeastOnce = 1,
/// Exactly once delivery
///
/// This is the highest quality of service,
/// for use when neither loss nor duplication of messages are acceptable.
/// There is an increased overhead associated with this quality of service.
ExactlyOnce = 2,
}
const_enum!(QoS: u8);

520
actix-mqtt/codec/src/topic.rs Executable file
View File

@@ -0,0 +1,520 @@
use std::fmt::{self, Write};
use std::{io, ops, str::FromStr};
use crate::error::TopicError;
#[inline]
fn is_metadata<T: AsRef<str>>(s: T) -> bool {
s.as_ref().chars().nth(0) == Some('$')
}
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub enum Level {
Normal(String),
Metadata(String), // $SYS
Blank,
SingleWildcard, // Single level wildcard +
MultiWildcard, // Multi-level wildcard #
}
impl Level {
pub fn parse<T: AsRef<str>>(s: T) -> Result<Level, TopicError> {
Level::from_str(s.as_ref())
}
pub fn normal<T: AsRef<str>>(s: T) -> Level {
if s.as_ref().contains(|c| c == '+' || c == '#') {
panic!("invalid normal level `{}` contains +|#", s.as_ref());
}
if s.as_ref().chars().nth(0) == Some('$') {
panic!("invalid normal level `{}` starts with $", s.as_ref())
}
Level::Normal(String::from(s.as_ref()))
}
pub fn metadata<T: AsRef<str>>(s: T) -> Level {
if s.as_ref().contains(|c| c == '+' || c == '#') {
panic!("invalid metadata level `{}` contains +|#", s.as_ref());
}
if s.as_ref().chars().nth(0) != Some('$') {
panic!("invalid metadata level `{}` not starts with $", s.as_ref())
}
Level::Metadata(String::from(s.as_ref()))
}
#[inline]
pub fn value(&self) -> Option<&str> {
match *self {
Level::Normal(ref s) | Level::Metadata(ref s) => Some(s),
_ => None,
}
}
#[inline]
pub fn is_normal(&self) -> bool {
if let Level::Normal(_) = *self {
true
} else {
false
}
}
#[inline]
pub fn is_metadata(&self) -> bool {
if let Level::Metadata(_) = *self {
true
} else {
false
}
}
#[inline]
pub fn is_valid(&self) -> bool {
match *self {
Level::Normal(ref s) => {
s.chars().nth(0) != Some('$') && !s.contains(|c| c == '+' || c == '#')
}
Level::Metadata(ref s) => {
s.chars().nth(0) == Some('$') && !s.contains(|c| c == '+' || c == '#')
}
_ => true,
}
}
}
#[derive(Debug, Eq, Clone)]
pub struct Topic(Vec<Level>);
impl Topic {
#[inline]
pub fn levels(&self) -> &Vec<Level> {
&self.0
}
#[inline]
pub fn is_valid(&self) -> bool {
self.0
.iter()
.position(|level| !level.is_valid())
.or_else(|| {
self.0
.iter()
.enumerate()
.position(|(pos, level)| match *level {
Level::MultiWildcard => pos != self.0.len() - 1,
Level::Metadata(_) => pos != 0,
_ => false,
})
})
.is_none()
}
}
macro_rules! match_topic {
($topic:expr, $levels:expr) => {{
let mut lhs = $topic.0.iter();
for rhs in $levels {
match lhs.next() {
Some(&Level::SingleWildcard) => {
if !rhs.match_level(&Level::SingleWildcard) {
break;
}
}
Some(&Level::MultiWildcard) => {
return rhs.match_level(&Level::MultiWildcard);
}
Some(level) if rhs.match_level(level) => continue,
_ => return false,
}
}
match lhs.next() {
Some(&Level::MultiWildcard) => true,
Some(_) => false,
None => true,
}
}};
}
impl PartialEq for Topic {
fn eq(&self, other: &Topic) -> bool {
match_topic!(self, &other.0)
}
}
impl<T: AsRef<str>> PartialEq<T> for Topic {
fn eq(&self, other: &T) -> bool {
match_topic!(self, other.as_ref().split('/'))
}
}
impl<'a> From<&'a [Level]> for Topic {
fn from(s: &[Level]) -> Self {
let mut v = vec![];
v.extend_from_slice(s);
Topic(v)
}
}
impl From<Vec<Level>> for Topic {
fn from(v: Vec<Level>) -> Self {
Topic(v)
}
}
impl Into<Vec<Level>> for Topic {
fn into(self) -> Vec<Level> {
self.0
}
}
impl ops::Deref for Topic {
type Target = Vec<Level>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl ops::DerefMut for Topic {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[macro_export]
macro_rules! topic {
($s:expr) => {
$s.parse::<Topic>().unwrap()
};
}
pub trait MatchLevel {
fn match_level(&self, level: &Level) -> bool;
}
impl MatchLevel for Level {
fn match_level(&self, level: &Level) -> bool {
match *level {
Level::Normal(ref lhs) => {
if let Level::Normal(ref rhs) = *self {
lhs == rhs
} else {
false
}
}
Level::Metadata(ref lhs) => {
if let Level::Metadata(ref rhs) = *self {
lhs == rhs
} else {
false
}
}
Level::Blank => true,
Level::SingleWildcard | Level::MultiWildcard => !self.is_metadata(),
}
}
}
impl<T: AsRef<str>> MatchLevel for T {
fn match_level(&self, level: &Level) -> bool {
match *level {
Level::Normal(ref lhs) => !is_metadata(self) && lhs == self.as_ref(),
Level::Metadata(ref lhs) => is_metadata(self) && lhs == self.as_ref(),
Level::Blank => self.as_ref().is_empty(),
Level::SingleWildcard | Level::MultiWildcard => !is_metadata(self),
}
}
}
impl FromStr for Level {
type Err = TopicError;
#[inline]
fn from_str(s: &str) -> Result<Self, TopicError> {
match s {
"+" => Ok(Level::SingleWildcard),
"#" => Ok(Level::MultiWildcard),
"" => Ok(Level::Blank),
_ => {
if s.contains(|c| c == '+' || c == '#') {
Err(TopicError::InvalidLevel)
} else if is_metadata(s) {
Ok(Level::Metadata(String::from(s)))
} else {
Ok(Level::Normal(String::from(s)))
}
}
}
}
}
impl FromStr for Topic {
type Err = TopicError;
#[inline]
fn from_str(s: &str) -> Result<Self, TopicError> {
s.split('/')
.map(|level| Level::from_str(level))
.collect::<Result<Vec<_>, TopicError>>()
.map(Topic)
.and_then(|topic| {
if topic.is_valid() {
Ok(topic)
} else {
Err(TopicError::InvalidTopic)
}
})
}
}
impl fmt::Display for Level {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Level::Normal(ref s) | Level::Metadata(ref s) => f.write_str(s.as_str()),
Level::Blank => Ok(()),
Level::SingleWildcard => f.write_char('+'),
Level::MultiWildcard => f.write_char('#'),
}
}
}
impl fmt::Display for Topic {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut first = true;
for level in &self.0 {
if first {
first = false;
} else {
f.write_char('/')?;
}
level.fmt(f)?;
}
Ok(())
}
}
pub trait WriteTopicExt: io::Write {
fn write_level(&mut self, level: &Level) -> io::Result<usize> {
match *level {
Level::Normal(ref s) | Level::Metadata(ref s) => self.write(s.as_str().as_bytes()),
Level::Blank => Ok(0),
Level::SingleWildcard => self.write(b"+"),
Level::MultiWildcard => self.write(b"#"),
}
}
fn write_topic(&mut self, topic: &Topic) -> io::Result<usize> {
let mut n = 0;
let mut first = true;
for level in topic.levels() {
if first {
first = false;
} else {
n += self.write(b"/")?;
}
n += self.write_level(level)?;
}
Ok(n)
}
}
impl<W: io::Write + ?Sized> WriteTopicExt for W {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_level() {
assert!(Level::normal("sport").is_normal());
assert!(Level::metadata("$SYS").is_metadata());
assert_eq!(Level::normal("sport").value(), Some("sport"));
assert_eq!(Level::metadata("$SYS").value(), Some("$SYS"));
assert_eq!(Level::normal("sport"), "sport".parse().unwrap());
assert_eq!(Level::metadata("$SYS"), "$SYS".parse().unwrap());
assert!(Level::Normal(String::from("sport")).is_valid());
assert!(Level::Metadata(String::from("$SYS")).is_valid());
assert!(!Level::Normal(String::from("$sport")).is_valid());
assert!(!Level::Metadata(String::from("SYS")).is_valid());
assert!(!Level::Normal(String::from("sport#")).is_valid());
assert!(!Level::Metadata(String::from("SYS+")).is_valid());
}
#[test]
fn test_valid_topic() {
assert!(Topic(vec![
Level::normal("sport"),
Level::normal("tennis"),
Level::normal("player1")
])
.is_valid());
assert!(Topic(vec![
Level::normal("sport"),
Level::normal("tennis"),
Level::MultiWildcard
])
.is_valid());
assert!(Topic(vec![
Level::metadata("$SYS"),
Level::normal("tennis"),
Level::MultiWildcard
])
.is_valid());
assert!(Topic(vec![
Level::normal("sport"),
Level::SingleWildcard,
Level::normal("player1")
])
.is_valid());
assert!(!Topic(vec![
Level::normal("sport"),
Level::MultiWildcard,
Level::normal("player1")
])
.is_valid());
assert!(!Topic(vec![
Level::normal("sport"),
Level::metadata("$SYS"),
Level::normal("player1")
])
.is_valid());
}
#[test]
fn test_parse_topic() {
assert_eq!(
topic!("sport/tennis/player1"),
Topic::from(vec![
Level::normal("sport"),
Level::normal("tennis"),
Level::normal("player1")
])
);
assert_eq!(topic!(""), Topic(vec![Level::Blank]));
assert_eq!(
topic!("/finance"),
Topic::from(vec![Level::Blank, Level::normal("finance")])
);
assert_eq!(topic!("$SYS"), Topic::from(vec![Level::metadata("$SYS")]));
assert!("sport/$SYS".parse::<Topic>().is_err());
}
#[test]
fn test_multi_wildcard_topic() {
assert_eq!(
topic!("sport/tennis/#"),
Topic::from(vec![
Level::normal("sport"),
Level::normal("tennis"),
Level::MultiWildcard
])
);
assert_eq!(topic!("#"), Topic::from(vec![Level::MultiWildcard]));
assert!("sport/tennis#".parse::<Topic>().is_err());
assert!("sport/tennis/#/ranking".parse::<Topic>().is_err());
}
#[test]
fn test_single_wildcard_topic() {
assert_eq!(topic!("+"), Topic::from(vec![Level::SingleWildcard]));
assert_eq!(
topic!("+/tennis/#"),
Topic::from(vec![
Level::SingleWildcard,
Level::normal("tennis"),
Level::MultiWildcard
])
);
assert_eq!(
topic!("sport/+/player1"),
Topic::from(vec![
Level::normal("sport"),
Level::SingleWildcard,
Level::normal("player1")
])
);
assert!("sport+".parse::<Topic>().is_err());
}
#[test]
fn test_write_topic() {
let mut v = vec![];
let t = vec![
Level::SingleWildcard,
Level::normal("tennis"),
Level::MultiWildcard,
]
.into();
assert_eq!(v.write_topic(&t).unwrap(), 10);
assert_eq!(v, b"+/tennis/#");
assert_eq!(format!("{}", t), "+/tennis/#");
assert_eq!(t.to_string(), "+/tennis/#");
}
#[test]
fn test_match_topic() {
assert!("test".match_level(&Level::normal("test")));
assert!("$SYS".match_level(&Level::metadata("$SYS")));
let t: Topic = "sport/tennis/player1/#".parse().unwrap();
assert_eq!(t, "sport/tennis/player1");
assert_eq!(t, "sport/tennis/player1/ranking");
assert_eq!(t, "sport/tennis/player1/score/wimbledon");
assert_eq!(Topic::from_str("sport/#").unwrap(), "sport");
let t: Topic = "sport/tennis/+".parse().unwrap();
assert_eq!(t, "sport/tennis/player1");
assert_eq!(t, "sport/tennis/player2");
assert!(t != "sport/tennis/player1/ranking");
let t: Topic = "sport/+".parse().unwrap();
assert!(t != "sport");
assert_eq!(t, "sport/");
assert_eq!(Topic::from_str("+/+").unwrap(), "/finance");
assert_eq!(Topic::from_str("/+").unwrap(), "/finance",);
assert!(Topic::from_str("+").unwrap() != "/finance",);
assert!(Topic::from_str("#").unwrap() != "$SYS");
assert!(Topic::from_str("+/monitor/Clients").unwrap() != "$SYS/monitor/Clients");
assert_eq!(Topic::from_str(&"$SYS/#").unwrap(), "$SYS/");
assert_eq!(
Topic::from_str("$SYS/monitor/+").unwrap(),
"$SYS/monitor/Clients",
);
}
}

35
actix-mqtt/examples/basic.rs Executable file
View File

@@ -0,0 +1,35 @@
use actix_mqtt::{Connect, ConnectAck, MqttServer, Publish};
#[derive(Clone)]
struct Session;
async fn connect<Io>(connect: Connect<Io>) -> Result<ConnectAck<Io, Session>, ()> {
log::info!("new connection: {:?}", connect);
Ok(connect.ack(Session, false))
}
async fn publish(publish: Publish<Session>) -> Result<(), ()> {
log::info!(
"incoming publish: {:?} -> {:?}",
publish.id(),
publish.topic()
);
Ok(())
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
std::env::set_var(
"RUST_LOG",
"actix_server=trace,actix_mqtt=trace,basic=trace",
);
env_logger::init();
actix_server::Server::build()
.bind("mqtt", "127.0.0.1:1883", || {
MqttServer::new(connect).finish(publish)
})?
.workers(1)
.run()
.await
}

64
actix-mqtt/src/cell.rs Executable file
View File

@@ -0,0 +1,64 @@
//! Custom cell impl
use std::cell::UnsafeCell;
use std::ops::Deref;
use std::rc::Rc;
use std::task::{Context, Poll};
use actix_service::Service;
pub(crate) struct Cell<T> {
inner: Rc<UnsafeCell<T>>,
}
impl<T> Clone for Cell<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl<T> Deref for Cell<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.get_ref()
}
}
impl<T: std::fmt::Debug> std::fmt::Debug for Cell<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.inner.fmt(f)
}
}
impl<T> Cell<T> {
pub fn new(inner: T) -> Self {
Self {
inner: Rc::new(UnsafeCell::new(inner)),
}
}
pub fn get_ref(&self) -> &T {
unsafe { &*self.inner.as_ref().get() }
}
pub fn get_mut(&mut self) -> &mut T {
unsafe { &mut *self.inner.as_ref().get() }
}
}
impl<T: Service> Service for Cell<T> {
type Request = T::Request;
type Response = T::Response;
type Error = T::Error;
type Future = T::Future;
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), T::Error>> {
self.get_mut().poll_ready(cx)
}
fn call(&mut self, req: T::Request) -> T::Future {
self.get_mut().call(req)
}
}

406
actix-mqtt/src/client.rs Executable file
View File

@@ -0,0 +1,406 @@
use std::marker::PhantomData;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use std::time::Duration;
use actix_codec::{AsyncRead, AsyncWrite};
use actix_ioframe as ioframe;
use actix_service::{boxed, IntoService, IntoServiceFactory, Service, ServiceFactory};
use bytes::Bytes;
use bytestring::ByteString;
use futures::future::{FutureExt, LocalBoxFuture};
use futures::{Sink, SinkExt, Stream, StreamExt};
use mqtt_codec as mqtt;
use crate::cell::Cell;
use crate::default::{SubsNotImplemented, UnsubsNotImplemented};
use crate::dispatcher::{dispatcher, MqttState};
use crate::error::MqttError;
use crate::publish::Publish;
use crate::sink::MqttSink;
use crate::subs::{Subscribe, SubscribeResult, Unsubscribe};
/// Mqtt client
#[derive(Clone)]
pub struct Client<Io, St> {
client_id: ByteString,
clean_session: bool,
protocol: mqtt::Protocol,
keep_alive: u16,
last_will: Option<mqtt::LastWill>,
username: Option<ByteString>,
password: Option<Bytes>,
inflight: usize,
_t: PhantomData<(Io, St)>,
}
impl<Io, St> Client<Io, St>
where
St: 'static,
{
/// Create new client and provide client id
pub fn new(client_id: ByteString) -> Self {
Client {
client_id,
clean_session: true,
protocol: mqtt::Protocol::default(),
keep_alive: 30,
last_will: None,
username: None,
password: None,
inflight: 15,
_t: PhantomData,
}
}
/// Mqtt protocol version
pub fn protocol(mut self, val: mqtt::Protocol) -> Self {
self.protocol = val;
self
}
/// The handling of the Session state.
pub fn clean_session(mut self, val: bool) -> Self {
self.clean_session = val;
self
}
/// A time interval measured in seconds.
///
/// keep-alive is set to 30 seconds by default.
pub fn keep_alive(mut self, val: u16) -> Self {
self.keep_alive = val;
self
}
/// Will Message be stored on the Server and associated with the Network Connection.
///
/// by default last will value is not set
pub fn last_will(mut self, val: mqtt::LastWill) -> Self {
self.last_will = Some(val);
self
}
/// Username can be used by the Server for authentication and authorization.
pub fn username(mut self, val: ByteString) -> Self {
self.username = Some(val);
self
}
/// Password can be used by the Server for authentication and authorization.
pub fn password(mut self, val: Bytes) -> Self {
self.password = Some(val);
self
}
/// Number of in-flight concurrent messages.
///
/// in-flight is set to 15 messages
pub fn inflight(mut self, val: usize) -> Self {
self.inflight = val;
self
}
/// Set state service
///
/// State service verifies connect ack packet and construct connection state.
pub fn state<C, F>(self, state: F) -> ServiceBuilder<Io, St, C>
where
F: IntoService<C>,
Io: AsyncRead + AsyncWrite,
C: Service<Request = ConnectAck<Io>, Response = ConnectAckResult<Io, St>>,
C::Error: 'static,
{
ServiceBuilder {
state: Cell::new(state.into_service()),
packet: mqtt::Connect {
client_id: self.client_id,
clean_session: self.clean_session,
protocol: self.protocol,
keep_alive: self.keep_alive,
last_will: self.last_will,
username: self.username,
password: self.password,
},
subscribe: Rc::new(boxed::factory(SubsNotImplemented::default())),
unsubscribe: Rc::new(boxed::factory(UnsubsNotImplemented::default())),
disconnect: None,
keep_alive: self.keep_alive.into(),
inflight: self.inflight,
_t: PhantomData,
}
}
}
pub struct ServiceBuilder<Io, St, C: Service> {
state: Cell<C>,
packet: mqtt::Connect,
subscribe: Rc<
boxed::BoxServiceFactory<
St,
Subscribe<St>,
SubscribeResult,
MqttError<C::Error>,
MqttError<C::Error>,
>,
>,
unsubscribe: Rc<
boxed::BoxServiceFactory<
St,
Unsubscribe<St>,
(),
MqttError<C::Error>,
MqttError<C::Error>,
>,
>,
disconnect: Option<Cell<boxed::BoxService<St, (), MqttError<C::Error>>>>,
keep_alive: u64,
inflight: usize,
_t: PhantomData<(Io, St, C)>,
}
impl<Io, St, C> ServiceBuilder<Io, St, C>
where
St: Clone + 'static,
Io: AsyncRead + AsyncWrite + 'static,
C: Service<Request = ConnectAck<Io>, Response = ConnectAckResult<Io, St>> + 'static,
C::Error: 'static,
{
/// Service to execute on disconnect
pub fn disconnect<UF, U>(mut self, srv: UF) -> Self
where
UF: IntoService<U>,
U: Service<Request = St, Response = (), Error = C::Error> + 'static,
{
self.disconnect = Some(Cell::new(boxed::service(
srv.into_service().map_err(MqttError::Service),
)));
self
}
pub fn finish<F, T>(
self,
service: F,
) -> impl Service<Request = Io, Response = (), Error = MqttError<C::Error>>
where
F: IntoServiceFactory<T>,
T: ServiceFactory<
Config = St,
Request = Publish<St>,
Response = (),
Error = C::Error,
InitError = C::Error,
> + 'static,
{
ioframe::Builder::new()
.service(ConnectService {
connect: self.state,
packet: self.packet,
keep_alive: self.keep_alive,
inflight: self.inflight,
_t: PhantomData,
})
.finish(dispatcher(
service
.into_factory()
.map_err(MqttError::Service)
.map_init_err(MqttError::Service),
self.subscribe,
self.unsubscribe,
))
.map_err(|e| match e {
ioframe::ServiceError::Service(e) => e,
ioframe::ServiceError::Encoder(e) => MqttError::Protocol(e),
ioframe::ServiceError::Decoder(e) => MqttError::Protocol(e),
})
}
}
struct ConnectService<Io, St, C> {
connect: Cell<C>,
packet: mqtt::Connect,
keep_alive: u64,
inflight: usize,
_t: PhantomData<(Io, St)>,
}
impl<Io, St, C> Service for ConnectService<Io, St, C>
where
St: 'static,
Io: AsyncRead + AsyncWrite + 'static,
C: Service<Request = ConnectAck<Io>, Response = ConnectAckResult<Io, St>> + 'static,
C::Error: 'static,
{
type Request = ioframe::Connect<Io, mqtt::Codec>;
type Response = ioframe::ConnectResult<Io, MqttState<St>, mqtt::Codec>;
type Error = MqttError<C::Error>;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
self.connect
.get_mut()
.poll_ready(cx)
.map_err(MqttError::Service)
}
fn call(&mut self, req: Self::Request) -> Self::Future {
let mut srv = self.connect.clone();
let packet = self.packet.clone();
let keep_alive = Duration::from_secs(self.keep_alive as u64);
let inflight = self.inflight;
// send Connect packet
async move {
let mut framed = req.codec(mqtt::Codec::new());
framed
.send(mqtt::Packet::Connect(packet))
.await
.map_err(MqttError::Protocol)?;
let packet = framed
.next()
.await
.ok_or(MqttError::Disconnected)
.and_then(|res| res.map_err(MqttError::Protocol))?;
match packet {
mqtt::Packet::ConnectAck {
session_present,
return_code,
} => {
let sink = MqttSink::new(framed.sink().clone());
let ack = ConnectAck {
sink,
session_present,
return_code,
keep_alive,
inflight,
io: framed,
};
Ok(srv
.get_mut()
.call(ack)
.await
.map_err(MqttError::Service)
.map(|ack| ack.io.state(ack.state))?)
}
p => Err(MqttError::Unexpected(p, "Expected CONNECT-ACK packet")),
}
}
.boxed_local()
}
}
pub struct ConnectAck<Io> {
io: ioframe::ConnectResult<Io, (), mqtt::Codec>,
sink: MqttSink,
session_present: bool,
return_code: mqtt::ConnectCode,
keep_alive: Duration,
inflight: usize,
}
impl<Io> ConnectAck<Io> {
#[inline]
/// Indicates whether there is already stored Session state
pub fn session_present(&self) -> bool {
self.session_present
}
#[inline]
/// Connect return code
pub fn return_code(&self) -> mqtt::ConnectCode {
self.return_code
}
#[inline]
/// Mqtt client sink object
pub fn sink(&self) -> &MqttSink {
&self.sink
}
#[inline]
/// Set connection state and create result object
pub fn state<St>(self, state: St) -> ConnectAckResult<Io, St> {
ConnectAckResult {
io: self.io,
state: MqttState::new(state, self.sink, self.keep_alive, self.inflight),
}
}
}
impl<Io> Stream for ConnectAck<Io>
where
Io: AsyncRead + AsyncWrite + Unpin,
{
type Item = Result<mqtt::Packet, mqtt::ParseError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
Pin::new(&mut self.io).poll_next(cx)
}
}
impl<Io> Sink<mqtt::Packet> for ConnectAck<Io>
where
Io: AsyncRead + AsyncWrite + Unpin,
{
type Error = mqtt::ParseError;
fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
Pin::new(&mut self.io).poll_ready(cx)
}
fn start_send(mut self: Pin<&mut Self>, item: mqtt::Packet) -> Result<(), Self::Error> {
Pin::new(&mut self.io).start_send(item)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
Pin::new(&mut self.io).poll_flush(cx)
}
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
Pin::new(&mut self.io).poll_close(cx)
}
}
#[pin_project::pin_project]
pub struct ConnectAckResult<Io, St> {
state: MqttState<St>,
io: ioframe::ConnectResult<Io, (), mqtt::Codec>,
}
impl<Io, St> Stream for ConnectAckResult<Io, St>
where
Io: AsyncRead + AsyncWrite + Unpin,
{
type Item = Result<mqtt::Packet, mqtt::ParseError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
Pin::new(&mut self.io).poll_next(cx)
}
}
impl<Io, St> Sink<mqtt::Packet> for ConnectAckResult<Io, St>
where
Io: AsyncRead + AsyncWrite + Unpin,
{
type Error = mqtt::ParseError;
fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
Pin::new(&mut self.io).poll_ready(cx)
}
fn start_send(mut self: Pin<&mut Self>, item: mqtt::Packet) -> Result<(), Self::Error> {
Pin::new(&mut self.io).start_send(item)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
Pin::new(&mut self.io).poll_flush(cx)
}
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
Pin::new(&mut self.io).poll_close(cx)
}
}

150
actix-mqtt/src/connect.rs Executable file
View File

@@ -0,0 +1,150 @@
use std::fmt;
use std::ops::Deref;
use std::time::Duration;
use actix_ioframe as ioframe;
use mqtt_codec as mqtt;
use crate::sink::MqttSink;
/// Connect message
pub struct Connect<Io> {
connect: mqtt::Connect,
sink: MqttSink,
keep_alive: Duration,
inflight: usize,
io: ioframe::ConnectResult<Io, (), mqtt::Codec>,
}
impl<Io> Connect<Io> {
pub(crate) fn new(
connect: mqtt::Connect,
io: ioframe::ConnectResult<Io, (), mqtt::Codec>,
sink: MqttSink,
inflight: usize,
) -> Self {
Self {
keep_alive: Duration::from_secs(connect.keep_alive as u64),
connect,
io,
sink,
inflight,
}
}
/// Returns reference to io object
pub fn get_ref(&self) -> &Io {
self.io.get_ref()
}
/// Returns mutable reference to io object
pub fn get_mut(&mut self) -> &mut Io {
self.io.get_mut()
}
/// Returns mqtt server sink
pub fn sink(&self) -> &MqttSink {
&self.sink
}
/// Ack connect message and set state
pub fn ack<St>(self, st: St, session_present: bool) -> ConnectAck<Io, St> {
ConnectAck::new(self.io, st, session_present, self.keep_alive, self.inflight)
}
/// Create connect ack object with `identifier rejected` return code
pub fn identifier_rejected<St>(self) -> ConnectAck<Io, St> {
ConnectAck {
io: self.io,
session: None,
session_present: false,
return_code: mqtt::ConnectCode::IdentifierRejected,
keep_alive: Duration::from_secs(5),
inflight: 15,
}
}
/// Create connect ack object with `bad user name or password` return code
pub fn bad_username_or_pwd<St>(self) -> ConnectAck<Io, St> {
ConnectAck {
io: self.io,
session: None,
session_present: false,
return_code: mqtt::ConnectCode::BadUserNameOrPassword,
keep_alive: Duration::from_secs(5),
inflight: 15,
}
}
/// Create connect ack object with `not authorized` return code
pub fn not_authorized<St>(self) -> ConnectAck<Io, St> {
ConnectAck {
io: self.io,
session: None,
session_present: false,
return_code: mqtt::ConnectCode::NotAuthorized,
keep_alive: Duration::from_secs(5),
inflight: 15,
}
}
}
impl<Io> Deref for Connect<Io> {
type Target = mqtt::Connect;
fn deref(&self) -> &Self::Target {
&self.connect
}
}
impl<T> fmt::Debug for Connect<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.connect.fmt(f)
}
}
/// Ack connect message
pub struct ConnectAck<Io, St> {
pub(crate) io: ioframe::ConnectResult<Io, (), mqtt::Codec>,
pub(crate) session: Option<St>,
pub(crate) session_present: bool,
pub(crate) return_code: mqtt::ConnectCode,
pub(crate) keep_alive: Duration,
pub(crate) inflight: usize,
}
impl<Io, St> ConnectAck<Io, St> {
/// Create connect ack, `session_present` indicates that previous session is presents
pub(crate) fn new(
io: ioframe::ConnectResult<Io, (), mqtt::Codec>,
session: St,
session_present: bool,
keep_alive: Duration,
inflight: usize,
) -> Self {
Self {
io,
session_present,
keep_alive,
inflight,
session: Some(session),
return_code: mqtt::ConnectCode::ConnectionAccepted,
}
}
/// Set idle time-out for the connection in milliseconds
///
/// By default idle time-out is set to 300000 milliseconds
pub fn idle_timeout(mut self, timeout: Duration) -> Self {
self.keep_alive = timeout;
self
}
/// Set in-flight count. Total number of `in-flight` packets
///
/// By default in-flight count is set to 15
pub fn in_flight(mut self, in_flight: usize) -> Self {
self.inflight = in_flight;
self
}
}

125
actix-mqtt/src/default.rs Executable file
View File

@@ -0,0 +1,125 @@
use std::marker::PhantomData;
use std::task::{Context, Poll};
use actix_service::{Service, ServiceFactory};
use futures::future::{ok, Ready};
use crate::publish::Publish;
use crate::subs::{Subscribe, SubscribeResult, Unsubscribe};
/// Not implemented publish service
pub struct NotImplemented<S, E>(PhantomData<(S, E)>);
impl<S, E> Default for NotImplemented<S, E> {
fn default() -> Self {
NotImplemented(PhantomData)
}
}
impl<S, E> ServiceFactory for NotImplemented<S, E> {
type Config = S;
type Request = Publish<S>;
type Response = ();
type Error = E;
type InitError = E;
type Service = NotImplemented<S, E>;
type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: S) -> Self::Future {
ok(NotImplemented(PhantomData))
}
}
impl<S, E> Service for NotImplemented<S, E> {
type Request = Publish<S>;
type Response = ();
type Error = E;
type Future = Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, _: Publish<S>) -> Self::Future {
log::warn!("MQTT Publish is not implemented");
ok(())
}
}
/// Not implemented subscribe service
pub struct SubsNotImplemented<S, E>(PhantomData<(S, E)>);
impl<S, E> Default for SubsNotImplemented<S, E> {
fn default() -> Self {
SubsNotImplemented(PhantomData)
}
}
impl<S, E> ServiceFactory for SubsNotImplemented<S, E> {
type Config = S;
type Request = Subscribe<S>;
type Response = SubscribeResult;
type Error = E;
type InitError = E;
type Service = SubsNotImplemented<S, E>;
type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: S) -> Self::Future {
ok(SubsNotImplemented(PhantomData))
}
}
impl<S, E> Service for SubsNotImplemented<S, E> {
type Request = Subscribe<S>;
type Response = SubscribeResult;
type Error = E;
type Future = Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, subs: Subscribe<S>) -> Self::Future {
log::warn!("MQTT Subscribe is not implemented");
ok(subs.into_result())
}
}
/// Not implemented unsubscribe service
pub struct UnsubsNotImplemented<S, E>(PhantomData<(S, E)>);
impl<S, E> Default for UnsubsNotImplemented<S, E> {
fn default() -> Self {
UnsubsNotImplemented(PhantomData)
}
}
impl<S, E> ServiceFactory for UnsubsNotImplemented<S, E> {
type Config = S;
type Request = Unsubscribe<S>;
type Response = ();
type Error = E;
type InitError = E;
type Service = UnsubsNotImplemented<S, E>;
type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: S) -> Self::Future {
ok(UnsubsNotImplemented(PhantomData))
}
}
impl<S, E> Service for UnsubsNotImplemented<S, E> {
type Request = Unsubscribe<S>;
type Response = ();
type Error = E;
type Future = Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, _: Unsubscribe<S>) -> Self::Future {
log::warn!("MQTT Unsubscribe is not implemented");
ok(())
}
}

286
actix-mqtt/src/dispatcher.rs Executable file
View File

@@ -0,0 +1,286 @@
use std::future::Future;
use std::marker::PhantomData;
use std::num::NonZeroU16;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use std::time::Duration;
use actix_ioframe as ioframe;
use actix_service::{boxed, fn_factory_with_config, pipeline, Service, ServiceFactory};
use actix_utils::inflight::InFlightService;
use actix_utils::keepalive::KeepAliveService;
use actix_utils::order::{InOrder, InOrderError};
use actix_utils::time::LowResTimeService;
use futures::future::{join3, ok, Either, FutureExt, LocalBoxFuture, Ready};
use futures::ready;
use mqtt_codec as mqtt;
use crate::cell::Cell;
use crate::error::MqttError;
use crate::publish::Publish;
use crate::sink::MqttSink;
use crate::subs::{Subscribe, SubscribeResult, Unsubscribe};
pub(crate) struct MqttState<St> {
inner: Cell<MqttStateInner<St>>,
}
struct MqttStateInner<St> {
pub(crate) st: St,
pub(crate) sink: MqttSink,
pub(self) timeout: Duration,
pub(self) in_flight: usize,
}
impl<St> Clone for MqttState<St> {
fn clone(&self) -> Self {
MqttState {
inner: self.inner.clone(),
}
}
}
impl<St> MqttState<St> {
pub(crate) fn new(st: St, sink: MqttSink, timeout: Duration, in_flight: usize) -> Self {
MqttState {
inner: Cell::new(MqttStateInner {
st,
sink,
timeout,
in_flight,
}),
}
}
pub(crate) fn sink(&self) -> &MqttSink {
&self.inner.sink
}
pub(crate) fn session(&self) -> &St {
&self.inner.get_ref().st
}
pub(crate) fn session_mut(&mut self) -> &mut St {
&mut self.inner.get_mut().st
}
}
// dispatcher factory
pub(crate) fn dispatcher<St, T, E>(
publish: T,
subscribe: Rc<
boxed::BoxServiceFactory<
St,
Subscribe<St>,
SubscribeResult,
MqttError<E>,
MqttError<E>,
>,
>,
unsubscribe: Rc<
boxed::BoxServiceFactory<St, Unsubscribe<St>, (), MqttError<E>, MqttError<E>>,
>,
) -> impl ServiceFactory<
Config = MqttState<St>,
Request = ioframe::Item<MqttState<St>, mqtt::Codec>,
Response = Option<mqtt::Packet>,
Error = MqttError<E>,
InitError = MqttError<E>,
>
where
E: 'static,
St: Clone + 'static,
T: ServiceFactory<
Config = St,
Request = Publish<St>,
Response = (),
Error = MqttError<E>,
InitError = MqttError<E>,
> + 'static,
{
let time = LowResTimeService::with(Duration::from_secs(1));
fn_factory_with_config(move |cfg: MqttState<St>| {
let time = time.clone();
let state = cfg.session().clone();
let timeout = cfg.inner.timeout;
let inflight = cfg.inner.in_flight;
// create services
let fut = join3(
publish.new_service(state.clone()),
subscribe.new_service(state.clone()),
unsubscribe.new_service(state.clone()),
);
async move {
let (publish, subscribe, unsubscribe) = fut.await;
// mqtt dispatcher
Ok(Dispatcher::new(
// keep-alive connection
pipeline(KeepAliveService::new(timeout, time, || {
MqttError::KeepAliveTimeout
}))
.and_then(
// limit number of in-flight messages
InFlightService::new(
inflight,
// mqtt spec requires ack ordering, so enforce response ordering
InOrder::service(publish?).map_err(|e| match e {
InOrderError::Service(e) => e,
InOrderError::Disconnected => MqttError::Disconnected,
}),
),
),
subscribe?,
unsubscribe?,
))
}
})
}
/// PUBLIS/SUBSCRIBER/UNSUBSCRIBER packets dispatcher
pub(crate) struct Dispatcher<St, T: Service> {
publish: T,
subscribe: boxed::BoxService<Subscribe<St>, SubscribeResult, T::Error>,
unsubscribe: boxed::BoxService<Unsubscribe<St>, (), T::Error>,
}
impl<St, T> Dispatcher<St, T>
where
T: Service<Request = Publish<St>, Response = ()>,
{
pub(crate) fn new(
publish: T,
subscribe: boxed::BoxService<Subscribe<St>, SubscribeResult, T::Error>,
unsubscribe: boxed::BoxService<Unsubscribe<St>, (), T::Error>,
) -> Self {
Self {
publish,
subscribe,
unsubscribe,
}
}
}
impl<St, T> Service for Dispatcher<St, T>
where
T: Service<Request = Publish<St>, Response = ()>,
T::Error: 'static,
{
type Request = ioframe::Item<MqttState<St>, mqtt::Codec>;
type Response = Option<mqtt::Packet>;
type Error = T::Error;
type Future = Either<
Either<
Ready<Result<Self::Response, T::Error>>,
LocalBoxFuture<'static, Result<Self::Response, T::Error>>,
>,
PublishResponse<T::Future, T::Error>,
>;
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
let res1 = self.publish.poll_ready(cx)?;
let res2 = self.subscribe.poll_ready(cx)?;
let res3 = self.unsubscribe.poll_ready(cx)?;
if res1.is_pending() || res2.is_pending() || res3.is_pending() {
Poll::Pending
} else {
Poll::Ready(Ok(()))
}
}
fn call(&mut self, req: ioframe::Item<MqttState<St>, mqtt::Codec>) -> Self::Future {
let (mut state, _, packet) = req.into_parts();
log::trace!("Dispatch packet: {:#?}", packet);
match packet {
mqtt::Packet::PingRequest => {
Either::Left(Either::Left(ok(Some(mqtt::Packet::PingResponse))))
}
mqtt::Packet::Disconnect => Either::Left(Either::Left(ok(None))),
mqtt::Packet::Publish(publish) => {
let packet_id = publish.packet_id;
Either::Right(PublishResponse {
packet_id,
fut: self.publish.call(Publish::new(state, publish)),
_t: PhantomData,
})
}
mqtt::Packet::PublishAck { packet_id } => {
state.inner.get_mut().sink.complete_publish_qos1(packet_id);
Either::Left(Either::Left(ok(None)))
}
mqtt::Packet::Subscribe {
packet_id,
topic_filters,
} => Either::Left(Either::Right(
SubscribeResponse {
packet_id,
fut: self.subscribe.call(Subscribe::new(state, topic_filters)),
}
.boxed_local(),
)),
mqtt::Packet::Unsubscribe {
packet_id,
topic_filters,
} => Either::Left(Either::Right(
self.unsubscribe
.call(Unsubscribe::new(state, topic_filters))
.map(move |_| Ok(Some(mqtt::Packet::UnsubscribeAck { packet_id })))
.boxed_local(),
)),
_ => Either::Left(Either::Left(ok(None))),
}
}
}
/// Publish service response future
#[pin_project::pin_project]
pub(crate) struct PublishResponse<T, E> {
#[pin]
fut: T,
packet_id: Option<NonZeroU16>,
_t: PhantomData<E>,
}
impl<T, E> Future for PublishResponse<T, E>
where
T: Future<Output = Result<(), E>>,
{
type Output = Result<Option<mqtt::Packet>, E>;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let this = self.project();
ready!(this.fut.poll(cx))?;
if let Some(packet_id) = this.packet_id {
Poll::Ready(Ok(Some(mqtt::Packet::PublishAck {
packet_id: *packet_id,
})))
} else {
Poll::Ready(Ok(None))
}
}
}
/// Subscribe service response future
pub(crate) struct SubscribeResponse<E> {
fut: LocalBoxFuture<'static, Result<SubscribeResult, E>>,
packet_id: NonZeroU16,
}
impl<E> Future for SubscribeResponse<E> {
type Output = Result<Option<mqtt::Packet>, E>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let res = ready!(Pin::new(&mut self.fut).poll(cx))?;
Poll::Ready(Ok(Some(mqtt::Packet::SubscribeAck {
status: res.codes,
packet_id: self.packet_id,
})))
}
}

34
actix-mqtt/src/error.rs Executable file
View File

@@ -0,0 +1,34 @@
use std::io;
/// Errors which can occur when attempting to handle mqtt connection.
#[derive(Debug)]
pub enum MqttError<E> {
/// Message handler service error
Service(E),
/// Mqtt parse error
Protocol(mqtt_codec::ParseError),
/// Unexpected packet
Unexpected(mqtt_codec::Packet, &'static str),
/// "SUBSCRIBE, UNSUBSCRIBE, and PUBLISH (in cases where QoS > 0) Control Packets MUST contain a non-zero 16-bit Packet Identifier [MQTT-2.3.1-1]."
PacketIdRequired,
/// Keep alive timeout
KeepAliveTimeout,
/// Handshake timeout
HandshakeTimeout,
/// Peer disconnect
Disconnected,
/// Unexpected io error
Io(io::Error),
}
impl<E> From<mqtt_codec::ParseError> for MqttError<E> {
fn from(err: mqtt_codec::ParseError) -> Self {
MqttError::Protocol(err)
}
}
impl<E> From<io::Error> for MqttError<E> {
fn from(err: io::Error) -> Self {
MqttError::Io(err)
}
}

23
actix-mqtt/src/lib.rs Executable file
View File

@@ -0,0 +1,23 @@
#![allow(clippy::type_complexity, clippy::new_ret_no_self)]
//! MQTT v3.1 Server framework
mod cell;
pub mod client;
mod connect;
mod default;
mod dispatcher;
mod error;
mod publish;
mod router;
mod server;
mod sink;
mod subs;
pub use self::client::Client;
pub use self::connect::{Connect, ConnectAck};
pub use self::error::MqttError;
pub use self::publish::Publish;
pub use self::router::Router;
pub use self::server::MqttServer;
pub use self::sink::MqttSink;
pub use self::subs::{Subscribe, SubscribeIter, SubscribeResult, Subscription, Unsubscribe};

137
actix-mqtt/src/publish.rs Executable file
View File

@@ -0,0 +1,137 @@
use std::convert::TryFrom;
use std::num::NonZeroU16;
use actix_router::Path;
use bytes::Bytes;
use bytestring::ByteString;
use mqtt_codec as mqtt;
use serde::de::DeserializeOwned;
use serde_json::Error as JsonError;
use crate::dispatcher::MqttState;
use crate::sink::MqttSink;
/// Publish message
pub struct Publish<S> {
publish: mqtt::Publish,
sink: MqttSink,
state: MqttState<S>,
topic: Path<ByteString>,
query: Option<ByteString>,
}
impl<S> Publish<S> {
pub(crate) fn new(state: MqttState<S>, publish: mqtt::Publish) -> Self {
let (topic, query) = if let Some(pos) = publish.topic.find('?') {
(
ByteString::try_from(publish.topic.get_ref().slice(0..pos)).unwrap(),
Some(
ByteString::try_from(
publish.topic.get_ref().slice(pos + 1..publish.topic.len()),
)
.unwrap(),
),
)
} else {
(publish.topic.clone(), None)
};
let topic = Path::new(topic);
let sink = state.sink().clone();
Self {
sink,
publish,
state,
topic,
query,
}
}
#[inline]
/// this might be re-delivery of an earlier attempt to send the Packet.
pub fn dup(&self) -> bool {
self.publish.dup
}
#[inline]
pub fn retain(&self) -> bool {
self.publish.retain
}
#[inline]
/// the level of assurance for delivery of an Application Message.
pub fn qos(&self) -> mqtt::QoS {
self.publish.qos
}
#[inline]
/// the information channel to which payload data is published.
pub fn publish_topic(&self) -> &str {
&self.publish.topic
}
#[inline]
/// returns reference to a connection session
pub fn session(&self) -> &S {
self.state.session()
}
#[inline]
/// returns mutable reference to a connection session
pub fn session_mut(&mut self) -> &mut S {
self.state.session_mut()
}
#[inline]
/// only present in PUBLISH Packets where the QoS level is 1 or 2.
pub fn id(&self) -> Option<NonZeroU16> {
self.publish.packet_id
}
#[inline]
pub fn topic(&self) -> &Path<ByteString> {
&self.topic
}
#[inline]
pub fn topic_mut(&mut self) -> &mut Path<ByteString> {
&mut self.topic
}
#[inline]
pub fn query(&self) -> &str {
self.query.as_ref().map(|s| s.as_ref()).unwrap_or("")
}
#[inline]
pub fn packet(&self) -> &mqtt::Publish {
&self.publish
}
#[inline]
/// the Application Message that is being published.
pub fn payload(&self) -> &Bytes {
&self.publish.payload
}
/// Extract Bytes from packet payload
pub fn take_payload(&self) -> Bytes {
self.publish.payload.clone()
}
#[inline]
/// Mqtt client sink object
pub fn sink(&self) -> &MqttSink {
&self.sink
}
/// Loads and parse `application/json` encoded body.
pub fn json<T: DeserializeOwned>(&mut self) -> Result<T, JsonError> {
serde_json::from_slice(&self.publish.payload)
}
}
impl<S> std::fmt::Debug for Publish<S> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.publish.fmt(f)
}
}

206
actix-mqtt/src/router.rs Executable file
View File

@@ -0,0 +1,206 @@
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use actix_router::{IntoPattern, RouterBuilder};
use actix_service::boxed::{self, BoxService, BoxServiceFactory};
use actix_service::{fn_service, IntoServiceFactory, Service, ServiceFactory};
use futures::future::{join_all, ok, JoinAll, LocalBoxFuture};
use crate::publish::Publish;
type Handler<S, E> = BoxServiceFactory<S, Publish<S>, (), E, E>;
type HandlerService<S, E> = BoxService<Publish<S>, (), E>;
/// Router - structure that follows the builder pattern
/// for building publish packet router instances for mqtt server.
pub struct Router<S, E> {
router: RouterBuilder<usize>,
handlers: Vec<Handler<S, E>>,
default: Handler<S, E>,
}
impl<S, E> Router<S, E>
where
S: Clone + 'static,
E: 'static,
{
/// Create mqtt application.
///
/// **Note** Default service acks all publish packets
pub fn new() -> Self {
Router {
router: actix_router::Router::build(),
handlers: Vec::new(),
default: boxed::factory(
fn_service(|p: Publish<S>| {
log::warn!("Unknown topic {:?}", p.publish_topic());
ok::<_, E>(())
})
.map_init_err(|_| panic!()),
),
}
}
/// Configure mqtt resource for a specific topic.
pub fn resource<T, F, U: 'static>(mut self, address: T, service: F) -> Self
where
T: IntoPattern,
F: IntoServiceFactory<U>,
U: ServiceFactory<Config = S, Request = Publish<S>, Response = (), Error = E>,
E: From<U::InitError>,
{
self.router.path(address, self.handlers.len());
self.handlers
.push(boxed::factory(service.into_factory().map_init_err(E::from)));
self
}
/// Default service to be used if no matching resource could be found.
pub fn default_resource<F, U: 'static>(mut self, service: F) -> Self
where
F: IntoServiceFactory<U>,
U: ServiceFactory<
Config = S,
Request = Publish<S>,
Response = (),
Error = E,
InitError = E,
>,
{
self.default = boxed::factory(service.into_factory());
self
}
}
impl<S, E> IntoServiceFactory<RouterFactory<S, E>> for Router<S, E>
where
S: Clone + 'static,
E: 'static,
{
fn into_factory(self) -> RouterFactory<S, E> {
RouterFactory {
router: Rc::new(self.router.finish()),
handlers: self.handlers,
default: self.default,
}
}
}
pub struct RouterFactory<S, E> {
router: Rc<actix_router::Router<usize>>,
handlers: Vec<Handler<S, E>>,
default: Handler<S, E>,
}
impl<S, E> ServiceFactory for RouterFactory<S, E>
where
S: Clone + 'static,
E: 'static,
{
type Config = S;
type Request = Publish<S>;
type Response = ();
type Error = E;
type InitError = E;
type Service = RouterService<S, E>;
type Future = RouterFactoryFut<S, E>;
fn new_service(&self, session: S) -> Self::Future {
let fut: Vec<_> = self
.handlers
.iter()
.map(|h| h.new_service(session.clone()))
.collect();
RouterFactoryFut {
router: self.router.clone(),
handlers: join_all(fut),
default: Some(either::Either::Left(self.default.new_service(session))),
}
}
}
pub struct RouterFactoryFut<S, E> {
router: Rc<actix_router::Router<usize>>,
handlers: JoinAll<LocalBoxFuture<'static, Result<HandlerService<S, E>, E>>>,
default: Option<
either::Either<
LocalBoxFuture<'static, Result<HandlerService<S, E>, E>>,
HandlerService<S, E>,
>,
>,
}
impl<S, E> Future for RouterFactoryFut<S, E> {
type Output = Result<RouterService<S, E>, E>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let res = match self.default.as_mut().unwrap() {
either::Either::Left(ref mut fut) => {
let default = match futures::ready!(Pin::new(fut).poll(cx)) {
Ok(default) => default,
Err(e) => return Poll::Ready(Err(e)),
};
self.default = Some(either::Either::Right(default));
return self.poll(cx);
}
either::Either::Right(_) => futures::ready!(Pin::new(&mut self.handlers).poll(cx)),
};
let mut handlers = Vec::new();
for handler in res {
match handler {
Ok(h) => handlers.push(h),
Err(e) => return Poll::Ready(Err(e)),
}
}
Poll::Ready(Ok(RouterService {
handlers,
router: self.router.clone(),
default: self.default.take().unwrap().right().unwrap(),
}))
}
}
pub struct RouterService<S, E> {
router: Rc<actix_router::Router<usize>>,
handlers: Vec<BoxService<Publish<S>, (), E>>,
default: BoxService<Publish<S>, (), E>,
}
impl<S, E> Service for RouterService<S, E>
where
S: 'static,
E: 'static,
{
type Request = Publish<S>;
type Response = ();
type Error = E;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
let mut not_ready = false;
for hnd in &mut self.handlers {
if let Poll::Pending = hnd.poll_ready(cx)? {
not_ready = true;
}
}
if not_ready {
Poll::Pending
} else {
Poll::Ready(Ok(()))
}
}
fn call(&mut self, mut req: Publish<S>) -> Self::Future {
if let Some((idx, _info)) = self.router.recognize(req.topic_mut()) {
self.handlers[*idx].call(req)
} else {
self.default.call(req)
}
}
}

331
actix-mqtt/src/server.rs Executable file
View File

@@ -0,0 +1,331 @@
use std::future::Future;
use std::marker::PhantomData;
use std::rc::Rc;
use std::time::Duration;
use actix_codec::{AsyncRead, AsyncWrite};
use actix_ioframe as ioframe;
use actix_service::{apply, apply_fn, boxed, fn_factory, pipeline_factory, unit_config};
use actix_service::{IntoServiceFactory, Service, ServiceFactory};
use actix_utils::timeout::{Timeout, TimeoutError};
use futures::{FutureExt, SinkExt, StreamExt};
use mqtt_codec as mqtt;
use crate::cell::Cell;
use crate::connect::{Connect, ConnectAck};
use crate::default::{SubsNotImplemented, UnsubsNotImplemented};
use crate::dispatcher::{dispatcher, MqttState};
use crate::error::MqttError;
use crate::publish::Publish;
use crate::sink::MqttSink;
use crate::subs::{Subscribe, SubscribeResult, Unsubscribe};
/// Mqtt Server
pub struct MqttServer<Io, St, C: ServiceFactory, U> {
connect: C,
subscribe: boxed::BoxServiceFactory<
St,
Subscribe<St>,
SubscribeResult,
MqttError<C::Error>,
MqttError<C::Error>,
>,
unsubscribe: boxed::BoxServiceFactory<
St,
Unsubscribe<St>,
(),
MqttError<C::Error>,
MqttError<C::Error>,
>,
disconnect: U,
max_size: usize,
inflight: usize,
handshake_timeout: u64,
_t: PhantomData<(Io, St)>,
}
fn default_disconnect<St>(_: St, _: bool) {}
impl<Io, St, C> MqttServer<Io, St, C, ()>
where
St: 'static,
C: ServiceFactory<Config = (), Request = Connect<Io>, Response = ConnectAck<Io, St>>
+ 'static,
{
/// Create server factory and provide connect service
pub fn new<F>(connect: F) -> MqttServer<Io, St, C, impl Fn(St, bool)>
where
F: IntoServiceFactory<C>,
{
MqttServer {
connect: connect.into_factory(),
subscribe: boxed::factory(
pipeline_factory(SubsNotImplemented::default())
.map_err(MqttError::Service)
.map_init_err(MqttError::Service),
),
unsubscribe: boxed::factory(
pipeline_factory(UnsubsNotImplemented::default())
.map_err(MqttError::Service)
.map_init_err(MqttError::Service),
),
max_size: 0,
inflight: 15,
disconnect: default_disconnect,
handshake_timeout: 0,
_t: PhantomData,
}
}
}
impl<Io, St, C, U> MqttServer<Io, St, C, U>
where
St: Clone + 'static,
U: Fn(St, bool) + 'static,
C: ServiceFactory<Config = (), Request = Connect<Io>, Response = ConnectAck<Io, St>>
+ 'static,
{
/// Set handshake timeout in millis.
///
/// Handshake includes `connect` packet and response `connect-ack`.
/// By default handshake timeuot is disabled.
pub fn handshake_timeout(mut self, timeout: u64) -> Self {
self.handshake_timeout = timeout;
self
}
/// Set max inbound frame size.
///
/// If max size is set to `0`, size is unlimited.
/// By default max size is set to `0`
pub fn max_size(mut self, size: usize) -> Self {
self.max_size = size;
self
}
/// Number of in-flight concurrent messages.
///
/// in-flight is set to 15 messages
pub fn inflight(mut self, val: usize) -> Self {
self.inflight = val;
self
}
/// Service to execute for subscribe packet
pub fn subscribe<F, Srv>(mut self, subscribe: F) -> Self
where
F: IntoServiceFactory<Srv>,
Srv: ServiceFactory<Config = St, Request = Subscribe<St>, Response = SubscribeResult>
+ 'static,
C::Error: From<Srv::Error> + From<Srv::InitError>,
{
self.subscribe = boxed::factory(
subscribe
.into_factory()
.map_err(|e| MqttError::Service(e.into()))
.map_init_err(|e| MqttError::Service(e.into())),
);
self
}
/// Service to execute for unsubscribe packet
pub fn unsubscribe<F, Srv>(mut self, unsubscribe: F) -> Self
where
F: IntoServiceFactory<Srv>,
Srv: ServiceFactory<Config = St, Request = Unsubscribe<St>, Response = ()> + 'static,
C::Error: From<Srv::Error> + From<Srv::InitError>,
{
self.unsubscribe = boxed::factory(
unsubscribe
.into_factory()
.map_err(|e| MqttError::Service(e.into()))
.map_init_err(|e| MqttError::Service(e.into())),
);
self
}
/// Callback to execute on disconnect
///
/// Second parameter indicates error occured during disconnect.
pub fn disconnect<F, Out>(self, disconnect: F) -> MqttServer<Io, St, C, impl Fn(St, bool)>
where
F: Fn(St, bool) -> Out,
Out: Future + 'static,
{
MqttServer {
connect: self.connect,
subscribe: self.subscribe,
unsubscribe: self.unsubscribe,
max_size: self.max_size,
inflight: self.inflight,
handshake_timeout: self.handshake_timeout,
disconnect: move |st: St, err| {
let fut = disconnect(st, err);
actix_rt::spawn(fut.map(|_| ()));
},
_t: PhantomData,
}
}
/// Set service to execute for publish packet and create service factory
pub fn finish<F, P>(
self,
publish: F,
) -> impl ServiceFactory<Config = (), Request = Io, Response = (), Error = MqttError<C::Error>>
where
Io: AsyncRead + AsyncWrite + 'static,
F: IntoServiceFactory<P>,
P: ServiceFactory<Config = St, Request = Publish<St>, Response = ()> + 'static,
C::Error: From<P::Error> + From<P::InitError>,
{
let connect = self.connect;
let max_size = self.max_size;
let handshake_timeout = self.handshake_timeout;
let disconnect = self.disconnect;
let publish = boxed::factory(
publish
.into_factory()
.map_err(|e| MqttError::Service(e.into()))
.map_init_err(|e| MqttError::Service(e.into())),
);
unit_config(
ioframe::Builder::new()
.factory(connect_service_factory(
connect,
max_size,
self.inflight,
handshake_timeout,
))
.disconnect(move |cfg, err| disconnect(cfg.session().clone(), err))
.finish(dispatcher(
publish,
Rc::new(self.subscribe),
Rc::new(self.unsubscribe),
))
.map_err(|e| match e {
ioframe::ServiceError::Service(e) => e,
ioframe::ServiceError::Encoder(e) => MqttError::Protocol(e),
ioframe::ServiceError::Decoder(e) => MqttError::Protocol(e),
}),
)
}
}
fn connect_service_factory<Io, St, C>(
factory: C,
max_size: usize,
inflight: usize,
handshake_timeout: u64,
) -> impl ServiceFactory<
Config = (),
Request = ioframe::Connect<Io, mqtt::Codec>,
Response = ioframe::ConnectResult<Io, MqttState<St>, mqtt::Codec>,
Error = MqttError<C::Error>,
>
where
Io: AsyncRead + AsyncWrite,
C: ServiceFactory<Config = (), Request = Connect<Io>, Response = ConnectAck<Io, St>>,
{
apply(
Timeout::new(Duration::from_millis(handshake_timeout)),
fn_factory(move || {
let fut = factory.new_service(());
async move {
let service = Cell::new(fut.await?);
Ok::<_, C::InitError>(apply_fn(
service.map_err(MqttError::Service),
move |conn: ioframe::Connect<Io, mqtt::Codec>, service| {
let mut srv = service.clone();
let mut framed = conn.codec(mqtt::Codec::new().max_size(max_size));
async move {
// read first packet
let packet = framed
.next()
.await
.ok_or(MqttError::Disconnected)
.and_then(|res| res.map_err(|e| MqttError::Protocol(e)))?;
match packet {
mqtt::Packet::Connect(connect) => {
let sink = MqttSink::new(framed.sink().clone());
// authenticate mqtt connection
let mut ack = srv
.call(Connect::new(
connect,
framed,
sink.clone(),
inflight,
))
.await?;
match ack.session {
Some(session) => {
log::trace!(
"Sending: {:#?}",
mqtt::Packet::ConnectAck {
session_present: ack.session_present,
return_code:
mqtt::ConnectCode::ConnectionAccepted,
}
);
ack.io
.send(mqtt::Packet::ConnectAck {
session_present: ack.session_present,
return_code:
mqtt::ConnectCode::ConnectionAccepted,
})
.await?;
Ok(ack.io.state(MqttState::new(
session,
sink,
ack.keep_alive,
ack.inflight,
)))
}
None => {
log::trace!(
"Sending: {:#?}",
mqtt::Packet::ConnectAck {
session_present: false,
return_code: ack.return_code,
}
);
ack.io
.send(mqtt::Packet::ConnectAck {
session_present: false,
return_code: ack.return_code,
})
.await?;
Err(MqttError::Disconnected)
}
}
}
packet => {
log::info!(
"MQTT-3.1.0-1: Expected CONNECT packet, received {}",
packet.packet_type()
);
Err(MqttError::Unexpected(
packet,
"MQTT-3.1.0-1: Expected CONNECT packet",
))
}
}
}
},
))
}
}),
)
.map_err(|e| match e {
TimeoutError::Service(e) => e,
TimeoutError::Timeout => MqttError::HandshakeTimeout,
})
}

107
actix-mqtt/src/sink.rs Executable file
View File

@@ -0,0 +1,107 @@
use std::collections::VecDeque;
use std::fmt;
use std::num::NonZeroU16;
use actix_ioframe::Sink;
use actix_utils::oneshot;
use bytes::Bytes;
use bytestring::ByteString;
use futures::future::{Future, TryFutureExt};
use mqtt_codec as mqtt;
use crate::cell::Cell;
#[derive(Clone)]
pub struct MqttSink {
sink: Sink<mqtt::Packet>,
pub(crate) inner: Cell<MqttSinkInner>,
}
#[derive(Default)]
pub(crate) struct MqttSinkInner {
pub(crate) idx: u16,
pub(crate) queue: VecDeque<(u16, oneshot::Sender<()>)>,
}
impl MqttSink {
pub(crate) fn new(sink: Sink<mqtt::Packet>) -> Self {
MqttSink {
sink,
inner: Cell::new(MqttSinkInner::default()),
}
}
/// Close mqtt connection
pub fn close(&self) {
self.sink.close();
}
/// Send publish packet with qos set to 0
pub fn publish_qos0(&self, topic: ByteString, payload: Bytes, dup: bool) {
log::trace!("Publish (QoS0) to {:?}", topic);
let publish = mqtt::Publish {
topic,
payload,
dup,
retain: false,
qos: mqtt::QoS::AtMostOnce,
packet_id: None,
};
self.sink.send(mqtt::Packet::Publish(publish));
}
/// Send publish packet
pub fn publish_qos1(
&mut self,
topic: ByteString,
payload: Bytes,
dup: bool,
) -> impl Future<Output = Result<(), ()>> {
let (tx, rx) = oneshot::channel();
let inner = self.inner.get_mut();
inner.idx += 1;
if inner.idx == 0 {
inner.idx = 1
}
inner.queue.push_back((inner.idx, tx));
let publish = mqtt::Packet::Publish(mqtt::Publish {
topic,
payload,
dup,
retain: false,
qos: mqtt::QoS::AtLeastOnce,
packet_id: NonZeroU16::new(inner.idx),
});
log::trace!("Publish (QoS1) to {:#?}", publish);
self.sink.send(publish);
rx.map_err(|_| ())
}
pub(crate) fn complete_publish_qos1(&mut self, packet_id: NonZeroU16) {
if let Some((idx, tx)) = self.inner.get_mut().queue.pop_front() {
if idx != packet_id.get() {
log::trace!(
"MQTT protocol error, packet_id order does not match, expected {}, got: {}",
idx,
packet_id
);
self.close();
} else {
log::trace!("Ack publish packet with id: {}", packet_id);
let _ = tx.send(());
}
} else {
log::trace!("Unexpected PublishAck packet");
self.close();
}
}
}
impl fmt::Debug for MqttSink {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("MqttSink").finish()
}
}

191
actix-mqtt/src/subs.rs Executable file
View File

@@ -0,0 +1,191 @@
use std::marker::PhantomData;
use bytestring::ByteString;
use mqtt_codec as mqtt;
use crate::dispatcher::MqttState;
use crate::sink::MqttSink;
/// Subscribe message
pub struct Subscribe<S> {
topics: Vec<(ByteString, mqtt::QoS)>,
codes: Vec<mqtt::SubscribeReturnCode>,
state: MqttState<S>,
}
/// Result of a subscribe message
pub struct SubscribeResult {
pub(crate) codes: Vec<mqtt::SubscribeReturnCode>,
}
impl<S> Subscribe<S> {
pub(crate) fn new(state: MqttState<S>, topics: Vec<(ByteString, mqtt::QoS)>) -> Self {
let mut codes = Vec::with_capacity(topics.len());
(0..topics.len()).for_each(|_| codes.push(mqtt::SubscribeReturnCode::Failure));
Self {
topics,
state,
codes,
}
}
#[inline]
/// returns reference to a connection session
pub fn session(&self) -> &S {
self.state.session()
}
#[inline]
/// returns mutable reference to a connection session
pub fn session_mut(&mut self) -> &mut S {
self.state.session_mut()
}
#[inline]
/// Mqtt client sink object
pub fn sink(&self) -> MqttSink {
self.state.sink().clone()
}
#[inline]
/// returns iterator over subscription topics
pub fn iter_mut(&mut self) -> SubscribeIter<S> {
SubscribeIter {
subs: self as *const _ as *mut _,
entry: 0,
lt: PhantomData,
}
}
#[inline]
/// convert subscription to a result
pub fn into_result(self) -> SubscribeResult {
SubscribeResult { codes: self.codes }
}
}
impl<'a, S> IntoIterator for &'a mut Subscribe<S> {
type Item = Subscription<'a, S>;
type IntoIter = SubscribeIter<'a, S>;
fn into_iter(self) -> SubscribeIter<'a, S> {
self.iter_mut()
}
}
/// Iterator over subscription topics
pub struct SubscribeIter<'a, S> {
subs: *mut Subscribe<S>,
entry: usize,
lt: PhantomData<&'a mut Subscribe<S>>,
}
impl<'a, S> SubscribeIter<'a, S> {
fn next_unsafe(&mut self) -> Option<Subscription<'a, S>> {
let subs = unsafe { &mut *self.subs };
if self.entry < subs.topics.len() {
let s = Subscription {
topic: &subs.topics[self.entry].0,
qos: subs.topics[self.entry].1,
state: subs.state.clone(),
code: &mut subs.codes[self.entry],
};
self.entry += 1;
Some(s)
} else {
None
}
}
}
impl<'a, S> Iterator for SubscribeIter<'a, S> {
type Item = Subscription<'a, S>;
#[inline]
fn next(&mut self) -> Option<Subscription<'a, S>> {
self.next_unsafe()
}
}
/// Subscription topic
pub struct Subscription<'a, S> {
topic: &'a ByteString,
state: MqttState<S>,
qos: mqtt::QoS,
code: &'a mut mqtt::SubscribeReturnCode,
}
impl<'a, S> Subscription<'a, S> {
#[inline]
/// reference to a connection session
pub fn session(&self) -> &S {
self.state.session()
}
#[inline]
/// mutable reference to a connection session
pub fn session_mut(&mut self) -> &mut S {
self.state.session_mut()
}
#[inline]
/// subscription topic
pub fn topic(&self) -> &'a ByteString {
&self.topic
}
#[inline]
/// the level of assurance for delivery of an Application Message.
pub fn qos(&self) -> mqtt::QoS {
self.qos
}
#[inline]
/// fail to subscribe to the topic
pub fn fail(&mut self) {
*self.code = mqtt::SubscribeReturnCode::Failure
}
#[inline]
/// subscribe to a topic with specific qos
pub fn subscribe(&mut self, qos: mqtt::QoS) {
*self.code = mqtt::SubscribeReturnCode::Success(qos)
}
}
/// Unsubscribe message
pub struct Unsubscribe<S> {
state: MqttState<S>,
topics: Vec<ByteString>,
}
impl<S> Unsubscribe<S> {
pub(crate) fn new(state: MqttState<S>, topics: Vec<ByteString>) -> Self {
Self { topics, state }
}
#[inline]
/// reference to a connection session
pub fn session(&self) -> &S {
self.state.session()
}
#[inline]
/// mutable reference to a connection session
pub fn session_mut(&mut self) -> &mut S {
self.state.session_mut()
}
#[inline]
/// Mqtt client sink object
pub fn sink(&self) -> MqttSink {
self.state.sink().clone()
}
/// returns iterator over unsubscribe topics
pub fn iter(&self) -> impl Iterator<Item = &ByteString> {
self.topics.iter()
}
}

52
actix-mqtt/tests/test_server.rs Executable file
View File

@@ -0,0 +1,52 @@
use actix_service::Service;
use actix_testing::TestServer;
use bytes::Bytes;
use bytestring::ByteString;
use futures::future::ok;
use actix_mqtt::{client, Connect, ConnectAck, MqttServer, Publish};
#[derive(Clone)]
struct Session;
async fn connect<Io>(packet: Connect<Io>) -> Result<ConnectAck<Io, Session>, ()> {
println!("CONNECT: {:?}", packet);
Ok(packet.ack(Session, false))
}
#[actix_rt::test]
async fn test_simple() -> std::io::Result<()> {
std::env::set_var(
"RUST_LOG",
"actix_codec=info,actix_server=trace,actix_connector=trace",
);
env_logger::init();
let srv = TestServer::with(|| MqttServer::new(connect).finish(|_t| ok(())));
#[derive(Clone)]
struct ClientSession;
let mut client = client::Client::new(ByteString::from_static("user"))
.state(|ack: client::ConnectAck<_>| async move {
ack.sink()
.publish_qos0(ByteString::from_static("#"), Bytes::new(), false);
ack.sink().close();
Ok(ack.state(ClientSession))
})
.finish(|_t: Publish<_>| {
async {
// t.sink().close();
Ok(())
}
});
let conn = actix_connect::default_connector()
.call(actix_connect::Connect::with(String::new(), srv.addr()))
.await
.unwrap();
client.call(conn.into_parts().0).await.unwrap();
Ok(())
}