mirror of
https://github.com/actix/actix-extras.git
synced 2024-11-23 15:51:06 +01:00
add MQTT and AMQP packages
This commit is contained in:
parent
e3da3094f0
commit
7de64899b4
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,3 +11,4 @@ guide/build/
|
|||||||
*.pid
|
*.pid
|
||||||
*.sock
|
*.sock
|
||||||
*~
|
*~
|
||||||
|
.DS_Store
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
"actix-amqp",
|
||||||
|
"actix-amqp/codec",
|
||||||
"actix-cors",
|
"actix-cors",
|
||||||
"actix-identity",
|
"actix-identity",
|
||||||
|
"actix-mqtt",
|
||||||
|
"actix-mqtt/codec",
|
||||||
"actix-protobuf",
|
"actix-protobuf",
|
||||||
"actix-protobuf/examples/prost-example",
|
"actix-protobuf/examples/prost-example",
|
||||||
"actix-redis",
|
"actix-redis",
|
||||||
@ -11,3 +15,5 @@ members = [
|
|||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
actix-session = { path = "actix-session" }
|
actix-session = { path = "actix-session" }
|
||||||
|
mqtt-codec = { path = "actix-mqtt/codec" }
|
||||||
|
amqp-codec = { path = "actix-amqp/codec" }
|
||||||
|
25
actix-amqp/CHANGES.md
Executable file
25
actix-amqp/CHANGES.md
Executable file
@ -0,0 +1,25 @@
|
|||||||
|
# Changes
|
||||||
|
|
||||||
|
## Unreleased - 2020-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.1.4 - 2020-03-05
|
||||||
|
* Add server handshake timeout
|
||||||
|
|
||||||
|
|
||||||
|
## 0.1.3 - 2020-02-10
|
||||||
|
* Allow to override sender link attach frame
|
||||||
|
|
||||||
|
|
||||||
|
## 0.1.2 - 2019-12-25
|
||||||
|
* Allow to specify multi-pattern for topics
|
||||||
|
|
||||||
|
|
||||||
|
## 0.1.1 - 2019-12-18
|
||||||
|
* Separate control frame entries for detach sender qand detach receiver
|
||||||
|
* Proper detach remote receiver
|
||||||
|
* Replace `async fn` with `impl Future`
|
||||||
|
|
||||||
|
|
||||||
|
## 0.1.0 - 2019-12-11
|
||||||
|
* Initial release
|
35
actix-amqp/Cargo.toml
Executable file
35
actix-amqp/Cargo.toml
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
[package]
|
||||||
|
name = "actix-amqp"
|
||||||
|
version = "0.1.4"
|
||||||
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
|
description = "AMQP 1.0 Client/Server framework"
|
||||||
|
documentation = "https://docs.rs/actix-amqp"
|
||||||
|
repository = "https://github.com/actix/actix-extras.git"
|
||||||
|
categories = ["network-programming"]
|
||||||
|
keywords = ["AMQP", "IoT", "messaging"]
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
amqp-codec = "0.1.0"
|
||||||
|
actix-codec = "0.2.0"
|
||||||
|
actix-service = "1.0.1"
|
||||||
|
actix-connect = "1.0.1"
|
||||||
|
actix-router = "0.2.2"
|
||||||
|
actix-utils = "1.0.4"
|
||||||
|
actix-rt = "1.0.0"
|
||||||
|
bytes = "0.5.3"
|
||||||
|
bytestring = "0.1.2"
|
||||||
|
derive_more = "0.99.2"
|
||||||
|
either = "1.5.3"
|
||||||
|
futures = "0.3.1"
|
||||||
|
fxhash = "0.2.1"
|
||||||
|
http = "0.2.0"
|
||||||
|
log = "0.4"
|
||||||
|
pin-project = "0.4.6"
|
||||||
|
uuid = { version = "0.8", features = ["v4"] }
|
||||||
|
slab = "0.4"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
env_logger = "0.6"
|
||||||
|
actix-testing = "1.0.0"
|
3
actix-amqp/README.md
Executable file
3
actix-amqp/README.md
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
# AMQP 1.0 Server Framework
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/actix/actix-amqp.svg?branch=master)](https://travis-ci.org/actix/actix-amqp)
|
31
actix-amqp/codec/Cargo.toml
Executable file
31
actix-amqp/codec/Cargo.toml
Executable file
@ -0,0 +1,31 @@
|
|||||||
|
[package]
|
||||||
|
name = "amqp-codec"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "AMQP 1.0 Protocol Codec"
|
||||||
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>", "Max Gortman <mgortman@microsoft.com>", "Mike Yagley <myagley@gmail.com>"]
|
||||||
|
license = "MIT/Apache-2.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
actix-codec = "0.2.0"
|
||||||
|
bytes = "0.5.2"
|
||||||
|
byteorder = "1.3.1"
|
||||||
|
bytestring = "0.1.1"
|
||||||
|
chrono = "0.4"
|
||||||
|
derive_more = "0.99.2"
|
||||||
|
fxhash = "0.2.1"
|
||||||
|
ordered-float = "1.0"
|
||||||
|
uuid = { version = "0.8", features = ["v4"] }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
handlebars = { version = "0.27", optional = true }
|
||||||
|
serde = { version = "1.0", optional = true }
|
||||||
|
serde_derive = { version = "1.0", optional = true }
|
||||||
|
serde_json = { version = "1.0", optional = true }
|
||||||
|
lazy_static = { version = "1.0", optional = true }
|
||||||
|
regex = { version = "1.3", optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
|
||||||
|
from-spec = ["handlebars", "serde", "serde_derive", "serde_json", "lazy_static", "regex"]
|
3
actix-amqp/codec/README.md
Executable file
3
actix-amqp/codec/README.md
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
# AMQP 1.0 Protocol Codec
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/fafhrd91/amqp-codec.svg?branch=master)](https://travis-ci.org/fafhrd91/amqp-codec)
|
102
actix-amqp/codec/build.rs
Executable file
102
actix-amqp/codec/build.rs
Executable file
@ -0,0 +1,102 @@
|
|||||||
|
#[cfg(feature = "from-spec")]
|
||||||
|
extern crate handlebars;
|
||||||
|
#[cfg(feature = "from-spec")]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
|
#[cfg(feature = "from-spec")]
|
||||||
|
extern crate serde;
|
||||||
|
#[cfg(feature = "from-spec")]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
#[cfg(feature = "from-spec")]
|
||||||
|
extern crate regex;
|
||||||
|
#[cfg(feature = "from-spec")]
|
||||||
|
extern crate serde_json;
|
||||||
|
|
||||||
|
#[cfg(feature = "from-spec")]
|
||||||
|
mod codegen;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
#[cfg(feature = "from-spec")]
|
||||||
|
{
|
||||||
|
generate_from_spec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "from-spec")]
|
||||||
|
fn generate_from_spec() {
|
||||||
|
use handlebars::{Handlebars, Helper, RenderContext, RenderError};
|
||||||
|
use std::env;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
let template = include_str!(concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/codegen/definitions.rs"
|
||||||
|
));
|
||||||
|
let spec = include_str!(concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/codegen/specification.json"
|
||||||
|
));
|
||||||
|
let out_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is not defined")
|
||||||
|
+ "/src/protocol/";
|
||||||
|
|
||||||
|
let definitions = codegen::parse(spec);
|
||||||
|
|
||||||
|
let mut codegen = Handlebars::new();
|
||||||
|
codegen.register_helper(
|
||||||
|
"snake",
|
||||||
|
Box::new(
|
||||||
|
|h: &Helper, _: &Handlebars, rc: &mut RenderContext| -> Result<(), RenderError> {
|
||||||
|
let value = h
|
||||||
|
.param(0)
|
||||||
|
.ok_or_else(|| RenderError::new("Param not found for helper \"snake\""))?;
|
||||||
|
let param = value.value().as_str().ok_or_else(|| {
|
||||||
|
RenderError::new("Non-string param given to helper \"snake\"")
|
||||||
|
})?;
|
||||||
|
rc.writer.write_all(codegen::snake_case(param).as_bytes())?;
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
codegen
|
||||||
|
.register_template_string("definitions", template.to_string())
|
||||||
|
.expect("Failed to register template.");
|
||||||
|
let mut data = std::collections::BTreeMap::new();
|
||||||
|
|
||||||
|
data.insert("defs", definitions);
|
||||||
|
let def_path = std::path::Path::new(&out_dir).join("definitions.rs");
|
||||||
|
{
|
||||||
|
let mut f = File::create(def_path.clone()).expect("Failed to create target file.");
|
||||||
|
let rendered = codegen
|
||||||
|
.render("definitions", &data)
|
||||||
|
.expect("Failed to render template.");
|
||||||
|
writeln!(f, "{}", rendered).expect("Failed to write to file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
reformat_file(&def_path);
|
||||||
|
|
||||||
|
fn reformat_file(path: &std::path::Path) {
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
use std::io::{Read, Seek};
|
||||||
|
std::process::Command::new("rustfmt")
|
||||||
|
.arg(path.to_str().unwrap())
|
||||||
|
.output()
|
||||||
|
.expect("failed to format definitions.rs");
|
||||||
|
|
||||||
|
let mut f = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.open(path)
|
||||||
|
.expect("failed to open file");
|
||||||
|
let mut data = String::new();
|
||||||
|
f.read_to_string(&mut data).expect("failed to read file.");
|
||||||
|
let regex = regex::Regex::new("(?P<a>[\r]?\n)[\\s]*\r?\n").unwrap();
|
||||||
|
data = regex.replace_all(&data, "$a").into();
|
||||||
|
f.seek(std::io::SeekFrom::Start(0)).unwrap();
|
||||||
|
f.set_len(data.len() as u64).unwrap();
|
||||||
|
f.write_all(data.as_bytes())
|
||||||
|
.expect("Error writing reformatted file");
|
||||||
|
}
|
||||||
|
}
|
460
actix-amqp/codec/codegen/definitions.rs
Executable file
460
actix-amqp/codec/codegen/definitions.rs
Executable file
@ -0,0 +1,460 @@
|
|||||||
|
#![allow(unused_assignments, unused_variables, unreachable_patterns)]
|
||||||
|
|
||||||
|
use std::u8;
|
||||||
|
use derive_more::From;
|
||||||
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
|
use bytestring::ByteString;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::errors::AmqpParseError;
|
||||||
|
use crate::codec::{self, decode_format_code, decode_list_header, Decode, DecodeFormatted, Encode};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, From)]
|
||||||
|
pub enum Frame {
|
||||||
|
Open(Open),
|
||||||
|
Begin(Begin),
|
||||||
|
Attach(Attach),
|
||||||
|
Flow(Flow),
|
||||||
|
Transfer(Transfer),
|
||||||
|
Disposition(Disposition),
|
||||||
|
Detach(Detach),
|
||||||
|
End(End),
|
||||||
|
Close(Close),
|
||||||
|
Empty,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Frame {
|
||||||
|
pub fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Frame::Open(_) => "Open",
|
||||||
|
Frame::Begin(_) => "Begin",
|
||||||
|
Frame::Attach(_) => "Attach",
|
||||||
|
Frame::Flow(_) => "Flow",
|
||||||
|
Frame::Transfer(_) => "Transfer",
|
||||||
|
Frame::Disposition(_) => "Disposition",
|
||||||
|
Frame::Detach(_) => "Detach",
|
||||||
|
Frame::End(_) => "End",
|
||||||
|
Frame::Close(_) => "Close",
|
||||||
|
Frame::Empty => "Empty",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode for Frame {
|
||||||
|
fn decode(input: &[u8]) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
if input.is_empty() {
|
||||||
|
Ok((input, Frame::Empty))
|
||||||
|
} else {
|
||||||
|
let (input, fmt) = decode_format_code(input)?;
|
||||||
|
validate_code!(fmt, codec::FORMATCODE_DESCRIBED);
|
||||||
|
let (input, descriptor) = Descriptor::decode(input)?;
|
||||||
|
match descriptor {
|
||||||
|
Descriptor::Ulong(16) => decode_open_inner(input).map(|(i, r)| (i, Frame::Open(r))),
|
||||||
|
Descriptor::Ulong(17) => {
|
||||||
|
decode_begin_inner(input).map(|(i, r)| (i, Frame::Begin(r)))
|
||||||
|
}
|
||||||
|
Descriptor::Ulong(18) => {
|
||||||
|
decode_attach_inner(input).map(|(i, r)| (i, Frame::Attach(r)))
|
||||||
|
}
|
||||||
|
Descriptor::Ulong(19) => decode_flow_inner(input).map(|(i, r)| (i, Frame::Flow(r))),
|
||||||
|
Descriptor::Ulong(20) => {
|
||||||
|
decode_transfer_inner(input).map(|(i, r)| (i, Frame::Transfer(r)))
|
||||||
|
}
|
||||||
|
Descriptor::Ulong(21) => {
|
||||||
|
decode_disposition_inner(input).map(|(i, r)| (i, Frame::Disposition(r)))
|
||||||
|
}
|
||||||
|
Descriptor::Ulong(22) => {
|
||||||
|
decode_detach_inner(input).map(|(i, r)| (i, Frame::Detach(r)))
|
||||||
|
}
|
||||||
|
Descriptor::Ulong(23) => decode_end_inner(input).map(|(i, r)| (i, Frame::End(r))),
|
||||||
|
Descriptor::Ulong(24) => {
|
||||||
|
decode_close_inner(input).map(|(i, r)| (i, Frame::Close(r)))
|
||||||
|
}
|
||||||
|
Descriptor::Symbol(ref a) if a.as_str() == "amqp:open:list" => {
|
||||||
|
decode_open_inner(input).map(|(i, r)| (i, Frame::Open(r)))
|
||||||
|
}
|
||||||
|
Descriptor::Symbol(ref a) if a.as_str() == "amqp:begin:list" => {
|
||||||
|
decode_begin_inner(input).map(|(i, r)| (i, Frame::Begin(r)))
|
||||||
|
}
|
||||||
|
Descriptor::Symbol(ref a) if a.as_str() == "amqp:attach:list" => {
|
||||||
|
decode_attach_inner(input).map(|(i, r)| (i, Frame::Attach(r)))
|
||||||
|
}
|
||||||
|
Descriptor::Symbol(ref a) if a.as_str() == "amqp:flow:list" => {
|
||||||
|
decode_flow_inner(input).map(|(i, r)| (i, Frame::Flow(r)))
|
||||||
|
}
|
||||||
|
Descriptor::Symbol(ref a) if a.as_str() == "amqp:transfer:list" => {
|
||||||
|
decode_transfer_inner(input).map(|(i, r)| (i, Frame::Transfer(r)))
|
||||||
|
}
|
||||||
|
Descriptor::Symbol(ref a) if a.as_str() == "amqp:disposition:list" => {
|
||||||
|
decode_disposition_inner(input).map(|(i, r)| (i, Frame::Disposition(r)))
|
||||||
|
}
|
||||||
|
Descriptor::Symbol(ref a) if a.as_str() == "amqp:detach:list" => {
|
||||||
|
decode_detach_inner(input).map(|(i, r)| (i, Frame::Detach(r)))
|
||||||
|
}
|
||||||
|
Descriptor::Symbol(ref a) if a.as_str() == "amqp:end:list" => {
|
||||||
|
decode_end_inner(input).map(|(i, r)| (i, Frame::End(r)))
|
||||||
|
}
|
||||||
|
Descriptor::Symbol(ref a) if a.as_str() == "amqp:close:list" => {
|
||||||
|
decode_close_inner(input).map(|(i, r)| (i, Frame::Close(r)))
|
||||||
|
}
|
||||||
|
_ => Err(AmqpParseError::InvalidDescriptor(descriptor)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for Frame {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
match *self {
|
||||||
|
Frame::Open(ref v) => encoded_size_open_inner(v),
|
||||||
|
Frame::Begin(ref v) => encoded_size_begin_inner(v),
|
||||||
|
Frame::Attach(ref v) => encoded_size_attach_inner(v),
|
||||||
|
Frame::Flow(ref v) => encoded_size_flow_inner(v),
|
||||||
|
Frame::Transfer(ref v) => encoded_size_transfer_inner(v),
|
||||||
|
Frame::Disposition(ref v) => encoded_size_disposition_inner(v),
|
||||||
|
Frame::Detach(ref v) => encoded_size_detach_inner(v),
|
||||||
|
Frame::End(ref v) => encoded_size_end_inner(v),
|
||||||
|
Frame::Close(ref v) => encoded_size_close_inner(v),
|
||||||
|
Frame::Empty => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
match *self {
|
||||||
|
Frame::Open(ref v) => encode_open_inner(v, buf),
|
||||||
|
Frame::Begin(ref v) => encode_begin_inner(v, buf),
|
||||||
|
Frame::Attach(ref v) => encode_attach_inner(v, buf),
|
||||||
|
Frame::Flow(ref v) => encode_flow_inner(v, buf),
|
||||||
|
Frame::Transfer(ref v) => encode_transfer_inner(v, buf),
|
||||||
|
Frame::Disposition(ref v) => encode_disposition_inner(v, buf),
|
||||||
|
Frame::Detach(ref v) => encode_detach_inner(v, buf),
|
||||||
|
Frame::End(ref v) => encode_end_inner(v, buf),
|
||||||
|
Frame::Close(ref v) => encode_close_inner(v, buf),
|
||||||
|
Frame::Empty => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{{#each defs.provides as |provide|}}
|
||||||
|
{{#if provide.described}}
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum {{provide.name}} {
|
||||||
|
{{#each provide.options as |option|}}
|
||||||
|
{{option.ty}}({{option.ty}}),
|
||||||
|
{{/each}}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for {{provide.name}} {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
validate_code!(fmt, codec::FORMATCODE_DESCRIBED);
|
||||||
|
let (input, descriptor) = Descriptor::decode(input)?;
|
||||||
|
match descriptor {
|
||||||
|
{{#each provide.options as |option|}}
|
||||||
|
Descriptor::Ulong({{option.descriptor.code}}) => decode_{{snake option.ty}}_inner(input).map(|(i, r)| (i, {{provide.name}}::{{option.ty}}(r))),
|
||||||
|
{{/each}}
|
||||||
|
{{#each provide.options as |option|}}
|
||||||
|
Descriptor::Symbol(ref a) if a.as_str() == "{{option.descriptor.name}}" => decode_{{snake option.ty}}_inner(input).map(|(i, r)| (i, {{provide.name}}::{{option.ty}}(r))),
|
||||||
|
{{/each}}
|
||||||
|
_ => Err(AmqpParseError::InvalidDescriptor(descriptor))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for {{provide.name}} {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
match *self {
|
||||||
|
{{#each provide.options as |option|}}
|
||||||
|
{{provide.name}}::{{option.ty}}(ref v) => encoded_size_{{snake option.ty}}_inner(v),
|
||||||
|
{{/each}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
match *self {
|
||||||
|
{{#each provide.options as |option|}}
|
||||||
|
{{provide.name}}::{{option.ty}}(ref v) => encode_{{snake option.ty}}_inner(v, buf),
|
||||||
|
{{/each}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{{/if}}
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
{{#each defs.aliases as |alias|}}
|
||||||
|
pub type {{alias.name}} = {{alias.source}};
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
{{#each defs.enums as |enum|}}
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum {{enum.name}} {
|
||||||
|
{{#each enum.items as |item|}}
|
||||||
|
{{item.name}},
|
||||||
|
{{/each}}
|
||||||
|
}
|
||||||
|
{{#if enum.is_symbol}}
|
||||||
|
impl {{enum.name}} {
|
||||||
|
pub fn try_from(v: &Symbol) -> Result<Self, AmqpParseError> {
|
||||||
|
match v.as_str() {
|
||||||
|
{{#each enum.items as |item|}}
|
||||||
|
"{{item.value}}" => Ok({{enum.name}}::{{item.name}}),
|
||||||
|
{{/each}}
|
||||||
|
_ => Err(AmqpParseError::UnknownEnumOption("{{enum.name}}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DecodeFormatted for {{enum.name}} {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
let (input, base) = Symbol::decode_with_format(input, fmt)?;
|
||||||
|
Ok((input, Self::try_from(&base)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Encode for {{enum.name}} {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
match *self {
|
||||||
|
{{#each enum.items as |item|}}
|
||||||
|
{{enum.name}}::{{item.name}} => {{item.value_len}} + 2,
|
||||||
|
{{/each}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
match *self {
|
||||||
|
{{#each enum.items as |item|}}
|
||||||
|
{{enum.name}}::{{item.name}} => StaticSymbol("{{item.value}}").encode(buf),
|
||||||
|
{{/each}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{{else}}
|
||||||
|
impl {{enum.name}} {
|
||||||
|
pub fn try_from(v: {{enum.ty}}) -> Result<Self, AmqpParseError> {
|
||||||
|
match v {
|
||||||
|
{{#each enum.items as |item|}}
|
||||||
|
{{item.value}} => Ok({{enum.name}}::{{item.name}}),
|
||||||
|
{{/each}}
|
||||||
|
_ => Err(AmqpParseError::UnknownEnumOption("{{enum.name}}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DecodeFormatted for {{enum.name}} {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
let (input, base) = {{enum.ty}}::decode_with_format(input, fmt)?;
|
||||||
|
Ok((input, Self::try_from(base)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Encode for {{enum.name}} {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
match *self {
|
||||||
|
{{#each enum.items as |item|}}
|
||||||
|
{{enum.name}}::{{item.name}} => {
|
||||||
|
let v : {{enum.ty}} = {{item.value}};
|
||||||
|
v.encoded_size()
|
||||||
|
},
|
||||||
|
{{/each}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
match *self {
|
||||||
|
{{#each enum.items as |item|}}
|
||||||
|
{{enum.name}}::{{item.name}} => {
|
||||||
|
let v : {{enum.ty}} = {{item.value}};
|
||||||
|
v.encode(buf);
|
||||||
|
},
|
||||||
|
{{/each}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{{/if}}
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
{{#each defs.described_restricted as |dr|}}
|
||||||
|
type {{dr.name}} = {{dr.ty}};
|
||||||
|
fn decode_{{snake dr.name}}_inner(input: &[u8]) -> Result<(&[u8], {{dr.name}}), AmqpParseError> {
|
||||||
|
{{dr.name}}::decode(input)
|
||||||
|
}
|
||||||
|
fn encoded_size_{{snake dr.name}}_inner(dr: &{{dr.name}}) -> usize {
|
||||||
|
// descriptor size + actual size
|
||||||
|
3 + dr.encoded_size()
|
||||||
|
}
|
||||||
|
fn encode_{{snake dr.name}}_inner(dr: &{{dr.name}}, buf: &mut BytesMut) {
|
||||||
|
Descriptor::Ulong({{dr.descriptor.code}}).encode(buf);
|
||||||
|
dr.encode(buf);
|
||||||
|
}
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
{{#each defs.lists as |list|}}
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct {{list.name}} {
|
||||||
|
{{#each list.fields as |field|}}
|
||||||
|
{{#if field.optional}}
|
||||||
|
pub {{field.name}}: Option<{{{field.ty}}}>,
|
||||||
|
{{else}}
|
||||||
|
pub {{field.name}}: {{{field.ty}}},
|
||||||
|
{{/if}}
|
||||||
|
{{/each}}
|
||||||
|
{{#if list.transfer}}
|
||||||
|
pub body: Option<TransferBody>,
|
||||||
|
{{/if}}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl {{list.name}} {
|
||||||
|
{{#each list.fields as |field|}}
|
||||||
|
{{#if field.is_str}}
|
||||||
|
{{#if field.optional}}
|
||||||
|
pub fn {{field.name}}(&self) -> Option<&str> {
|
||||||
|
match self.{{field.name}} {
|
||||||
|
None => None,
|
||||||
|
Some(ref s) => Some(s.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{{else}}
|
||||||
|
pub fn {{field.name}}(&self) -> &str { self.{{field.name}}.as_str() }
|
||||||
|
{{/if}}
|
||||||
|
{{else}}
|
||||||
|
{{#if field.is_ref}}
|
||||||
|
{{#if field.optional}}
|
||||||
|
pub fn {{field.name}}(&self) -> Option<&{{{field.ty}}}> { self.{{field.name}}.as_ref() }
|
||||||
|
{{else}}
|
||||||
|
pub fn {{field.name}}(&self) -> &{{{field.ty}}} { &self.{{field.name}} }
|
||||||
|
{{/if}}
|
||||||
|
{{else}}
|
||||||
|
{{#if field.optional}}
|
||||||
|
pub fn {{field.name}}(&self) -> Option<{{{field.ty}}}> { self.{{field.name}} }
|
||||||
|
{{else}}
|
||||||
|
pub fn {{field.name}}(&self) -> {{{field.ty}}} { self.{{field.name}} }
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
{{#if list.transfer}}
|
||||||
|
pub fn body(&self) -> Option<&TransferBody> {
|
||||||
|
self.body.as_ref()
|
||||||
|
}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
#[allow(clippy::identity_op)]
|
||||||
|
const FIELD_COUNT: usize = 0 {{#each list.fields as |field|}} + 1{{/each}};
|
||||||
|
}
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
fn decode_{{snake list.name}}_inner(input: &[u8]) -> Result<(&[u8], {{list.name}}), AmqpParseError> {
|
||||||
|
let (input, format) = decode_format_code(input)?;
|
||||||
|
let (input, header) = decode_list_header(input, format)?;
|
||||||
|
let size = header.size as usize;
|
||||||
|
decode_check_len!(input, size);
|
||||||
|
{{#if list.fields}}
|
||||||
|
let (mut input, mut remainder) = input.split_at(size);
|
||||||
|
let mut count = header.count;
|
||||||
|
{{#each list.fields as |field|}}
|
||||||
|
{{#if field.optional}}
|
||||||
|
let {{field.name}}: Option<{{{field.ty}}}>;
|
||||||
|
if count > 0 {
|
||||||
|
let decoded = Option::<{{{field.ty}}}>::decode(input)?;
|
||||||
|
input = decoded.0;
|
||||||
|
{{field.name}} = decoded.1;
|
||||||
|
count -= 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
{{field.name}} = None;
|
||||||
|
}
|
||||||
|
{{else}}
|
||||||
|
let {{field.name}}: {{{field.ty}}};
|
||||||
|
if count > 0 {
|
||||||
|
{{#if field.default}}
|
||||||
|
let (in1, decoded) = Option::<{{{field.ty}}}>::decode(input)?;
|
||||||
|
{{field.name}} = decoded.unwrap_or({{field.default}});
|
||||||
|
{{else}}
|
||||||
|
let (in1, decoded) = {{{field.ty}}}::decode(input)?;
|
||||||
|
{{field.name}} = decoded;
|
||||||
|
{{/if}}
|
||||||
|
input = in1;
|
||||||
|
count -= 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
{{#if field.default}}
|
||||||
|
{{field.name}} = {{field.default}};
|
||||||
|
{{else}}
|
||||||
|
return Err(AmqpParseError::RequiredFieldOmitted("{{field.name}}"))
|
||||||
|
{{/if}}
|
||||||
|
}
|
||||||
|
{{/if}}
|
||||||
|
{{/each}}
|
||||||
|
{{else}}
|
||||||
|
let mut remainder = &input[size..];
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if list.transfer}}
|
||||||
|
let body = if remainder.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let b = Bytes::copy_from_slice(remainder);
|
||||||
|
remainder = &[];
|
||||||
|
Some(b.into())
|
||||||
|
};
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
Ok((remainder, {{list.name}} {
|
||||||
|
{{#each list.fields as |field|}}
|
||||||
|
{{field.name}},
|
||||||
|
{{/each}}
|
||||||
|
{{#if list.transfer}}
|
||||||
|
body
|
||||||
|
{{/if}}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encoded_size_{{snake list.name}}_inner(list: &{{list.name}}) -> usize {
|
||||||
|
#[allow(clippy::identity_op)]
|
||||||
|
let content_size = 0 {{#each list.fields as |field|}} + list.{{field.name}}.encoded_size(){{/each}};
|
||||||
|
// header: 0x00 0x53 <descriptor code> format_code size count
|
||||||
|
(if content_size + 1 > u8::MAX as usize { 12 } else { 6 })
|
||||||
|
+ content_size
|
||||||
|
|
||||||
|
{{#if list.transfer}}
|
||||||
|
+ list.body.as_ref().map(|b| b.len()).unwrap_or(0)
|
||||||
|
{{/if}}
|
||||||
|
}
|
||||||
|
fn encode_{{snake list.name}}_inner(list: &{{list.name}}, buf: &mut BytesMut) {
|
||||||
|
Descriptor::Ulong({{list.descriptor.code}}).encode(buf);
|
||||||
|
#[allow(clippy::identity_op)]
|
||||||
|
let content_size = 0 {{#each list.fields as |field|}} + list.{{field.name}}.encoded_size(){{/each}};
|
||||||
|
if content_size + 1 > u8::MAX as usize {
|
||||||
|
buf.put_u8(codec::FORMATCODE_LIST32);
|
||||||
|
buf.put_u32((content_size + 4) as u32); // +4 for 4 byte count
|
||||||
|
buf.put_u32({{list.name}}::FIELD_COUNT as u32);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buf.put_u8(codec::FORMATCODE_LIST8);
|
||||||
|
buf.put_u8((content_size + 1) as u8);
|
||||||
|
buf.put_u8({{list.name}}::FIELD_COUNT as u8);
|
||||||
|
}
|
||||||
|
{{#each list.fields as |field|}}
|
||||||
|
list.{{field.name}}.encode(buf);
|
||||||
|
{{/each}}
|
||||||
|
{{#if list.transfer}}
|
||||||
|
if let Some(ref body) = list.body {
|
||||||
|
body.encode(buf)
|
||||||
|
}
|
||||||
|
{{/if}}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for {{list.name}} {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
validate_code!(fmt, codec::FORMATCODE_DESCRIBED);
|
||||||
|
let (input, descriptor) = Descriptor::decode(input)?;
|
||||||
|
let is_match = match descriptor {
|
||||||
|
Descriptor::Ulong(val) => val == {{list.descriptor.code}},
|
||||||
|
Descriptor::Symbol(ref sym) => sym.as_bytes() == b"{{list.descriptor.name}}",
|
||||||
|
};
|
||||||
|
if !is_match {
|
||||||
|
Err(AmqpParseError::InvalidDescriptor(descriptor))
|
||||||
|
} else {
|
||||||
|
decode_{{snake list.name}}_inner(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for {{list.name}} {
|
||||||
|
fn encoded_size(&self) -> usize { encoded_size_{{snake list.name}}_inner(self) }
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut) { encode_{{snake list.name}}_inner(self, buf) }
|
||||||
|
}
|
||||||
|
{{/each}}
|
478
actix-amqp/codec/codegen/mod.rs
Executable file
478
actix-amqp/codec/codegen/mod.rs
Executable file
@ -0,0 +1,478 @@
|
|||||||
|
use serde::{Deserialize, Deserializer};
|
||||||
|
use serde_json::from_str;
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::str::{FromStr, ParseBoolError};
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref PRIMITIVE_TYPES: HashMap<&'static str, &'static str> = {
|
||||||
|
let mut m = HashMap::new();
|
||||||
|
m.insert("*", "Variant");
|
||||||
|
m.insert("binary", "Bytes");
|
||||||
|
m.insert("string", "ByteString");
|
||||||
|
m.insert("ubyte", "u8");
|
||||||
|
m.insert("ushort", "u16");
|
||||||
|
m.insert("uint", "u32");
|
||||||
|
m.insert("ulong", "u64");
|
||||||
|
m.insert("boolean", "bool");
|
||||||
|
m
|
||||||
|
};
|
||||||
|
static ref STRING_TYPES: HashSet<&'static str> = ["string", "symbol"].iter().cloned().collect();
|
||||||
|
static ref REF_TYPES: Mutex<HashSet<String>> = Mutex::new(
|
||||||
|
[
|
||||||
|
"Bytes",
|
||||||
|
"ByteString",
|
||||||
|
"Symbol",
|
||||||
|
"Fields",
|
||||||
|
"Map",
|
||||||
|
"MessageId",
|
||||||
|
"Address",
|
||||||
|
"NodeProperties",
|
||||||
|
"Outcome",
|
||||||
|
"DeliveryState",
|
||||||
|
"FilterSet",
|
||||||
|
"DeliveryTag",
|
||||||
|
"Symbols",
|
||||||
|
"IetfLanguageTags",
|
||||||
|
"ErrorCondition",
|
||||||
|
"DistributionMode"
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect()
|
||||||
|
);
|
||||||
|
static ref ENUM_TYPES: Mutex<HashSet<String>> = Mutex::new(HashSet::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(spec: &str) -> Definitions {
|
||||||
|
let types = from_str::<Vec<_Type>>(spec).expect("Failed to parse AMQP spec.");
|
||||||
|
{
|
||||||
|
let mut ref_map = REF_TYPES.lock().unwrap();
|
||||||
|
let mut enum_map = ENUM_TYPES.lock().unwrap();
|
||||||
|
for t in types.iter() {
|
||||||
|
match *t {
|
||||||
|
_Type::Described(ref l) if l.source == "list" => {
|
||||||
|
ref_map.insert(camel_case(&*l.name));
|
||||||
|
}
|
||||||
|
_Type::Choice(ref e) => {
|
||||||
|
enum_map.insert(camel_case(&*e.name));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Definitions::from(types)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum _Type {
|
||||||
|
Choice(_Enum),
|
||||||
|
Described(_Described),
|
||||||
|
Alias(_Alias),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
struct _Descriptor {
|
||||||
|
name: String,
|
||||||
|
code: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
struct _Enum {
|
||||||
|
name: String,
|
||||||
|
source: String,
|
||||||
|
provides: Option<String>,
|
||||||
|
choice: Vec<EnumItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
struct _Described {
|
||||||
|
name: String,
|
||||||
|
class: String,
|
||||||
|
source: String,
|
||||||
|
provides: Option<String>,
|
||||||
|
descriptor: _Descriptor,
|
||||||
|
#[serde(default)]
|
||||||
|
field: Vec<_Field>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
struct _Field {
|
||||||
|
name: String,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
ty: String,
|
||||||
|
#[serde(default)]
|
||||||
|
#[serde(deserialize_with = "string_as_bool")]
|
||||||
|
mandatory: bool,
|
||||||
|
default: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
#[serde(deserialize_with = "string_as_bool")]
|
||||||
|
multiple: bool,
|
||||||
|
requires: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct _Alias {
|
||||||
|
name: String,
|
||||||
|
source: String,
|
||||||
|
provides: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Alias {
|
||||||
|
name: String,
|
||||||
|
source: String,
|
||||||
|
provides: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct EnumItem {
|
||||||
|
name: String,
|
||||||
|
value: String,
|
||||||
|
#[serde(default)]
|
||||||
|
value_len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Definitions {
|
||||||
|
aliases: Vec<Alias>,
|
||||||
|
enums: Vec<Enum>,
|
||||||
|
lists: Vec<Described>,
|
||||||
|
described_restricted: Vec<Described>,
|
||||||
|
provides: Vec<ProvidesEnum>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
struct ProvidesEnum {
|
||||||
|
name: String,
|
||||||
|
described: bool,
|
||||||
|
options: Vec<ProvidesItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
struct ProvidesItem {
|
||||||
|
ty: String,
|
||||||
|
descriptor: Descriptor,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Enum {
|
||||||
|
name: String,
|
||||||
|
ty: String,
|
||||||
|
provides: Vec<String>,
|
||||||
|
items: Vec<EnumItem>,
|
||||||
|
is_symbol: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Described {
|
||||||
|
name: String,
|
||||||
|
ty: String,
|
||||||
|
provides: Vec<String>,
|
||||||
|
descriptor: Descriptor,
|
||||||
|
fields: Vec<Field>,
|
||||||
|
transfer: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Descriptor {
|
||||||
|
name: String,
|
||||||
|
domain: u32,
|
||||||
|
code: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Field {
|
||||||
|
name: String,
|
||||||
|
ty: String,
|
||||||
|
is_str: bool,
|
||||||
|
is_ref: bool,
|
||||||
|
optional: bool,
|
||||||
|
default: String,
|
||||||
|
multiple: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Definitions {
|
||||||
|
fn from(types: Vec<_Type>) -> Definitions {
|
||||||
|
let mut aliases = vec![];
|
||||||
|
let mut enums = vec![];
|
||||||
|
let mut lists = vec![];
|
||||||
|
let mut described_restricted = vec![];
|
||||||
|
let mut provide_map: HashMap<String, Vec<ProvidesItem>> = HashMap::new();
|
||||||
|
for t in types.into_iter() {
|
||||||
|
match t {
|
||||||
|
_Type::Alias(ref a) if a.source != "map" => {
|
||||||
|
let al = Alias::from(a.clone());
|
||||||
|
Definitions::register_provides(&mut provide_map, &al.name, None, &al.provides);
|
||||||
|
aliases.push(al);
|
||||||
|
}
|
||||||
|
_Type::Choice(ref e) => {
|
||||||
|
let en = Enum::from(e.clone());
|
||||||
|
Definitions::register_provides(&mut provide_map, &en.name, None, &en.provides);
|
||||||
|
enums.push(en);
|
||||||
|
}
|
||||||
|
_Type::Described(ref d) if d.source == "list" && d.class != "restricted" => {
|
||||||
|
let ls = Described::list(d.clone());
|
||||||
|
Definitions::register_provides(
|
||||||
|
&mut provide_map,
|
||||||
|
&ls.name,
|
||||||
|
Some(ls.descriptor.clone()),
|
||||||
|
&ls.provides,
|
||||||
|
);
|
||||||
|
lists.push(ls);
|
||||||
|
}
|
||||||
|
_Type::Described(ref d) if d.class == "restricted" => {
|
||||||
|
let ls = Described::alias(d.clone());
|
||||||
|
Definitions::register_provides(
|
||||||
|
&mut provide_map,
|
||||||
|
&ls.name,
|
||||||
|
Some(ls.descriptor.clone()),
|
||||||
|
&ls.provides,
|
||||||
|
);
|
||||||
|
described_restricted.push(ls);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let provides = provide_map
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(k, v)| {
|
||||||
|
if v.len() == 1 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(ProvidesEnum {
|
||||||
|
described: if k == "Frame" {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
v.iter().any(|v| v.descriptor.code != 0)
|
||||||
|
},
|
||||||
|
name: k,
|
||||||
|
options: v,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Definitions {
|
||||||
|
aliases,
|
||||||
|
enums,
|
||||||
|
lists,
|
||||||
|
described_restricted,
|
||||||
|
provides,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_provides(
|
||||||
|
map: &mut HashMap<String, Vec<ProvidesItem>>,
|
||||||
|
name: &str,
|
||||||
|
descriptor: Option<Descriptor>,
|
||||||
|
provides: &Vec<String>,
|
||||||
|
) {
|
||||||
|
for p in provides.iter() {
|
||||||
|
map.entry(p.clone())
|
||||||
|
.or_insert_with(|| vec![])
|
||||||
|
.push(ProvidesItem {
|
||||||
|
ty: name.to_string(),
|
||||||
|
descriptor: descriptor.clone().unwrap_or_else(|| Descriptor {
|
||||||
|
name: String::new(),
|
||||||
|
domain: 0,
|
||||||
|
code: 0,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Alias {
|
||||||
|
fn from(a: _Alias) -> Alias {
|
||||||
|
Alias {
|
||||||
|
name: camel_case(&*a.name),
|
||||||
|
source: get_type_name(&*a.source, None),
|
||||||
|
provides: parse_provides(a.provides),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Enum {
|
||||||
|
fn from(e: _Enum) -> Enum {
|
||||||
|
let ty = get_type_name(&*e.source, None);
|
||||||
|
let is_symbol = ty == "Symbol";
|
||||||
|
Enum {
|
||||||
|
name: camel_case(&*e.name),
|
||||||
|
ty: ty.clone(),
|
||||||
|
provides: parse_provides(e.provides),
|
||||||
|
is_symbol,
|
||||||
|
items: e
|
||||||
|
.choice
|
||||||
|
.into_iter()
|
||||||
|
.map(|c| EnumItem {
|
||||||
|
name: camel_case(&*c.name),
|
||||||
|
value_len: c.value.len(),
|
||||||
|
value: c.value,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Described {
|
||||||
|
fn list(d: _Described) -> Described {
|
||||||
|
let transfer = d.name == "transfer";
|
||||||
|
Described {
|
||||||
|
name: camel_case(&d.name),
|
||||||
|
ty: String::new(),
|
||||||
|
provides: parse_provides(d.provides),
|
||||||
|
descriptor: Descriptor::from(d.descriptor),
|
||||||
|
fields: d.field.into_iter().map(|f| Field::from(f)).collect(),
|
||||||
|
transfer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn alias(d: _Described) -> Described {
|
||||||
|
Described {
|
||||||
|
name: camel_case(&d.name),
|
||||||
|
ty: get_type_name(&d.source, None),
|
||||||
|
provides: parse_provides(d.provides),
|
||||||
|
descriptor: Descriptor::from(d.descriptor),
|
||||||
|
fields: d.field.into_iter().map(|f| Field::from(f)).collect(),
|
||||||
|
transfer: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Descriptor {
|
||||||
|
fn from(d: _Descriptor) -> Descriptor {
|
||||||
|
let code_parts: Vec<u32> = d
|
||||||
|
.code
|
||||||
|
.split(":")
|
||||||
|
.map(|p| {
|
||||||
|
assert!(p.starts_with("0x"));
|
||||||
|
u32::from_str_radix(&p[2..], 16).expect("malformed descriptor code")
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Descriptor {
|
||||||
|
name: d.name,
|
||||||
|
domain: code_parts[0],
|
||||||
|
code: code_parts[1],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Field {
|
||||||
|
fn from(field: _Field) -> Field {
|
||||||
|
let mut ty = get_type_name(&*field.ty, field.requires);
|
||||||
|
if field.multiple {
|
||||||
|
ty.push('s');
|
||||||
|
}
|
||||||
|
let is_str = STRING_TYPES.contains(&*ty) && !field.multiple;
|
||||||
|
let is_ref = REF_TYPES.lock().unwrap().contains(&ty);
|
||||||
|
let default = Field::format_default(field.default, &ty);
|
||||||
|
Field {
|
||||||
|
name: snake_case(&*field.name),
|
||||||
|
ty: ty,
|
||||||
|
is_ref,
|
||||||
|
is_str,
|
||||||
|
optional: !field.mandatory && default.len() == 0,
|
||||||
|
multiple: field.multiple,
|
||||||
|
default,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_default(default: Option<String>, ty: &str) -> String {
|
||||||
|
match default {
|
||||||
|
None => String::new(),
|
||||||
|
Some(def) => {
|
||||||
|
if ENUM_TYPES.lock().unwrap().contains(ty) {
|
||||||
|
format!("{}::{}", ty, camel_case(&*def))
|
||||||
|
} else {
|
||||||
|
def
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_type_name(ty: &str, req: Option<String>) -> String {
|
||||||
|
match req {
|
||||||
|
Some(t) => camel_case(&*t),
|
||||||
|
None => match PRIMITIVE_TYPES.get(ty) {
|
||||||
|
Some(p) => p.to_string(),
|
||||||
|
None => camel_case(&*ty),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_provides(p: Option<String>) -> Vec<String> {
|
||||||
|
p.map(|v| {
|
||||||
|
v.split_terminator(",")
|
||||||
|
.filter_map(|s| {
|
||||||
|
let s = s.trim();
|
||||||
|
if s == "" {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(camel_case(&s))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap_or(vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_as_bool<'de, T, D>(deserializer: D) -> Result<T, D::Error>
|
||||||
|
where
|
||||||
|
T: FromStr<Err = ParseBoolError>,
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
Ok(String::deserialize(deserializer)?
|
||||||
|
.parse::<T>()
|
||||||
|
.expect("Error parsing bool from string"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn camel_case(name: &str) -> String {
|
||||||
|
let mut new_word = true;
|
||||||
|
name.chars().fold("".to_string(), |mut result, ch| {
|
||||||
|
if ch == '-' || ch == '_' || ch == ' ' {
|
||||||
|
new_word = true;
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
result.push(if new_word {
|
||||||
|
ch.to_ascii_uppercase()
|
||||||
|
} else {
|
||||||
|
ch
|
||||||
|
});
|
||||||
|
new_word = false;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn snake_case(name: &str) -> String {
|
||||||
|
match name {
|
||||||
|
"type" => "type_".to_string(),
|
||||||
|
"return" => "return_".to_string(),
|
||||||
|
name => {
|
||||||
|
let mut new_word = false;
|
||||||
|
let mut last_was_upper = false;
|
||||||
|
name.chars().fold("".to_string(), |mut result, ch| {
|
||||||
|
if ch == '-' || ch == '_' || ch == ' ' {
|
||||||
|
new_word = true;
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
let uppercase = ch.is_uppercase();
|
||||||
|
if new_word || (!last_was_upper && !result.is_empty() && uppercase) {
|
||||||
|
result.push('_');
|
||||||
|
new_word = false;
|
||||||
|
}
|
||||||
|
last_was_upper = uppercase;
|
||||||
|
result.push(if uppercase {
|
||||||
|
ch.to_ascii_lowercase()
|
||||||
|
} else {
|
||||||
|
ch
|
||||||
|
});
|
||||||
|
result
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1234
actix-amqp/codec/codegen/specification.json
Executable file
1234
actix-amqp/codec/codegen/specification.json
Executable file
File diff suppressed because it is too large
Load Diff
834
actix-amqp/codec/src/codec/decode.rs
Executable file
834
actix-amqp/codec/src/codec/decode.rs
Executable file
@ -0,0 +1,834 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::hash::{BuildHasher, Hash};
|
||||||
|
use std::{char, str, u8};
|
||||||
|
|
||||||
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
|
use bytes::Bytes;
|
||||||
|
use bytestring::ByteString;
|
||||||
|
use chrono::{DateTime, TimeZone, Utc};
|
||||||
|
use fxhash::FxHashMap;
|
||||||
|
use ordered_float::OrderedFloat;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::codec::{self, ArrayDecode, Decode, DecodeFormatted};
|
||||||
|
use crate::errors::AmqpParseError;
|
||||||
|
use crate::framing::{self, AmqpFrame, SaslFrame, HEADER_LEN};
|
||||||
|
use crate::protocol::{self, CompoundHeader};
|
||||||
|
use crate::types::{
|
||||||
|
Descriptor, List, Multiple, Str, Symbol, Variant, VariantMap, VecStringMap, VecSymbolMap,
|
||||||
|
};
|
||||||
|
|
||||||
|
macro_rules! be_read {
|
||||||
|
($input:ident, $fn:ident, $size:expr) => {{
|
||||||
|
decode_check_len!($input, $size);
|
||||||
|
let x = BigEndian::$fn($input);
|
||||||
|
Ok((&$input[$size..], x))
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_u8(input: &[u8]) -> Result<(&[u8], u8), AmqpParseError> {
|
||||||
|
decode_check_len!(input, 1);
|
||||||
|
Ok((&input[1..], input[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_i8(input: &[u8]) -> Result<(&[u8], i8), AmqpParseError> {
|
||||||
|
decode_check_len!(input, 1);
|
||||||
|
Ok((&input[1..], input[0] as i8))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_bytes_u8(input: &[u8]) -> Result<(&[u8], &[u8]), AmqpParseError> {
|
||||||
|
let (input, len) = read_u8(input)?;
|
||||||
|
let len = len as usize;
|
||||||
|
decode_check_len!(input, len);
|
||||||
|
let (bytes, input) = input.split_at(len);
|
||||||
|
Ok((input, bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_bytes_u32(input: &[u8]) -> Result<(&[u8], &[u8]), AmqpParseError> {
|
||||||
|
let result: Result<(&[u8], u32), AmqpParseError> = be_read!(input, read_u32, 4);
|
||||||
|
let (input, len) = result?;
|
||||||
|
let len = len as usize;
|
||||||
|
decode_check_len!(input, len);
|
||||||
|
let (bytes, input) = input.split_at(len);
|
||||||
|
Ok((input, bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! validate_code {
|
||||||
|
($fmt:ident, $code:expr) => {
|
||||||
|
if $fmt != $code {
|
||||||
|
return Err(AmqpParseError::InvalidFormatCode($fmt));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for bool {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
match fmt {
|
||||||
|
codec::FORMATCODE_BOOLEAN => read_u8(input).map(|(i, o)| (i, o != 0)),
|
||||||
|
codec::FORMATCODE_BOOLEAN_TRUE => Ok((input, true)),
|
||||||
|
codec::FORMATCODE_BOOLEAN_FALSE => Ok((input, false)),
|
||||||
|
_ => Err(AmqpParseError::InvalidFormatCode(fmt)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for u8 {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
validate_code!(fmt, codec::FORMATCODE_UBYTE);
|
||||||
|
read_u8(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for u16 {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
validate_code!(fmt, codec::FORMATCODE_USHORT);
|
||||||
|
be_read!(input, read_u16, 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for u32 {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
match fmt {
|
||||||
|
codec::FORMATCODE_UINT => be_read!(input, read_u32, 4),
|
||||||
|
codec::FORMATCODE_SMALLUINT => read_u8(input).map(|(i, o)| (i, u32::from(o))),
|
||||||
|
codec::FORMATCODE_UINT_0 => Ok((input, 0)),
|
||||||
|
_ => Err(AmqpParseError::InvalidFormatCode(fmt)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for u64 {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
match fmt {
|
||||||
|
codec::FORMATCODE_ULONG => be_read!(input, read_u64, 8),
|
||||||
|
codec::FORMATCODE_SMALLULONG => read_u8(input).map(|(i, o)| (i, u64::from(o))),
|
||||||
|
codec::FORMATCODE_ULONG_0 => Ok((input, 0)),
|
||||||
|
_ => Err(AmqpParseError::InvalidFormatCode(fmt)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for i8 {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
validate_code!(fmt, codec::FORMATCODE_BYTE);
|
||||||
|
read_i8(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for i16 {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
validate_code!(fmt, codec::FORMATCODE_SHORT);
|
||||||
|
be_read!(input, read_i16, 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for i32 {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
match fmt {
|
||||||
|
codec::FORMATCODE_INT => be_read!(input, read_i32, 4),
|
||||||
|
codec::FORMATCODE_SMALLINT => read_i8(input).map(|(i, o)| (i, i32::from(o))),
|
||||||
|
_ => Err(AmqpParseError::InvalidFormatCode(fmt)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for i64 {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
match fmt {
|
||||||
|
codec::FORMATCODE_LONG => be_read!(input, read_i64, 8),
|
||||||
|
codec::FORMATCODE_SMALLLONG => read_i8(input).map(|(i, o)| (i, i64::from(o))),
|
||||||
|
_ => Err(AmqpParseError::InvalidFormatCode(fmt)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for f32 {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
validate_code!(fmt, codec::FORMATCODE_FLOAT);
|
||||||
|
be_read!(input, read_f32, 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for f64 {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
validate_code!(fmt, codec::FORMATCODE_DOUBLE);
|
||||||
|
be_read!(input, read_f64, 8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for char {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
validate_code!(fmt, codec::FORMATCODE_CHAR);
|
||||||
|
let result: Result<(&[u8], u32), AmqpParseError> = be_read!(input, read_u32, 4);
|
||||||
|
let (i, o) = result?;
|
||||||
|
if let Some(c) = char::from_u32(o) {
|
||||||
|
Ok((i, c))
|
||||||
|
} else {
|
||||||
|
Err(AmqpParseError::InvalidChar(o))
|
||||||
|
} // todo: replace with CharTryFromError once try_from is stabilized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for DateTime<Utc> {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
validate_code!(fmt, codec::FORMATCODE_TIMESTAMP);
|
||||||
|
be_read!(input, read_i64, 8).map(|(i, o)| (i, datetime_from_millis(o)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for Uuid {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
validate_code!(fmt, codec::FORMATCODE_UUID);
|
||||||
|
decode_check_len!(input, 16);
|
||||||
|
let uuid = Uuid::from_slice(&input[..16])?;
|
||||||
|
Ok((&input[16..], uuid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for Bytes {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
match fmt {
|
||||||
|
codec::FORMATCODE_BINARY8 => {
|
||||||
|
read_bytes_u8(input).map(|(i, o)| (i, Bytes::copy_from_slice(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_BINARY32 => {
|
||||||
|
read_bytes_u32(input).map(|(i, o)| (i, Bytes::copy_from_slice(o)))
|
||||||
|
}
|
||||||
|
_ => Err(AmqpParseError::InvalidFormatCode(fmt)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for ByteString {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
match fmt {
|
||||||
|
codec::FORMATCODE_STRING8 => {
|
||||||
|
let (input, bytes) = read_bytes_u8(input)?;
|
||||||
|
Ok((input, ByteString::try_from(bytes)?))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_STRING32 => {
|
||||||
|
let (input, bytes) = read_bytes_u32(input)?;
|
||||||
|
Ok((input, ByteString::try_from(bytes)?))
|
||||||
|
}
|
||||||
|
_ => Err(AmqpParseError::InvalidFormatCode(fmt)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for Str {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
match fmt {
|
||||||
|
codec::FORMATCODE_STRING8 => {
|
||||||
|
let (input, bytes) = read_bytes_u8(input)?;
|
||||||
|
Ok((input, Str::from_str(str::from_utf8(bytes)?)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_STRING32 => {
|
||||||
|
let (input, bytes) = read_bytes_u32(input)?;
|
||||||
|
Ok((input, Str::from_str(str::from_utf8(bytes)?)))
|
||||||
|
}
|
||||||
|
_ => Err(AmqpParseError::InvalidFormatCode(fmt)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for Symbol {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
match fmt {
|
||||||
|
codec::FORMATCODE_SYMBOL8 => {
|
||||||
|
let (input, bytes) = read_bytes_u8(input)?;
|
||||||
|
Ok((input, Symbol::from_slice(str::from_utf8(bytes)?)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_SYMBOL32 => {
|
||||||
|
let (input, bytes) = read_bytes_u32(input)?;
|
||||||
|
Ok((input, Symbol::from_slice(str::from_utf8(bytes)?)))
|
||||||
|
}
|
||||||
|
_ => Err(AmqpParseError::InvalidFormatCode(fmt)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArrayDecode for Symbol {
|
||||||
|
fn array_decode(input: &[u8]) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
let (input, bytes) = read_bytes_u32(input)?;
|
||||||
|
Ok((input, Symbol::from_slice(str::from_utf8(bytes)?)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: Decode + Eq + Hash, V: Decode, S: BuildHasher + Default> DecodeFormatted
|
||||||
|
for HashMap<K, V, S>
|
||||||
|
{
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
let (input, header) = decode_map_header(input, fmt)?;
|
||||||
|
let mut map_input = &input[..header.size as usize];
|
||||||
|
let count = header.count / 2;
|
||||||
|
let mut map: HashMap<K, V, S> =
|
||||||
|
HashMap::with_capacity_and_hasher(count as usize, Default::default());
|
||||||
|
for _ in 0..count {
|
||||||
|
let (input1, key) = K::decode(map_input)?;
|
||||||
|
let (input2, value) = V::decode(input1)?;
|
||||||
|
map_input = input2;
|
||||||
|
map.insert(key, value); // todo: ensure None returned?
|
||||||
|
}
|
||||||
|
// todo: validate map_input is empty
|
||||||
|
Ok((&input[header.size as usize..], map))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: DecodeFormatted> DecodeFormatted for Vec<T> {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
let (input, header) = decode_array_header(input, fmt)?;
|
||||||
|
let item_fmt = input[0]; // todo: support descriptor
|
||||||
|
let mut input = &input[1..];
|
||||||
|
let mut result: Vec<T> = Vec::with_capacity(header.count as usize);
|
||||||
|
for _ in 0..header.count {
|
||||||
|
let (new_input, decoded) = T::decode_with_format(input, item_fmt)?;
|
||||||
|
result.push(decoded);
|
||||||
|
input = new_input;
|
||||||
|
}
|
||||||
|
Ok((input, result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for VecSymbolMap {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
let (input, header) = decode_map_header(input, fmt)?;
|
||||||
|
let mut map_input = &input[..header.size as usize];
|
||||||
|
let count = header.count / 2;
|
||||||
|
let mut map = Vec::with_capacity(count as usize);
|
||||||
|
for _ in 0..count {
|
||||||
|
let (input1, key) = Symbol::decode(map_input)?;
|
||||||
|
let (input2, value) = Variant::decode(input1)?;
|
||||||
|
map_input = input2;
|
||||||
|
map.push((key, value)); // todo: ensure None returned?
|
||||||
|
}
|
||||||
|
// todo: validate map_input is empty
|
||||||
|
Ok((&input[header.size as usize..], VecSymbolMap(map)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for VecStringMap {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
let (input, header) = decode_map_header(input, fmt)?;
|
||||||
|
let mut map_input = &input[..header.size as usize];
|
||||||
|
let count = header.count / 2;
|
||||||
|
let mut map = Vec::with_capacity(count as usize);
|
||||||
|
for _ in 0..count {
|
||||||
|
let (input1, key) = Str::decode(map_input)?;
|
||||||
|
let (input2, value) = Variant::decode(input1)?;
|
||||||
|
map_input = input2;
|
||||||
|
map.push((key, value)); // todo: ensure None returned?
|
||||||
|
}
|
||||||
|
// todo: validate map_input is empty
|
||||||
|
Ok((&input[header.size as usize..], VecStringMap(map)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ArrayDecode + DecodeFormatted> DecodeFormatted for Multiple<T> {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
match fmt {
|
||||||
|
codec::FORMATCODE_ARRAY8 | codec::FORMATCODE_ARRAY32 => {
|
||||||
|
let (input, items) = Vec::<T>::decode_with_format(input, fmt)?;
|
||||||
|
Ok((input, Multiple(items)))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let (input, item) = T::decode_with_format(input, fmt)?;
|
||||||
|
Ok((input, Multiple(vec![item])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for List {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
let (mut input, header) = decode_list_header(input, fmt)?;
|
||||||
|
let mut result: Vec<Variant> = Vec::with_capacity(header.count as usize);
|
||||||
|
for _ in 0..header.count {
|
||||||
|
let (new_input, decoded) = Variant::decode(input)?;
|
||||||
|
result.push(decoded);
|
||||||
|
input = new_input;
|
||||||
|
}
|
||||||
|
Ok((input, List(result)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for Variant {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
match fmt {
|
||||||
|
codec::FORMATCODE_NULL => Ok((input, Variant::Null)),
|
||||||
|
codec::FORMATCODE_BOOLEAN => {
|
||||||
|
bool::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Boolean(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_BOOLEAN_FALSE => Ok((input, Variant::Boolean(false))),
|
||||||
|
codec::FORMATCODE_BOOLEAN_TRUE => Ok((input, Variant::Boolean(true))),
|
||||||
|
codec::FORMATCODE_UINT_0 => Ok((input, Variant::Uint(0))),
|
||||||
|
codec::FORMATCODE_ULONG_0 => Ok((input, Variant::Ulong(0))),
|
||||||
|
codec::FORMATCODE_UBYTE => {
|
||||||
|
u8::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Ubyte(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_USHORT => {
|
||||||
|
u16::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Ushort(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_UINT => {
|
||||||
|
u32::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Uint(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_ULONG => {
|
||||||
|
u64::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Ulong(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_BYTE => {
|
||||||
|
i8::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Byte(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_SHORT => {
|
||||||
|
i16::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Short(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_INT => {
|
||||||
|
i32::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Int(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_LONG => {
|
||||||
|
i64::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Long(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_SMALLUINT => {
|
||||||
|
u32::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Uint(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_SMALLULONG => {
|
||||||
|
u64::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Ulong(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_SMALLINT => {
|
||||||
|
i32::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Int(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_SMALLLONG => {
|
||||||
|
i64::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Long(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_FLOAT => f32::decode_with_format(input, fmt)
|
||||||
|
.map(|(i, o)| (i, Variant::Float(OrderedFloat(o)))),
|
||||||
|
codec::FORMATCODE_DOUBLE => f64::decode_with_format(input, fmt)
|
||||||
|
.map(|(i, o)| (i, Variant::Double(OrderedFloat(o)))),
|
||||||
|
// codec::FORMATCODE_DECIMAL32 => x::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Decimal(o))),
|
||||||
|
// codec::FORMATCODE_DECIMAL64 => x::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Decimal(o))),
|
||||||
|
// codec::FORMATCODE_DECIMAL128 => x::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Decimal(o))),
|
||||||
|
codec::FORMATCODE_CHAR => {
|
||||||
|
char::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Char(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_TIMESTAMP => DateTime::<Utc>::decode_with_format(input, fmt)
|
||||||
|
.map(|(i, o)| (i, Variant::Timestamp(o))),
|
||||||
|
codec::FORMATCODE_UUID => {
|
||||||
|
Uuid::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Uuid(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_BINARY8 => {
|
||||||
|
Bytes::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Binary(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_BINARY32 => {
|
||||||
|
Bytes::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Binary(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_STRING8 => ByteString::decode_with_format(input, fmt)
|
||||||
|
.map(|(i, o)| (i, Variant::String(o.into()))),
|
||||||
|
codec::FORMATCODE_STRING32 => ByteString::decode_with_format(input, fmt)
|
||||||
|
.map(|(i, o)| (i, Variant::String(o.into()))),
|
||||||
|
codec::FORMATCODE_SYMBOL8 => {
|
||||||
|
Symbol::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Symbol(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_SYMBOL32 => {
|
||||||
|
Symbol::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Symbol(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_LIST0 => Ok((input, Variant::List(List(vec![])))),
|
||||||
|
codec::FORMATCODE_LIST8 => {
|
||||||
|
List::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::List(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_LIST32 => {
|
||||||
|
List::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::List(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_MAP8 => FxHashMap::<Variant, Variant>::decode_with_format(input, fmt)
|
||||||
|
.map(|(i, o)| (i, Variant::Map(VariantMap::new(o)))),
|
||||||
|
codec::FORMATCODE_MAP32 => {
|
||||||
|
FxHashMap::<Variant, Variant>::decode_with_format(input, fmt)
|
||||||
|
.map(|(i, o)| (i, Variant::Map(VariantMap::new(o))))
|
||||||
|
}
|
||||||
|
// codec::FORMATCODE_ARRAY8 => Vec::<Variant>::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Array(o))),
|
||||||
|
// codec::FORMATCODE_ARRAY32 => Vec::<Variant>::decode_with_format(input, fmt).map(|(i, o)| (i, Variant::Array(o))),
|
||||||
|
codec::FORMATCODE_DESCRIBED => {
|
||||||
|
let (input, descriptor) = Descriptor::decode(input)?;
|
||||||
|
let (input, value) = Variant::decode(input)?;
|
||||||
|
Ok((input, Variant::Described((descriptor, Box::new(value)))))
|
||||||
|
}
|
||||||
|
_ => Err(AmqpParseError::InvalidFormatCode(fmt)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: DecodeFormatted> DecodeFormatted for Option<T> {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
match fmt {
|
||||||
|
codec::FORMATCODE_NULL => Ok((input, None)),
|
||||||
|
_ => T::decode_with_format(input, fmt).map(|(i, o)| (i, Some(o))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for Descriptor {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
match fmt {
|
||||||
|
codec::FORMATCODE_SMALLULONG => {
|
||||||
|
u64::decode_with_format(input, fmt).map(|(i, o)| (i, Descriptor::Ulong(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_ULONG => {
|
||||||
|
u64::decode_with_format(input, fmt).map(|(i, o)| (i, Descriptor::Ulong(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_SYMBOL8 => {
|
||||||
|
Symbol::decode_with_format(input, fmt).map(|(i, o)| (i, Descriptor::Symbol(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_SYMBOL32 => {
|
||||||
|
Symbol::decode_with_format(input, fmt).map(|(i, o)| (i, Descriptor::Symbol(o)))
|
||||||
|
}
|
||||||
|
_ => Err(AmqpParseError::InvalidFormatCode(fmt)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode for AmqpFrame {
|
||||||
|
fn decode(input: &[u8]) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
let (input, channel_id) = decode_frame_header(input, framing::FRAME_TYPE_AMQP)?;
|
||||||
|
let (input, performative) = protocol::Frame::decode(input)?;
|
||||||
|
Ok((input, AmqpFrame::new(channel_id, performative)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode for SaslFrame {
|
||||||
|
fn decode(input: &[u8]) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
let (input, _) = decode_frame_header(input, framing::FRAME_TYPE_SASL)?;
|
||||||
|
let (input, frame) = protocol::SaslFrameBody::decode(input)?;
|
||||||
|
Ok((input, SaslFrame { body: frame }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_frame_header(
|
||||||
|
input: &[u8],
|
||||||
|
expected_frame_type: u8,
|
||||||
|
) -> Result<(&[u8], u16), AmqpParseError> {
|
||||||
|
decode_check_len!(input, 4);
|
||||||
|
let doff = input[0];
|
||||||
|
let frame_type = input[1];
|
||||||
|
if frame_type != expected_frame_type {
|
||||||
|
return Err(AmqpParseError::UnexpectedFrameType(frame_type));
|
||||||
|
}
|
||||||
|
|
||||||
|
let channel_id = BigEndian::read_u16(&input[2..]);
|
||||||
|
let doff = doff as usize * 4;
|
||||||
|
if doff < HEADER_LEN {
|
||||||
|
return Err(AmqpParseError::InvalidSize);
|
||||||
|
}
|
||||||
|
let ext_header_len = doff - HEADER_LEN;
|
||||||
|
decode_check_len!(input, ext_header_len + 4);
|
||||||
|
let input = &input[ext_header_len + 4..]; // skipping remaining two header bytes and ext header
|
||||||
|
Ok((input, channel_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_array_header(input: &[u8], fmt: u8) -> Result<(&[u8], CompoundHeader), AmqpParseError> {
|
||||||
|
match fmt {
|
||||||
|
codec::FORMATCODE_ARRAY8 => decode_compound8(input),
|
||||||
|
codec::FORMATCODE_ARRAY32 => decode_compound32(input),
|
||||||
|
_ => Err(AmqpParseError::InvalidFormatCode(fmt)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn decode_list_header(
|
||||||
|
input: &[u8],
|
||||||
|
fmt: u8,
|
||||||
|
) -> Result<(&[u8], CompoundHeader), AmqpParseError> {
|
||||||
|
match fmt {
|
||||||
|
codec::FORMATCODE_LIST0 => Ok((input, CompoundHeader::empty())),
|
||||||
|
codec::FORMATCODE_LIST8 => decode_compound8(input),
|
||||||
|
codec::FORMATCODE_LIST32 => decode_compound32(input),
|
||||||
|
_ => Err(AmqpParseError::InvalidFormatCode(fmt)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn decode_map_header(
|
||||||
|
input: &[u8],
|
||||||
|
fmt: u8,
|
||||||
|
) -> Result<(&[u8], CompoundHeader), AmqpParseError> {
|
||||||
|
match fmt {
|
||||||
|
codec::FORMATCODE_MAP8 => decode_compound8(input),
|
||||||
|
codec::FORMATCODE_MAP32 => decode_compound32(input),
|
||||||
|
_ => Err(AmqpParseError::InvalidFormatCode(fmt)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_compound8(input: &[u8]) -> Result<(&[u8], CompoundHeader), AmqpParseError> {
|
||||||
|
decode_check_len!(input, 2);
|
||||||
|
let size = input[0] - 1; // -1 for 1 byte count
|
||||||
|
let count = input[1];
|
||||||
|
Ok((
|
||||||
|
&input[2..],
|
||||||
|
CompoundHeader {
|
||||||
|
size: u32::from(size),
|
||||||
|
count: u32::from(count),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_compound32(input: &[u8]) -> Result<(&[u8], CompoundHeader), AmqpParseError> {
|
||||||
|
decode_check_len!(input, 8);
|
||||||
|
let size = BigEndian::read_u32(input) - 4; // -4 for 4 byte count
|
||||||
|
let count = BigEndian::read_u32(&input[4..]);
|
||||||
|
Ok((&input[8..], CompoundHeader { size, count }))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn datetime_from_millis(millis: i64) -> DateTime<Utc> {
|
||||||
|
let seconds = millis / 1000;
|
||||||
|
if seconds < 0 {
|
||||||
|
// In order to handle time before 1970 correctly, we need to subtract a second
|
||||||
|
// and use the nanoseconds field to add it back. This is a result of the nanoseconds
|
||||||
|
// parameter being u32
|
||||||
|
let nanoseconds = ((1000 + (millis - (seconds * 1000))) * 1_000_000).abs() as u32;
|
||||||
|
Utc.timestamp(seconds - 1, nanoseconds)
|
||||||
|
} else {
|
||||||
|
let nanoseconds = ((millis - (seconds * 1000)) * 1_000_000).abs() as u32;
|
||||||
|
Utc.timestamp(seconds, nanoseconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::codec::Encode;
|
||||||
|
use bytes::{BufMut, BytesMut};
|
||||||
|
|
||||||
|
const LOREM: &str = include_str!("lorem.txt");
|
||||||
|
|
||||||
|
macro_rules! decode_tests {
|
||||||
|
($($name:ident: $kind:ident, $test:expr, $expected:expr,)*) => {
|
||||||
|
$(
|
||||||
|
#[test]
|
||||||
|
fn $name() {
|
||||||
|
let b1 = &mut BytesMut::with_capacity(($test).encoded_size());
|
||||||
|
($test).encode(b1);
|
||||||
|
assert_eq!($expected, unwrap_value($kind::decode(b1)));
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
decode_tests! {
|
||||||
|
ubyte: u8, 255_u8, 255_u8,
|
||||||
|
ushort: u16, 350_u16, 350_u16,
|
||||||
|
|
||||||
|
uint_zero: u32, 0_u32, 0_u32,
|
||||||
|
uint_small: u32, 128_u32, 128_u32,
|
||||||
|
uint_big: u32, 2147483647_u32, 2147483647_u32,
|
||||||
|
|
||||||
|
ulong_zero: u64, 0_u64, 0_u64,
|
||||||
|
ulong_small: u64, 128_u64, 128_u64,
|
||||||
|
uulong_big: u64, 2147483649_u64, 2147483649_u64,
|
||||||
|
|
||||||
|
byte: i8, -128_i8, -128_i8,
|
||||||
|
short: i16, -255_i16, -255_i16,
|
||||||
|
|
||||||
|
int_zero: i32, 0_i32, 0_i32,
|
||||||
|
int_small: i32, -50000_i32, -50000_i32,
|
||||||
|
int_neg: i32, -128_i32, -128_i32,
|
||||||
|
|
||||||
|
long_zero: i64, 0_i64, 0_i64,
|
||||||
|
long_big: i64, -2147483647_i64, -2147483647_i64,
|
||||||
|
long_small: i64, -128_i64, -128_i64,
|
||||||
|
|
||||||
|
float: f32, 1.234_f32, 1.234_f32,
|
||||||
|
double: f64, 1.234_f64, 1.234_f64,
|
||||||
|
|
||||||
|
test_char: char, '💯', '💯',
|
||||||
|
|
||||||
|
uuid: Uuid, Uuid::from_slice(&[4, 54, 67, 12, 43, 2, 98, 76, 32, 50, 87, 5, 1, 33, 43, 87]).expect("parse error"),
|
||||||
|
Uuid::parse_str("0436430c2b02624c2032570501212b57").expect("parse error"),
|
||||||
|
|
||||||
|
binary_short: Bytes, Bytes::from(&[4u8, 5u8][..]), Bytes::from(&[4u8, 5u8][..]),
|
||||||
|
binary_long: Bytes, Bytes::from(&[4u8; 500][..]), Bytes::from(&[4u8; 500][..]),
|
||||||
|
|
||||||
|
string_short: ByteString, ByteString::from("Hello there"), ByteString::from("Hello there"),
|
||||||
|
string_long: ByteString, ByteString::from(LOREM), ByteString::from(LOREM),
|
||||||
|
|
||||||
|
// symbol_short: Symbol, Symbol::from("Hello there"), Symbol::from("Hello there"),
|
||||||
|
// symbol_long: Symbol, Symbol::from(LOREM), Symbol::from(LOREM),
|
||||||
|
|
||||||
|
variant_ubyte: Variant, Variant::Ubyte(255_u8), Variant::Ubyte(255_u8),
|
||||||
|
variant_ushort: Variant, Variant::Ushort(350_u16), Variant::Ushort(350_u16),
|
||||||
|
|
||||||
|
variant_uint_zero: Variant, Variant::Uint(0_u32), Variant::Uint(0_u32),
|
||||||
|
variant_uint_small: Variant, Variant::Uint(128_u32), Variant::Uint(128_u32),
|
||||||
|
variant_uint_big: Variant, Variant::Uint(2147483647_u32), Variant::Uint(2147483647_u32),
|
||||||
|
|
||||||
|
variant_ulong_zero: Variant, Variant::Ulong(0_u64), Variant::Ulong(0_u64),
|
||||||
|
variant_ulong_small: Variant, Variant::Ulong(128_u64), Variant::Ulong(128_u64),
|
||||||
|
variant_ulong_big: Variant, Variant::Ulong(2147483649_u64), Variant::Ulong(2147483649_u64),
|
||||||
|
|
||||||
|
variant_byte: Variant, Variant::Byte(-128_i8), Variant::Byte(-128_i8),
|
||||||
|
variant_short: Variant, Variant::Short(-255_i16), Variant::Short(-255_i16),
|
||||||
|
|
||||||
|
variant_int_zero: Variant, Variant::Int(0_i32), Variant::Int(0_i32),
|
||||||
|
variant_int_small: Variant, Variant::Int(-50000_i32), Variant::Int(-50000_i32),
|
||||||
|
variant_int_neg: Variant, Variant::Int(-128_i32), Variant::Int(-128_i32),
|
||||||
|
|
||||||
|
variant_long_zero: Variant, Variant::Long(0_i64), Variant::Long(0_i64),
|
||||||
|
variant_long_big: Variant, Variant::Long(-2147483647_i64), Variant::Long(-2147483647_i64),
|
||||||
|
variant_long_small: Variant, Variant::Long(-128_i64), Variant::Long(-128_i64),
|
||||||
|
|
||||||
|
variant_float: Variant, Variant::Float(OrderedFloat(1.234_f32)), Variant::Float(OrderedFloat(1.234_f32)),
|
||||||
|
variant_double: Variant, Variant::Double(OrderedFloat(1.234_f64)), Variant::Double(OrderedFloat(1.234_f64)),
|
||||||
|
|
||||||
|
variant_char: Variant, Variant::Char('💯'), Variant::Char('💯'),
|
||||||
|
|
||||||
|
variant_uuid: Variant, Variant::Uuid(Uuid::from_slice(&[4, 54, 67, 12, 43, 2, 98, 76, 32, 50, 87, 5, 1, 33, 43, 87]).expect("parse error")),
|
||||||
|
Variant::Uuid(Uuid::parse_str("0436430c2b02624c2032570501212b57").expect("parse error")),
|
||||||
|
|
||||||
|
variant_binary_short: Variant, Variant::Binary(Bytes::from(&[4u8, 5u8][..])), Variant::Binary(Bytes::from(&[4u8, 5u8][..])),
|
||||||
|
variant_binary_long: Variant, Variant::Binary(Bytes::from(&[4u8; 500][..])), Variant::Binary(Bytes::from(&[4u8; 500][..])),
|
||||||
|
|
||||||
|
variant_string_short: Variant, Variant::String(ByteString::from("Hello there").into()), Variant::String(ByteString::from("Hello there").into()),
|
||||||
|
variant_string_long: Variant, Variant::String(ByteString::from(LOREM).into()), Variant::String(ByteString::from(LOREM).into()),
|
||||||
|
|
||||||
|
// variant_symbol_short: Variant, Variant::Symbol(Symbol::from("Hello there")), Variant::Symbol(Symbol::from("Hello there")),
|
||||||
|
// variant_symbol_long: Variant, Variant::Symbol(Symbol::from(LOREM)), Variant::Symbol(Symbol::from(LOREM)),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unwrap_value<T>(res: Result<(&[u8], T), AmqpParseError>) -> T {
|
||||||
|
let r = res.map(|(_i, o)| o);
|
||||||
|
assert!(r.is_ok());
|
||||||
|
r.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bool_true() {
|
||||||
|
let b1 = &mut BytesMut::with_capacity(0);
|
||||||
|
b1.put_u8(0x41);
|
||||||
|
assert_eq!(true, unwrap_value(bool::decode(b1)));
|
||||||
|
|
||||||
|
let b2 = &mut BytesMut::with_capacity(0);
|
||||||
|
b2.put_u8(0x56);
|
||||||
|
b2.put_u8(0x01);
|
||||||
|
assert_eq!(true, unwrap_value(bool::decode(b2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bool_false() {
|
||||||
|
let b1 = &mut BytesMut::with_capacity(0);
|
||||||
|
b1.put_u8(0x42u8);
|
||||||
|
assert_eq!(false, unwrap_value(bool::decode(b1)));
|
||||||
|
|
||||||
|
let b2 = &mut BytesMut::with_capacity(0);
|
||||||
|
b2.put_u8(0x56);
|
||||||
|
b2.put_u8(0x00);
|
||||||
|
assert_eq!(false, unwrap_value(bool::decode(b2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// UTC with a precision of milliseconds. For example, 1311704463521
|
||||||
|
/// represents the moment 2011-07-26T18:21:03.521Z.
|
||||||
|
#[test]
|
||||||
|
fn test_timestamp() {
|
||||||
|
let b1 = &mut BytesMut::with_capacity(0);
|
||||||
|
let datetime = Utc.ymd(2011, 7, 26).and_hms_milli(18, 21, 3, 521);
|
||||||
|
datetime.encode(b1);
|
||||||
|
|
||||||
|
let expected = Utc.ymd(2011, 7, 26).and_hms_milli(18, 21, 3, 521);
|
||||||
|
assert_eq!(expected, unwrap_value(DateTime::<Utc>::decode(b1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_timestamp_pre_unix() {
|
||||||
|
let b1 = &mut BytesMut::with_capacity(0);
|
||||||
|
let datetime = Utc.ymd(1968, 7, 26).and_hms_milli(18, 21, 3, 521);
|
||||||
|
datetime.encode(b1);
|
||||||
|
|
||||||
|
let expected = Utc.ymd(1968, 7, 26).and_hms_milli(18, 21, 3, 521);
|
||||||
|
assert_eq!(expected, unwrap_value(DateTime::<Utc>::decode(b1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn variant_null() {
|
||||||
|
let mut b = BytesMut::with_capacity(0);
|
||||||
|
Variant::Null.encode(&mut b);
|
||||||
|
let t = unwrap_value(Variant::decode(&mut b));
|
||||||
|
assert_eq!(Variant::Null, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn variant_bool_true() {
|
||||||
|
let b1 = &mut BytesMut::with_capacity(0);
|
||||||
|
b1.put_u8(0x41);
|
||||||
|
assert_eq!(Variant::Boolean(true), unwrap_value(Variant::decode(b1)));
|
||||||
|
|
||||||
|
let b2 = &mut BytesMut::with_capacity(0);
|
||||||
|
b2.put_u8(0x56);
|
||||||
|
b2.put_u8(0x01);
|
||||||
|
assert_eq!(Variant::Boolean(true), unwrap_value(Variant::decode(b2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn variant_bool_false() {
|
||||||
|
let b1 = &mut BytesMut::with_capacity(0);
|
||||||
|
b1.put_u8(0x42u8);
|
||||||
|
assert_eq!(Variant::Boolean(false), unwrap_value(Variant::decode(b1)));
|
||||||
|
|
||||||
|
let b2 = &mut BytesMut::with_capacity(0);
|
||||||
|
b2.put_u8(0x56);
|
||||||
|
b2.put_u8(0x00);
|
||||||
|
assert_eq!(Variant::Boolean(false), unwrap_value(Variant::decode(b2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// UTC with a precision of milliseconds. For example, 1311704463521
|
||||||
|
/// represents the moment 2011-07-26T18:21:03.521Z.
|
||||||
|
#[test]
|
||||||
|
fn variant_timestamp() {
|
||||||
|
let b1 = &mut BytesMut::with_capacity(0);
|
||||||
|
let datetime = Utc.ymd(2011, 7, 26).and_hms_milli(18, 21, 3, 521);
|
||||||
|
Variant::Timestamp(datetime).encode(b1);
|
||||||
|
|
||||||
|
let expected = Utc.ymd(2011, 7, 26).and_hms_milli(18, 21, 3, 521);
|
||||||
|
assert_eq!(
|
||||||
|
Variant::Timestamp(expected),
|
||||||
|
unwrap_value(Variant::decode(b1))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn variant_timestamp_pre_unix() {
|
||||||
|
let b1 = &mut BytesMut::with_capacity(0);
|
||||||
|
let datetime = Utc.ymd(1968, 7, 26).and_hms_milli(18, 21, 3, 521);
|
||||||
|
Variant::Timestamp(datetime).encode(b1);
|
||||||
|
|
||||||
|
let expected = Utc.ymd(1968, 7, 26).and_hms_milli(18, 21, 3, 521);
|
||||||
|
assert_eq!(
|
||||||
|
Variant::Timestamp(expected),
|
||||||
|
unwrap_value(Variant::decode(b1))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn option_i8() {
|
||||||
|
let b1 = &mut BytesMut::with_capacity(0);
|
||||||
|
Some(42i8).encode(b1);
|
||||||
|
|
||||||
|
assert_eq!(Some(42), unwrap_value(Option::<i8>::decode(b1)));
|
||||||
|
|
||||||
|
let b2 = &mut BytesMut::with_capacity(0);
|
||||||
|
let o1: Option<i8> = None;
|
||||||
|
o1.encode(b2);
|
||||||
|
|
||||||
|
assert_eq!(None, unwrap_value(Option::<i8>::decode(b2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn option_string() {
|
||||||
|
let b1 = &mut BytesMut::with_capacity(0);
|
||||||
|
Some(ByteString::from("hello")).encode(b1);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Some(ByteString::from("hello")),
|
||||||
|
unwrap_value(Option::<ByteString>::decode(b1))
|
||||||
|
);
|
||||||
|
|
||||||
|
let b2 = &mut BytesMut::with_capacity(0);
|
||||||
|
let o1: Option<ByteString> = None;
|
||||||
|
o1.encode(b2);
|
||||||
|
|
||||||
|
assert_eq!(None, unwrap_value(Option::<ByteString>::decode(b2)));
|
||||||
|
}
|
||||||
|
}
|
786
actix-amqp/codec/src/codec/encode.rs
Executable file
786
actix-amqp/codec/src/codec/encode.rs
Executable file
@ -0,0 +1,786 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::hash::{BuildHasher, Hash};
|
||||||
|
use std::{i8, u8};
|
||||||
|
|
||||||
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
|
use bytestring::ByteString;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::codec::{self, ArrayEncode, Encode};
|
||||||
|
use crate::framing::{self, AmqpFrame, SaslFrame};
|
||||||
|
use crate::types::{
|
||||||
|
Descriptor, List, Multiple, StaticSymbol, Str, Symbol, Variant, VecStringMap, VecSymbolMap,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn encode_null(buf: &mut BytesMut) {
|
||||||
|
buf.put_u8(codec::FORMATCODE_NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FixedEncode {}
|
||||||
|
|
||||||
|
impl<T: FixedEncode + ArrayEncode> Encode for T {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
self.array_encoded_size() + 1
|
||||||
|
}
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
buf.put_u8(T::ARRAY_FORMAT_CODE);
|
||||||
|
self.array_encode(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for bool {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
buf.put_u8(if *self {
|
||||||
|
codec::FORMATCODE_BOOLEAN_TRUE
|
||||||
|
} else {
|
||||||
|
codec::FORMATCODE_BOOLEAN_FALSE
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ArrayEncode for bool {
|
||||||
|
const ARRAY_FORMAT_CODE: u8 = codec::FORMATCODE_BOOLEAN;
|
||||||
|
fn array_encoded_size(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
fn array_encode(&self, buf: &mut BytesMut) {
|
||||||
|
buf.put_u8(if *self { 1 } else { 0 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FixedEncode for u8 {}
|
||||||
|
impl ArrayEncode for u8 {
|
||||||
|
const ARRAY_FORMAT_CODE: u8 = codec::FORMATCODE_UBYTE;
|
||||||
|
fn array_encoded_size(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
fn array_encode(&self, buf: &mut BytesMut) {
|
||||||
|
buf.put_u8(*self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FixedEncode for u16 {}
|
||||||
|
impl ArrayEncode for u16 {
|
||||||
|
const ARRAY_FORMAT_CODE: u8 = codec::FORMATCODE_USHORT;
|
||||||
|
fn array_encoded_size(&self) -> usize {
|
||||||
|
2
|
||||||
|
}
|
||||||
|
fn array_encode(&self, buf: &mut BytesMut) {
|
||||||
|
buf.put_u16(*self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for u32 {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
if *self == 0 {
|
||||||
|
1
|
||||||
|
} else if *self > u32::from(u8::MAX) {
|
||||||
|
5
|
||||||
|
} else {
|
||||||
|
2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
if *self == 0 {
|
||||||
|
buf.put_u8(codec::FORMATCODE_UINT_0)
|
||||||
|
} else if *self > u32::from(u8::MAX) {
|
||||||
|
buf.put_u8(codec::FORMATCODE_UINT);
|
||||||
|
buf.put_u32(*self);
|
||||||
|
} else {
|
||||||
|
buf.put_u8(codec::FORMATCODE_SMALLUINT);
|
||||||
|
buf.put_u8(*self as u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ArrayEncode for u32 {
|
||||||
|
const ARRAY_FORMAT_CODE: u8 = codec::FORMATCODE_UINT;
|
||||||
|
fn array_encoded_size(&self) -> usize {
|
||||||
|
4
|
||||||
|
}
|
||||||
|
fn array_encode(&self, buf: &mut BytesMut) {
|
||||||
|
buf.put_u32(*self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for u64 {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
if *self == 0 {
|
||||||
|
1
|
||||||
|
} else if *self > u64::from(u8::MAX) {
|
||||||
|
9
|
||||||
|
} else {
|
||||||
|
2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
if *self == 0 {
|
||||||
|
buf.put_u8(codec::FORMATCODE_ULONG_0)
|
||||||
|
} else if *self > u64::from(u8::MAX) {
|
||||||
|
buf.put_u8(codec::FORMATCODE_ULONG);
|
||||||
|
buf.put_u64(*self);
|
||||||
|
} else {
|
||||||
|
buf.put_u8(codec::FORMATCODE_SMALLULONG);
|
||||||
|
buf.put_u8(*self as u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArrayEncode for u64 {
|
||||||
|
const ARRAY_FORMAT_CODE: u8 = codec::FORMATCODE_ULONG;
|
||||||
|
fn array_encoded_size(&self) -> usize {
|
||||||
|
8
|
||||||
|
}
|
||||||
|
fn array_encode(&self, buf: &mut BytesMut) {
|
||||||
|
buf.put_u64(*self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FixedEncode for i8 {}
|
||||||
|
|
||||||
|
impl ArrayEncode for i8 {
|
||||||
|
const ARRAY_FORMAT_CODE: u8 = codec::FORMATCODE_BYTE;
|
||||||
|
fn array_encoded_size(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
fn array_encode(&self, buf: &mut BytesMut) {
|
||||||
|
buf.put_i8(*self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FixedEncode for i16 {}
|
||||||
|
|
||||||
|
impl ArrayEncode for i16 {
|
||||||
|
const ARRAY_FORMAT_CODE: u8 = codec::FORMATCODE_SHORT;
|
||||||
|
fn array_encoded_size(&self) -> usize {
|
||||||
|
2
|
||||||
|
}
|
||||||
|
fn array_encode(&self, buf: &mut BytesMut) {
|
||||||
|
buf.put_i16(*self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for i32 {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
if *self > i32::from(i8::MAX) || *self < i32::from(i8::MIN) {
|
||||||
|
5
|
||||||
|
} else {
|
||||||
|
2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
if *self > i32::from(i8::MAX) || *self < i32::from(i8::MIN) {
|
||||||
|
buf.put_u8(codec::FORMATCODE_INT);
|
||||||
|
buf.put_i32(*self);
|
||||||
|
} else {
|
||||||
|
buf.put_u8(codec::FORMATCODE_SMALLINT);
|
||||||
|
buf.put_i8(*self as i8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArrayEncode for i32 {
|
||||||
|
const ARRAY_FORMAT_CODE: u8 = codec::FORMATCODE_INT;
|
||||||
|
|
||||||
|
fn array_encoded_size(&self) -> usize {
|
||||||
|
4
|
||||||
|
}
|
||||||
|
|
||||||
|
fn array_encode(&self, buf: &mut BytesMut) {
|
||||||
|
buf.put_i32(*self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for i64 {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
if *self > i64::from(i8::MAX) || *self < i64::from(i8::MIN) {
|
||||||
|
9
|
||||||
|
} else {
|
||||||
|
2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
if *self > i64::from(i8::MAX) || *self < i64::from(i8::MIN) {
|
||||||
|
buf.put_u8(codec::FORMATCODE_LONG);
|
||||||
|
buf.put_i64(*self);
|
||||||
|
} else {
|
||||||
|
buf.put_u8(codec::FORMATCODE_SMALLLONG);
|
||||||
|
buf.put_i8(*self as i8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArrayEncode for i64 {
|
||||||
|
const ARRAY_FORMAT_CODE: u8 = codec::FORMATCODE_LONG;
|
||||||
|
fn array_encoded_size(&self) -> usize {
|
||||||
|
8
|
||||||
|
}
|
||||||
|
fn array_encode(&self, buf: &mut BytesMut) {
|
||||||
|
buf.put_i64(*self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FixedEncode for f32 {}
|
||||||
|
|
||||||
|
impl ArrayEncode for f32 {
|
||||||
|
const ARRAY_FORMAT_CODE: u8 = codec::FORMATCODE_FLOAT;
|
||||||
|
|
||||||
|
fn array_encoded_size(&self) -> usize {
|
||||||
|
4
|
||||||
|
}
|
||||||
|
|
||||||
|
fn array_encode(&self, buf: &mut BytesMut) {
|
||||||
|
buf.put_f32(*self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FixedEncode for f64 {}
|
||||||
|
|
||||||
|
impl ArrayEncode for f64 {
|
||||||
|
const ARRAY_FORMAT_CODE: u8 = codec::FORMATCODE_DOUBLE;
|
||||||
|
fn array_encoded_size(&self) -> usize {
|
||||||
|
8
|
||||||
|
}
|
||||||
|
fn array_encode(&self, buf: &mut BytesMut) {
|
||||||
|
buf.put_f64(*self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FixedEncode for char {}
|
||||||
|
|
||||||
|
impl ArrayEncode for char {
|
||||||
|
const ARRAY_FORMAT_CODE: u8 = codec::FORMATCODE_CHAR;
|
||||||
|
fn array_encoded_size(&self) -> usize {
|
||||||
|
4
|
||||||
|
}
|
||||||
|
fn array_encode(&self, buf: &mut BytesMut) {
|
||||||
|
buf.put_u32(*self as u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FixedEncode for DateTime<Utc> {}
|
||||||
|
|
||||||
|
impl ArrayEncode for DateTime<Utc> {
|
||||||
|
const ARRAY_FORMAT_CODE: u8 = codec::FORMATCODE_TIMESTAMP;
|
||||||
|
fn array_encoded_size(&self) -> usize {
|
||||||
|
8
|
||||||
|
}
|
||||||
|
fn array_encode(&self, buf: &mut BytesMut) {
|
||||||
|
let timestamp = self.timestamp() * 1000 + i64::from(self.timestamp_subsec_millis());
|
||||||
|
buf.put_i64(timestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FixedEncode for Uuid {}
|
||||||
|
|
||||||
|
impl ArrayEncode for Uuid {
|
||||||
|
const ARRAY_FORMAT_CODE: u8 = codec::FORMATCODE_UUID;
|
||||||
|
fn array_encoded_size(&self) -> usize {
|
||||||
|
16
|
||||||
|
}
|
||||||
|
fn array_encode(&self, buf: &mut BytesMut) {
|
||||||
|
buf.extend_from_slice(self.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for Bytes {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
let length = self.len();
|
||||||
|
let size = if length > u8::MAX as usize { 5 } else { 2 };
|
||||||
|
size + length
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
let length = self.len();
|
||||||
|
if length > u8::MAX as usize {
|
||||||
|
buf.put_u8(codec::FORMATCODE_BINARY32);
|
||||||
|
buf.put_u32(length as u32);
|
||||||
|
} else {
|
||||||
|
buf.put_u8(codec::FORMATCODE_BINARY8);
|
||||||
|
buf.put_u8(length as u8);
|
||||||
|
}
|
||||||
|
buf.put_slice(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArrayEncode for Bytes {
|
||||||
|
const ARRAY_FORMAT_CODE: u8 = codec::FORMATCODE_BINARY32;
|
||||||
|
fn array_encoded_size(&self) -> usize {
|
||||||
|
4 + self.len()
|
||||||
|
}
|
||||||
|
fn array_encode(&self, buf: &mut BytesMut) {
|
||||||
|
buf.put_u32(self.len() as u32);
|
||||||
|
buf.put_slice(&self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for ByteString {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
let length = self.len();
|
||||||
|
let size = if length > u8::MAX as usize { 5 } else { 2 };
|
||||||
|
size + length
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
let length = self.len();
|
||||||
|
if length > u8::MAX as usize {
|
||||||
|
buf.put_u8(codec::FORMATCODE_STRING32);
|
||||||
|
buf.put_u32(length as u32);
|
||||||
|
} else {
|
||||||
|
buf.put_u8(codec::FORMATCODE_STRING8);
|
||||||
|
buf.put_u8(length as u8);
|
||||||
|
}
|
||||||
|
buf.put_slice(self.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ArrayEncode for ByteString {
|
||||||
|
const ARRAY_FORMAT_CODE: u8 = codec::FORMATCODE_STRING32;
|
||||||
|
fn array_encoded_size(&self) -> usize {
|
||||||
|
4 + self.len()
|
||||||
|
}
|
||||||
|
fn array_encode(&self, buf: &mut BytesMut) {
|
||||||
|
buf.put_u32(self.len() as u32);
|
||||||
|
buf.put_slice(self.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for str {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
let length = self.len();
|
||||||
|
let size = if length > u8::MAX as usize { 5 } else { 2 };
|
||||||
|
size + length
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
let length = self.len();
|
||||||
|
if length > u8::MAX as usize {
|
||||||
|
buf.put_u8(codec::FORMATCODE_STRING32);
|
||||||
|
buf.put_u32(length as u32);
|
||||||
|
} else {
|
||||||
|
buf.put_u8(codec::FORMATCODE_STRING8);
|
||||||
|
buf.put_u8(length as u8);
|
||||||
|
}
|
||||||
|
buf.put_slice(self.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArrayEncode for str {
|
||||||
|
const ARRAY_FORMAT_CODE: u8 = codec::FORMATCODE_STRING32;
|
||||||
|
fn array_encoded_size(&self) -> usize {
|
||||||
|
4 + self.len()
|
||||||
|
}
|
||||||
|
fn array_encode(&self, buf: &mut BytesMut) {
|
||||||
|
buf.put_u32(self.len() as u32);
|
||||||
|
buf.put_slice(self.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for Str {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
let length = self.len();
|
||||||
|
let size = if length > u8::MAX as usize { 5 } else { 2 };
|
||||||
|
size + length
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
let length = self.as_str().len();
|
||||||
|
if length > u8::MAX as usize {
|
||||||
|
buf.put_u8(codec::FORMATCODE_STRING32);
|
||||||
|
buf.put_u32(length as u32);
|
||||||
|
} else {
|
||||||
|
buf.put_u8(codec::FORMATCODE_STRING8);
|
||||||
|
buf.put_u8(length as u8);
|
||||||
|
}
|
||||||
|
buf.put_slice(self.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for Symbol {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
let length = self.len();
|
||||||
|
let size = if length > u8::MAX as usize { 5 } else { 2 };
|
||||||
|
size + length
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
let length = self.as_str().len();
|
||||||
|
if length > u8::MAX as usize {
|
||||||
|
buf.put_u8(codec::FORMATCODE_SYMBOL32);
|
||||||
|
buf.put_u32(length as u32);
|
||||||
|
} else {
|
||||||
|
buf.put_u8(codec::FORMATCODE_SYMBOL8);
|
||||||
|
buf.put_u8(length as u8);
|
||||||
|
}
|
||||||
|
buf.put_slice(self.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArrayEncode for Symbol {
|
||||||
|
const ARRAY_FORMAT_CODE: u8 = codec::FORMATCODE_SYMBOL32;
|
||||||
|
fn array_encoded_size(&self) -> usize {
|
||||||
|
4 + self.len()
|
||||||
|
}
|
||||||
|
fn array_encode(&self, buf: &mut BytesMut) {
|
||||||
|
buf.put_u32(self.len() as u32);
|
||||||
|
buf.put_slice(self.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for StaticSymbol {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
let length = self.0.len();
|
||||||
|
let size = if length > u8::MAX as usize { 5 } else { 2 };
|
||||||
|
size + length
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
let length = self.0.len();
|
||||||
|
if length > u8::MAX as usize {
|
||||||
|
buf.put_u8(codec::FORMATCODE_SYMBOL32);
|
||||||
|
buf.put_u32(length as u32);
|
||||||
|
} else {
|
||||||
|
buf.put_u8(codec::FORMATCODE_SYMBOL8);
|
||||||
|
buf.put_u8(length as u8);
|
||||||
|
}
|
||||||
|
buf.put_slice(self.0.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_encoded_size<K: Hash + Eq + Encode, V: Encode, S: BuildHasher>(
|
||||||
|
map: &HashMap<K, V, S>,
|
||||||
|
) -> usize {
|
||||||
|
map.iter()
|
||||||
|
.fold(0, |r, (k, v)| r + k.encoded_size() + v.encoded_size())
|
||||||
|
}
|
||||||
|
impl<K: Eq + Hash + Encode, V: Encode, S: BuildHasher> Encode for HashMap<K, V, S> {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
let size = map_encoded_size(self);
|
||||||
|
// f:1 + s:4 + c:4 vs f:1 + s:1 + c:1
|
||||||
|
let preamble = if size + 1 > u8::MAX as usize { 9 } else { 3 };
|
||||||
|
preamble + size
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
let count = self.len() * 2; // key-value pair accounts for two items in count
|
||||||
|
let size = map_encoded_size(self);
|
||||||
|
if size + 1 > u8::MAX as usize {
|
||||||
|
buf.put_u8(codec::FORMATCODE_MAP32);
|
||||||
|
buf.put_u32((size + 4) as u32); // +4 for 4 byte count that follows
|
||||||
|
buf.put_u32(count as u32);
|
||||||
|
} else {
|
||||||
|
buf.put_u8(codec::FORMATCODE_MAP8);
|
||||||
|
buf.put_u8((size + 1) as u8); // +1 for 1 byte count that follows
|
||||||
|
buf.put_u8(count as u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (k, v) in self {
|
||||||
|
k.encode(buf);
|
||||||
|
v.encode(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: Eq + Hash + Encode, V: Encode> ArrayEncode for HashMap<K, V> {
|
||||||
|
const ARRAY_FORMAT_CODE: u8 = codec::FORMATCODE_MAP32;
|
||||||
|
fn array_encoded_size(&self) -> usize {
|
||||||
|
8 + map_encoded_size(self)
|
||||||
|
}
|
||||||
|
fn array_encode(&self, buf: &mut BytesMut) {
|
||||||
|
let count = self.len() * 2;
|
||||||
|
let size = map_encoded_size(self) + 4;
|
||||||
|
buf.put_u32(size as u32);
|
||||||
|
buf.put_u32(count as u32);
|
||||||
|
|
||||||
|
for (k, v) in self {
|
||||||
|
k.encode(buf);
|
||||||
|
v.encode(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for VecSymbolMap {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
let size = self
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.fold(0, |r, (k, v)| r + k.encoded_size() + v.encoded_size());
|
||||||
|
|
||||||
|
// f:1 + s:4 + c:4 vs f:1 + s:1 + c:1
|
||||||
|
let preamble = if size + 1 > u8::MAX as usize { 9 } else { 3 };
|
||||||
|
preamble + size
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
let count = self.len() * 2; // key-value pair accounts for two items in count
|
||||||
|
let size = self
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.fold(0, |r, (k, v)| r + k.encoded_size() + v.encoded_size());
|
||||||
|
|
||||||
|
if size + 1 > u8::MAX as usize {
|
||||||
|
buf.put_u8(codec::FORMATCODE_MAP32);
|
||||||
|
buf.put_u32((size + 4) as u32); // +4 for 4 byte count that follows
|
||||||
|
buf.put_u32(count as u32);
|
||||||
|
} else {
|
||||||
|
buf.put_u8(codec::FORMATCODE_MAP8);
|
||||||
|
buf.put_u8((size + 1) as u8); // +1 for 1 byte count that follows
|
||||||
|
buf.put_u8(count as u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (k, v) in self.iter() {
|
||||||
|
k.encode(buf);
|
||||||
|
v.encode(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for VecStringMap {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
let size = self
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.fold(0, |r, (k, v)| r + k.encoded_size() + v.encoded_size());
|
||||||
|
|
||||||
|
// f:1 + s:4 + c:4 vs f:1 + s:1 + c:1
|
||||||
|
let preamble = if size + 1 > u8::MAX as usize { 9 } else { 3 };
|
||||||
|
preamble + size
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
let count = self.len() * 2; // key-value pair accounts for two items in count
|
||||||
|
let size = self
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.fold(0, |r, (k, v)| r + k.encoded_size() + v.encoded_size());
|
||||||
|
|
||||||
|
if size + 1 > u8::MAX as usize {
|
||||||
|
buf.put_u8(codec::FORMATCODE_MAP32);
|
||||||
|
buf.put_u32((size + 4) as u32); // +4 for 4 byte count that follows
|
||||||
|
buf.put_u32(count as u32);
|
||||||
|
} else {
|
||||||
|
buf.put_u8(codec::FORMATCODE_MAP8);
|
||||||
|
buf.put_u8((size + 1) as u8); // +1 for 1 byte count that follows
|
||||||
|
buf.put_u8(count as u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (k, v) in self.iter() {
|
||||||
|
k.encode(buf);
|
||||||
|
v.encode(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn array_encoded_size<T: ArrayEncode>(vec: &[T]) -> usize {
|
||||||
|
vec.iter().fold(0, |r, i| r + i.array_encoded_size())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ArrayEncode> Encode for Vec<T> {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
let content_size = array_encoded_size(self);
|
||||||
|
// format_code + size + count + item constructor -- todo: support described ctor?
|
||||||
|
(if content_size + 1 > u8::MAX as usize {
|
||||||
|
10
|
||||||
|
} else {
|
||||||
|
4
|
||||||
|
}) // +1 for 1 byte count and 1 byte format code
|
||||||
|
+ content_size
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
let size = array_encoded_size(self);
|
||||||
|
if size + 1 > u8::MAX as usize {
|
||||||
|
buf.put_u8(codec::FORMATCODE_ARRAY32);
|
||||||
|
buf.put_u32((size + 5) as u32); // +4 for 4 byte count and 1 byte item ctor that follow
|
||||||
|
buf.put_u32(self.len() as u32);
|
||||||
|
} else {
|
||||||
|
buf.put_u8(codec::FORMATCODE_ARRAY8);
|
||||||
|
buf.put_u8((size + 2) as u8); // +1 for 1 byte count and 1 byte item ctor that follow
|
||||||
|
buf.put_u8(self.len() as u8);
|
||||||
|
}
|
||||||
|
buf.put_u8(T::ARRAY_FORMAT_CODE);
|
||||||
|
for i in self {
|
||||||
|
i.array_encode(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Encode + ArrayEncode> Encode for Multiple<T> {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
let count = self.len();
|
||||||
|
if count == 1 {
|
||||||
|
// special case: single item is encoded without array encoding
|
||||||
|
self.0[0].encoded_size()
|
||||||
|
} else {
|
||||||
|
self.0.encoded_size()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
let count = self.0.len();
|
||||||
|
if count == 1 {
|
||||||
|
// special case: single item is encoded without array encoding
|
||||||
|
self.0[0].encode(buf)
|
||||||
|
} else {
|
||||||
|
self.0.encode(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_encoded_size(vec: &List) -> usize {
|
||||||
|
vec.iter().fold(0, |r, i| r + i.encoded_size())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for List {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
let content_size = list_encoded_size(self);
|
||||||
|
// format_code + size + count
|
||||||
|
(if content_size + 1 > u8::MAX as usize {
|
||||||
|
9
|
||||||
|
} else {
|
||||||
|
3
|
||||||
|
}) + content_size
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
let size = list_encoded_size(self);
|
||||||
|
if size + 1 > u8::MAX as usize {
|
||||||
|
buf.put_u8(codec::FORMATCODE_ARRAY32);
|
||||||
|
buf.put_u32((size + 4) as u32); // +4 for 4 byte count that follow
|
||||||
|
buf.put_u32(self.len() as u32);
|
||||||
|
} else {
|
||||||
|
buf.put_u8(codec::FORMATCODE_ARRAY8);
|
||||||
|
buf.put_u8((size + 1) as u8); // +1 for 1 byte count that follow
|
||||||
|
buf.put_u8(self.len() as u8);
|
||||||
|
}
|
||||||
|
for i in self.iter() {
|
||||||
|
i.encode(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for Variant {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
match *self {
|
||||||
|
Variant::Null => 1,
|
||||||
|
Variant::Boolean(b) => b.encoded_size(),
|
||||||
|
Variant::Ubyte(b) => b.encoded_size(),
|
||||||
|
Variant::Ushort(s) => s.encoded_size(),
|
||||||
|
Variant::Uint(i) => i.encoded_size(),
|
||||||
|
Variant::Ulong(l) => l.encoded_size(),
|
||||||
|
Variant::Byte(b) => b.encoded_size(),
|
||||||
|
Variant::Short(s) => s.encoded_size(),
|
||||||
|
Variant::Int(i) => i.encoded_size(),
|
||||||
|
Variant::Long(l) => l.encoded_size(),
|
||||||
|
Variant::Float(f) => f.encoded_size(),
|
||||||
|
Variant::Double(d) => d.encoded_size(),
|
||||||
|
Variant::Char(c) => c.encoded_size(),
|
||||||
|
Variant::Timestamp(ref t) => t.encoded_size(),
|
||||||
|
Variant::Uuid(ref u) => u.encoded_size(),
|
||||||
|
Variant::Binary(ref b) => b.encoded_size(),
|
||||||
|
Variant::String(ref s) => s.encoded_size(),
|
||||||
|
Variant::Symbol(ref s) => s.encoded_size(),
|
||||||
|
Variant::StaticSymbol(ref s) => s.encoded_size(),
|
||||||
|
Variant::List(ref l) => l.encoded_size(),
|
||||||
|
Variant::Map(ref m) => m.map.encoded_size(),
|
||||||
|
Variant::Described(ref dv) => dv.0.encoded_size() + dv.1.encoded_size(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encodes `Variant` into provided `BytesMut`
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
match *self {
|
||||||
|
Variant::Null => encode_null(buf),
|
||||||
|
Variant::Boolean(b) => b.encode(buf),
|
||||||
|
Variant::Ubyte(b) => b.encode(buf),
|
||||||
|
Variant::Ushort(s) => s.encode(buf),
|
||||||
|
Variant::Uint(i) => i.encode(buf),
|
||||||
|
Variant::Ulong(l) => l.encode(buf),
|
||||||
|
Variant::Byte(b) => b.encode(buf),
|
||||||
|
Variant::Short(s) => s.encode(buf),
|
||||||
|
Variant::Int(i) => i.encode(buf),
|
||||||
|
Variant::Long(l) => l.encode(buf),
|
||||||
|
Variant::Float(f) => f.encode(buf),
|
||||||
|
Variant::Double(d) => d.encode(buf),
|
||||||
|
Variant::Char(c) => c.encode(buf),
|
||||||
|
Variant::Timestamp(ref t) => t.encode(buf),
|
||||||
|
Variant::Uuid(ref u) => u.encode(buf),
|
||||||
|
Variant::Binary(ref b) => b.encode(buf),
|
||||||
|
Variant::String(ref s) => s.encode(buf),
|
||||||
|
Variant::Symbol(ref s) => s.encode(buf),
|
||||||
|
Variant::StaticSymbol(ref s) => s.encode(buf),
|
||||||
|
Variant::List(ref l) => l.encode(buf),
|
||||||
|
Variant::Map(ref m) => m.map.encode(buf),
|
||||||
|
Variant::Described(ref dv) => {
|
||||||
|
dv.0.encode(buf);
|
||||||
|
dv.1.encode(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Encode> Encode for Option<T> {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
self.as_ref().map_or(1, |v| v.encoded_size())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
match *self {
|
||||||
|
Some(ref e) => e.encode(buf),
|
||||||
|
None => encode_null(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for Descriptor {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
match *self {
|
||||||
|
Descriptor::Ulong(v) => 1 + v.encoded_size(),
|
||||||
|
Descriptor::Symbol(ref v) => 1 + v.encoded_size(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
buf.put_u8(codec::FORMATCODE_DESCRIBED);
|
||||||
|
match *self {
|
||||||
|
Descriptor::Ulong(v) => v.encode(buf),
|
||||||
|
Descriptor::Symbol(ref v) => v.encode(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const WORD_LEN: usize = 4;
|
||||||
|
|
||||||
|
impl Encode for AmqpFrame {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
framing::HEADER_LEN + self.performative().encoded_size()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
let doff: u8 = (framing::HEADER_LEN / WORD_LEN) as u8;
|
||||||
|
buf.put_u32(self.encoded_size() as u32);
|
||||||
|
buf.put_u8(doff);
|
||||||
|
buf.put_u8(framing::FRAME_TYPE_AMQP);
|
||||||
|
buf.put_u16(self.channel_id());
|
||||||
|
self.performative().encode(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for SaslFrame {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
framing::HEADER_LEN + self.body.encoded_size()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
let doff: u8 = (framing::HEADER_LEN / WORD_LEN) as u8;
|
||||||
|
buf.put_u32(self.encoded_size() as u32);
|
||||||
|
buf.put_u8(doff);
|
||||||
|
buf.put_u8(framing::FRAME_TYPE_SASL);
|
||||||
|
buf.put_u16(0);
|
||||||
|
self.body.encode(buf);
|
||||||
|
}
|
||||||
|
}
|
1
actix-amqp/codec/src/codec/lorem.txt
Executable file
1
actix-amqp/codec/src/codec/lorem.txt
Executable file
@ -0,0 +1 @@
|
|||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec accumsan iaculis ipsum sed convallis. Phasellus consectetur justo et odio maximus, vel vehicula sapien venenatis. Nunc ac viverra risus. Pellentesque elementum, mauris et viverra ultricies, lacus erat varius nulla, eget maximus nisl sed.
|
150
actix-amqp/codec/src/codec/mod.rs
Executable file
150
actix-amqp/codec/src/codec/mod.rs
Executable file
@ -0,0 +1,150 @@
|
|||||||
|
use bytes::BytesMut;
|
||||||
|
use std::marker::Sized;
|
||||||
|
|
||||||
|
use crate::errors::AmqpParseError;
|
||||||
|
|
||||||
|
macro_rules! decode_check_len {
|
||||||
|
($buf:ident, $size:expr) => {
|
||||||
|
if $buf.len() < $size {
|
||||||
|
return Err(AmqpParseError::Incomplete(Some($size)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod decode;
|
||||||
|
mod encode;
|
||||||
|
|
||||||
|
pub(crate) use self::decode::decode_list_header;
|
||||||
|
|
||||||
|
pub trait Encode {
|
||||||
|
fn encoded_size(&self) -> usize;
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ArrayEncode {
|
||||||
|
const ARRAY_FORMAT_CODE: u8;
|
||||||
|
|
||||||
|
fn array_encoded_size(&self) -> usize;
|
||||||
|
|
||||||
|
fn array_encode(&self, buf: &mut BytesMut);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Decode
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
fn decode(input: &[u8]) -> Result<(&[u8], Self), AmqpParseError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DecodeFormatted
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ArrayDecode: Sized {
|
||||||
|
fn array_decode(input: &[u8]) -> Result<(&[u8], Self), AmqpParseError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: DecodeFormatted> Decode for T {
|
||||||
|
fn decode(input: &[u8]) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
let (input, fmt) = decode_format_code(input)?;
|
||||||
|
T::decode_with_format(input, fmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode_format_code(input: &[u8]) -> Result<(&[u8], u8), AmqpParseError> {
|
||||||
|
decode_check_len!(input, 1);
|
||||||
|
Ok((&input[1..], input[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const FORMATCODE_DESCRIBED: u8 = 0x00;
|
||||||
|
pub const FORMATCODE_NULL: u8 = 0x40; // fixed width --V
|
||||||
|
pub const FORMATCODE_BOOLEAN: u8 = 0x56;
|
||||||
|
pub const FORMATCODE_BOOLEAN_TRUE: u8 = 0x41;
|
||||||
|
pub const FORMATCODE_BOOLEAN_FALSE: u8 = 0x42;
|
||||||
|
pub const FORMATCODE_UINT_0: u8 = 0x43;
|
||||||
|
pub const FORMATCODE_ULONG_0: u8 = 0x44;
|
||||||
|
pub const FORMATCODE_UBYTE: u8 = 0x50;
|
||||||
|
pub const FORMATCODE_USHORT: u8 = 0x60;
|
||||||
|
pub const FORMATCODE_UINT: u8 = 0x70;
|
||||||
|
pub const FORMATCODE_ULONG: u8 = 0x80;
|
||||||
|
pub const FORMATCODE_BYTE: u8 = 0x51;
|
||||||
|
pub const FORMATCODE_SHORT: u8 = 0x61;
|
||||||
|
pub const FORMATCODE_INT: u8 = 0x71;
|
||||||
|
pub const FORMATCODE_LONG: u8 = 0x81;
|
||||||
|
pub const FORMATCODE_SMALLUINT: u8 = 0x52;
|
||||||
|
pub const FORMATCODE_SMALLULONG: u8 = 0x53;
|
||||||
|
pub const FORMATCODE_SMALLINT: u8 = 0x54;
|
||||||
|
pub const FORMATCODE_SMALLLONG: u8 = 0x55;
|
||||||
|
pub const FORMATCODE_FLOAT: u8 = 0x72;
|
||||||
|
pub const FORMATCODE_DOUBLE: u8 = 0x82;
|
||||||
|
// pub const FORMATCODE_DECIMAL32: u8 = 0x74;
|
||||||
|
// pub const FORMATCODE_DECIMAL64: u8 = 0x84;
|
||||||
|
// pub const FORMATCODE_DECIMAL128: u8 = 0x94;
|
||||||
|
pub const FORMATCODE_CHAR: u8 = 0x73;
|
||||||
|
pub const FORMATCODE_TIMESTAMP: u8 = 0x83;
|
||||||
|
pub const FORMATCODE_UUID: u8 = 0x98;
|
||||||
|
pub const FORMATCODE_BINARY8: u8 = 0xa0; // variable --V
|
||||||
|
pub const FORMATCODE_BINARY32: u8 = 0xb0;
|
||||||
|
pub const FORMATCODE_STRING8: u8 = 0xa1;
|
||||||
|
pub const FORMATCODE_STRING32: u8 = 0xb1;
|
||||||
|
pub const FORMATCODE_SYMBOL8: u8 = 0xa3;
|
||||||
|
pub const FORMATCODE_SYMBOL32: u8 = 0xb3;
|
||||||
|
pub const FORMATCODE_LIST0: u8 = 0x45; // compound --V
|
||||||
|
pub const FORMATCODE_LIST8: u8 = 0xc0;
|
||||||
|
pub const FORMATCODE_LIST32: u8 = 0xd0;
|
||||||
|
pub const FORMATCODE_MAP8: u8 = 0xc1;
|
||||||
|
pub const FORMATCODE_MAP32: u8 = 0xd1;
|
||||||
|
pub const FORMATCODE_ARRAY8: u8 = 0xe0;
|
||||||
|
pub const FORMATCODE_ARRAY32: u8 = 0xf0;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use bytes::{Bytes, BytesMut};
|
||||||
|
|
||||||
|
use crate::codec::{Decode, Encode};
|
||||||
|
use crate::errors::AmqpCodecError;
|
||||||
|
use crate::framing::{AmqpFrame, SaslFrame};
|
||||||
|
use crate::protocol::SaslFrameBody;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sasl_mechanisms() -> Result<(), AmqpCodecError> {
|
||||||
|
let data = b"\x02\x01\0\0\0S@\xc02\x01\xe0/\x04\xb3\0\0\0\x07MSSBCBS\0\0\0\x05PLAIN\0\0\0\tANONYMOUS\0\0\0\x08EXTERNAL";
|
||||||
|
|
||||||
|
let (remainder, frame) = SaslFrame::decode(data.as_ref())?;
|
||||||
|
assert!(remainder.is_empty());
|
||||||
|
match frame.body {
|
||||||
|
SaslFrameBody::SaslMechanisms(_) => (),
|
||||||
|
_ => panic!("error"),
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buf = BytesMut::new();
|
||||||
|
buf.reserve(frame.encoded_size());
|
||||||
|
frame.encode(&mut buf);
|
||||||
|
buf.split_to(4);
|
||||||
|
assert_eq!(Bytes::from_static(data), buf.freeze());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_disposition() -> Result<(), AmqpCodecError> {
|
||||||
|
let data = b"\x02\0\0\0\0S\x15\xc0\x0c\x06AC@A\0S$\xc0\x01\0B";
|
||||||
|
|
||||||
|
let (remainder, frame) = AmqpFrame::decode(data.as_ref())?;
|
||||||
|
assert!(remainder.is_empty());
|
||||||
|
assert_eq!(frame.performative().name(), "Disposition");
|
||||||
|
|
||||||
|
let mut buf = BytesMut::new();
|
||||||
|
buf.reserve(frame.encoded_size());
|
||||||
|
frame.encode(&mut buf);
|
||||||
|
buf.split_to(4);
|
||||||
|
assert_eq!(Bytes::from_static(data), buf.freeze());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
73
actix-amqp/codec/src/errors.rs
Executable file
73
actix-amqp/codec/src/errors.rs
Executable file
@ -0,0 +1,73 @@
|
|||||||
|
use uuid;
|
||||||
|
|
||||||
|
use crate::protocol::ProtocolId;
|
||||||
|
use crate::types::Descriptor;
|
||||||
|
|
||||||
|
#[derive(Debug, Display, From, Clone)]
|
||||||
|
pub enum AmqpParseError {
|
||||||
|
#[display(fmt = "Loaded item size is invalid")]
|
||||||
|
InvalidSize,
|
||||||
|
#[display(fmt = "More data required during frame parsing: '{:?}'", "_0")]
|
||||||
|
Incomplete(Option<usize>),
|
||||||
|
#[from(ignore)]
|
||||||
|
#[display(fmt = "Unexpected format code: '{}'", "_0")]
|
||||||
|
InvalidFormatCode(u8),
|
||||||
|
#[display(fmt = "Invalid value converting to char: {}", "_0")]
|
||||||
|
InvalidChar(u32),
|
||||||
|
#[display(fmt = "Unexpected descriptor: '{:?}'", "_0")]
|
||||||
|
InvalidDescriptor(Descriptor),
|
||||||
|
#[from(ignore)]
|
||||||
|
#[display(fmt = "Unexpected frame type: '{:?}'", "_0")]
|
||||||
|
UnexpectedFrameType(u8),
|
||||||
|
#[from(ignore)]
|
||||||
|
#[display(fmt = "Required field '{:?}' was omitted.", "_0")]
|
||||||
|
RequiredFieldOmitted(&'static str),
|
||||||
|
#[from(ignore)]
|
||||||
|
#[display(fmt = "Unknown {:?} option.", "_0")]
|
||||||
|
UnknownEnumOption(&'static str),
|
||||||
|
UuidParseError(uuid::Error),
|
||||||
|
Utf8Error(std::str::Utf8Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Display, From)]
|
||||||
|
pub enum AmqpCodecError {
|
||||||
|
ParseError(AmqpParseError),
|
||||||
|
#[display(fmt = "bytes left unparsed at the frame trail")]
|
||||||
|
UnparsedBytesLeft,
|
||||||
|
#[display(fmt = "max inbound frame size exceeded")]
|
||||||
|
MaxSizeExceeded,
|
||||||
|
#[display(fmt = "Io error: {:?}", _0)]
|
||||||
|
Io(Option<std::io::Error>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for AmqpCodecError {
|
||||||
|
fn clone(&self) -> AmqpCodecError {
|
||||||
|
match self {
|
||||||
|
AmqpCodecError::ParseError(err) => AmqpCodecError::ParseError(err.clone()),
|
||||||
|
AmqpCodecError::UnparsedBytesLeft => AmqpCodecError::UnparsedBytesLeft,
|
||||||
|
AmqpCodecError::MaxSizeExceeded => AmqpCodecError::MaxSizeExceeded,
|
||||||
|
AmqpCodecError::Io(_) => AmqpCodecError::Io(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for AmqpCodecError {
|
||||||
|
fn from(err: std::io::Error) -> AmqpCodecError {
|
||||||
|
AmqpCodecError::Io(Some(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Display, From)]
|
||||||
|
pub enum ProtocolIdError {
|
||||||
|
InvalidHeader,
|
||||||
|
Incompatible,
|
||||||
|
Unknown,
|
||||||
|
#[display(fmt = "Expected {:?} protocol id, seen {:?} instead.", exp, got)]
|
||||||
|
Unexpected {
|
||||||
|
exp: ProtocolId,
|
||||||
|
got: ProtocolId,
|
||||||
|
},
|
||||||
|
Disconnected,
|
||||||
|
#[display(fmt = "io error: {:?}", "_0")]
|
||||||
|
Io(std::io::Error),
|
||||||
|
}
|
80
actix-amqp/codec/src/framing.rs
Executable file
80
actix-amqp/codec/src/framing.rs
Executable file
@ -0,0 +1,80 @@
|
|||||||
|
use super::protocol;
|
||||||
|
|
||||||
|
/// Length in bytes of the fixed frame header
|
||||||
|
pub const HEADER_LEN: usize = 8;
|
||||||
|
|
||||||
|
/// AMQP Frame type marker (0)
|
||||||
|
pub const FRAME_TYPE_AMQP: u8 = 0x00;
|
||||||
|
pub const FRAME_TYPE_SASL: u8 = 0x01;
|
||||||
|
|
||||||
|
/// Represents an AMQP Frame
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct AmqpFrame {
|
||||||
|
channel_id: u16,
|
||||||
|
performative: protocol::Frame,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AmqpFrame {
|
||||||
|
pub fn new(channel_id: u16, performative: protocol::Frame) -> AmqpFrame {
|
||||||
|
AmqpFrame {
|
||||||
|
channel_id,
|
||||||
|
performative,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn channel_id(&self) -> u16 {
|
||||||
|
self.channel_id
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn performative(&self) -> &protocol::Frame {
|
||||||
|
&self.performative
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn into_parts(self) -> (u16, protocol::Frame) {
|
||||||
|
(self.channel_id, self.performative)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct SaslFrame {
|
||||||
|
pub body: protocol::SaslFrameBody,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SaslFrame {
|
||||||
|
pub fn new(body: protocol::SaslFrameBody) -> SaslFrame {
|
||||||
|
SaslFrame { body }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<protocol::SaslMechanisms> for SaslFrame {
|
||||||
|
fn from(item: protocol::SaslMechanisms) -> SaslFrame {
|
||||||
|
SaslFrame::new(protocol::SaslFrameBody::SaslMechanisms(item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<protocol::SaslInit> for SaslFrame {
|
||||||
|
fn from(item: protocol::SaslInit) -> SaslFrame {
|
||||||
|
SaslFrame::new(protocol::SaslFrameBody::SaslInit(item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<protocol::SaslChallenge> for SaslFrame {
|
||||||
|
fn from(item: protocol::SaslChallenge) -> SaslFrame {
|
||||||
|
SaslFrame::new(protocol::SaslFrameBody::SaslChallenge(item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<protocol::SaslResponse> for SaslFrame {
|
||||||
|
fn from(item: protocol::SaslResponse) -> SaslFrame {
|
||||||
|
SaslFrame::new(protocol::SaslFrameBody::SaslResponse(item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<protocol::SaslOutcome> for SaslFrame {
|
||||||
|
fn from(item: protocol::SaslOutcome) -> SaslFrame {
|
||||||
|
SaslFrame::new(protocol::SaslFrameBody::SaslOutcome(item))
|
||||||
|
}
|
||||||
|
}
|
160
actix-amqp/codec/src/io.rs
Executable file
160
actix-amqp/codec/src/io.rs
Executable file
@ -0,0 +1,160 @@
|
|||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use actix_codec::{Decoder, Encoder};
|
||||||
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
|
use bytes::{BufMut, BytesMut};
|
||||||
|
|
||||||
|
use super::errors::{AmqpCodecError, ProtocolIdError};
|
||||||
|
use super::framing::HEADER_LEN;
|
||||||
|
use crate::codec::{Decode, Encode};
|
||||||
|
use crate::protocol::ProtocolId;
|
||||||
|
|
||||||
|
const SIZE_LOW_WM: usize = 4096;
|
||||||
|
const SIZE_HIGH_WM: usize = 32768;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AmqpCodec<T: Decode + Encode> {
|
||||||
|
state: DecodeState,
|
||||||
|
max_size: usize,
|
||||||
|
phantom: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
enum DecodeState {
|
||||||
|
FrameHeader,
|
||||||
|
Frame(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Decode + Encode> Default for AmqpCodec<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Decode + Encode> AmqpCodec<T> {
|
||||||
|
pub fn new() -> AmqpCodec<T> {
|
||||||
|
AmqpCodec {
|
||||||
|
state: DecodeState::FrameHeader,
|
||||||
|
max_size: 0,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.max_size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Decode + Encode> Decoder for AmqpCodec<T> {
|
||||||
|
type Item = T;
|
||||||
|
type Error = AmqpCodecError;
|
||||||
|
|
||||||
|
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||||
|
loop {
|
||||||
|
match self.state {
|
||||||
|
DecodeState::FrameHeader => {
|
||||||
|
let len = src.len();
|
||||||
|
if len < HEADER_LEN {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// read frame size
|
||||||
|
let size = BigEndian::read_u32(src.as_ref()) as usize;
|
||||||
|
if self.max_size != 0 && size > self.max_size {
|
||||||
|
return Err(AmqpCodecError::MaxSizeExceeded);
|
||||||
|
}
|
||||||
|
self.state = DecodeState::Frame(size - 4);
|
||||||
|
src.split_to(4);
|
||||||
|
|
||||||
|
if len < size {
|
||||||
|
// extend receiving buffer to fit the whole frame
|
||||||
|
if src.remaining_mut() < std::cmp::max(SIZE_LOW_WM, size + HEADER_LEN) {
|
||||||
|
src.reserve(SIZE_HIGH_WM);
|
||||||
|
}
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DecodeState::Frame(size) => {
|
||||||
|
if src.len() < size {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let frame_buf = src.split_to(size);
|
||||||
|
let (remainder, frame) = T::decode(frame_buf.as_ref())?;
|
||||||
|
if !remainder.is_empty() {
|
||||||
|
// todo: could it really happen?
|
||||||
|
return Err(AmqpCodecError::UnparsedBytesLeft);
|
||||||
|
}
|
||||||
|
self.state = DecodeState::FrameHeader;
|
||||||
|
return Ok(Some(frame));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Decode + Encode + ::std::fmt::Debug> Encoder for AmqpCodec<T> {
|
||||||
|
type Item = T;
|
||||||
|
type Error = AmqpCodecError;
|
||||||
|
|
||||||
|
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
|
||||||
|
let size = item.encoded_size();
|
||||||
|
let need = std::cmp::max(SIZE_LOW_WM, size);
|
||||||
|
if dst.remaining_mut() < need {
|
||||||
|
dst.reserve(std::cmp::max(need, SIZE_HIGH_WM));
|
||||||
|
}
|
||||||
|
|
||||||
|
item.encode(dst);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PROTOCOL_HEADER_LEN: usize = 8;
|
||||||
|
const PROTOCOL_HEADER_PREFIX: &[u8] = b"AMQP";
|
||||||
|
const PROTOCOL_VERSION: &[u8] = &[1, 0, 0];
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct ProtocolIdCodec;
|
||||||
|
|
||||||
|
impl Decoder for ProtocolIdCodec {
|
||||||
|
type Item = ProtocolId;
|
||||||
|
type Error = ProtocolIdError;
|
||||||
|
|
||||||
|
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||||
|
if src.len() < PROTOCOL_HEADER_LEN {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
let src = src.split_to(8);
|
||||||
|
if &src[0..4] != PROTOCOL_HEADER_PREFIX {
|
||||||
|
Err(ProtocolIdError::InvalidHeader)
|
||||||
|
} else if &src[5..8] != PROTOCOL_VERSION {
|
||||||
|
Err(ProtocolIdError::Incompatible)
|
||||||
|
} else {
|
||||||
|
let protocol_id = src[4];
|
||||||
|
match protocol_id {
|
||||||
|
0 => Ok(Some(ProtocolId::Amqp)),
|
||||||
|
2 => Ok(Some(ProtocolId::AmqpTls)),
|
||||||
|
3 => Ok(Some(ProtocolId::AmqpSasl)),
|
||||||
|
_ => Err(ProtocolIdError::Unknown),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encoder for ProtocolIdCodec {
|
||||||
|
type Item = ProtocolId;
|
||||||
|
type Error = ProtocolIdError;
|
||||||
|
|
||||||
|
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
|
||||||
|
dst.reserve(PROTOCOL_HEADER_LEN);
|
||||||
|
dst.put_slice(PROTOCOL_HEADER_PREFIX);
|
||||||
|
dst.put_u8(item as u8);
|
||||||
|
dst.put_slice(PROTOCOL_VERSION);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
17
actix-amqp/codec/src/lib.rs
Executable file
17
actix-amqp/codec/src/lib.rs
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
#[macro_use]
|
||||||
|
extern crate derive_more;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod codec;
|
||||||
|
mod errors;
|
||||||
|
mod framing;
|
||||||
|
mod io;
|
||||||
|
mod message;
|
||||||
|
pub mod protocol;
|
||||||
|
pub mod types;
|
||||||
|
|
||||||
|
pub use self::codec::{Decode, Encode};
|
||||||
|
pub use self::errors::{AmqpCodecError, AmqpParseError, ProtocolIdError};
|
||||||
|
pub use self::framing::{AmqpFrame, SaslFrame};
|
||||||
|
pub use self::io::{AmqpCodec, ProtocolIdCodec};
|
||||||
|
pub use self::message::{InMessage, MessageBody, OutMessage};
|
89
actix-amqp/codec/src/message/body.rs
Executable file
89
actix-amqp/codec/src/message/body.rs
Executable file
@ -0,0 +1,89 @@
|
|||||||
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
|
|
||||||
|
use crate::codec::{Encode, FORMATCODE_BINARY32, FORMATCODE_BINARY8};
|
||||||
|
use crate::protocol::TransferBody;
|
||||||
|
use crate::types::{Descriptor, List, Variant};
|
||||||
|
|
||||||
|
use super::SECTION_PREFIX_LENGTH;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq)]
|
||||||
|
pub struct MessageBody {
|
||||||
|
pub data: Vec<Bytes>,
|
||||||
|
pub sequence: Vec<List>,
|
||||||
|
pub messages: Vec<TransferBody>,
|
||||||
|
pub value: Option<Variant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageBody {
|
||||||
|
pub fn data(&self) -> Option<&Bytes> {
|
||||||
|
if self.data.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(&self.data[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn value(&self) -> Option<&Variant> {
|
||||||
|
self.value.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_data(&mut self, data: Bytes) {
|
||||||
|
self.data.clear();
|
||||||
|
self.data.push(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for MessageBody {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
let mut size = self
|
||||||
|
.data
|
||||||
|
.iter()
|
||||||
|
.fold(0, |a, d| a + d.encoded_size() + SECTION_PREFIX_LENGTH);
|
||||||
|
size += self
|
||||||
|
.sequence
|
||||||
|
.iter()
|
||||||
|
.fold(0, |a, seq| a + seq.encoded_size() + SECTION_PREFIX_LENGTH);
|
||||||
|
size += self.messages.iter().fold(0, |a, m| {
|
||||||
|
let length = m.encoded_size();
|
||||||
|
let size = length + if length > std::u8::MAX as usize { 5 } else { 2 };
|
||||||
|
a + size + SECTION_PREFIX_LENGTH
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(ref val) = self.value {
|
||||||
|
size + val.encoded_size() + SECTION_PREFIX_LENGTH
|
||||||
|
} else {
|
||||||
|
size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, dst: &mut BytesMut) {
|
||||||
|
self.data.iter().for_each(|d| {
|
||||||
|
Descriptor::Ulong(117).encode(dst);
|
||||||
|
d.encode(dst);
|
||||||
|
});
|
||||||
|
self.sequence.iter().for_each(|seq| {
|
||||||
|
Descriptor::Ulong(118).encode(dst);
|
||||||
|
seq.encode(dst)
|
||||||
|
});
|
||||||
|
if let Some(ref val) = self.value {
|
||||||
|
Descriptor::Ulong(119).encode(dst);
|
||||||
|
val.encode(dst);
|
||||||
|
}
|
||||||
|
// encode Message as nested Bytes object
|
||||||
|
self.messages.iter().for_each(|m| {
|
||||||
|
Descriptor::Ulong(117).encode(dst);
|
||||||
|
|
||||||
|
// Bytes prefix
|
||||||
|
let length = m.encoded_size();
|
||||||
|
if length > std::u8::MAX as usize {
|
||||||
|
dst.put_u8(FORMATCODE_BINARY32);
|
||||||
|
dst.put_u32(length as u32);
|
||||||
|
} else {
|
||||||
|
dst.put_u8(FORMATCODE_BINARY8);
|
||||||
|
dst.put_u8(length as u8);
|
||||||
|
}
|
||||||
|
// encode nested Message
|
||||||
|
m.encode(dst);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
406
actix-amqp/codec/src/message/inmessage.rs
Executable file
406
actix-amqp/codec/src/message/inmessage.rs
Executable file
@ -0,0 +1,406 @@
|
|||||||
|
use std::cell::Cell;
|
||||||
|
|
||||||
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
|
use fxhash::FxHashMap;
|
||||||
|
|
||||||
|
use crate::codec::{Decode, Encode, FORMATCODE_BINARY8};
|
||||||
|
use crate::errors::AmqpParseError;
|
||||||
|
use crate::protocol::{
|
||||||
|
Annotations, Header, MessageFormat, Properties, Section, StringVariantMap, TransferBody,
|
||||||
|
};
|
||||||
|
use crate::types::{Descriptor, Str, Variant};
|
||||||
|
|
||||||
|
use super::body::MessageBody;
|
||||||
|
use super::outmessage::OutMessage;
|
||||||
|
use super::SECTION_PREFIX_LENGTH;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq)]
|
||||||
|
pub struct InMessage {
|
||||||
|
pub message_format: Option<MessageFormat>,
|
||||||
|
pub(super) header: Option<Header>,
|
||||||
|
pub(super) delivery_annotations: Option<Annotations>,
|
||||||
|
pub(super) message_annotations: Option<Annotations>,
|
||||||
|
pub(super) properties: Option<Properties>,
|
||||||
|
pub(super) application_properties: Option<StringVariantMap>,
|
||||||
|
pub(super) footer: Option<Annotations>,
|
||||||
|
pub(super) body: MessageBody,
|
||||||
|
pub(super) size: Cell<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InMessage {
|
||||||
|
/// Create new message and set body
|
||||||
|
pub fn with_body(body: Bytes) -> InMessage {
|
||||||
|
let mut msg = InMessage::default();
|
||||||
|
msg.body.data.push(body);
|
||||||
|
msg
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create new message and set messages as body
|
||||||
|
pub fn with_messages(messages: Vec<TransferBody>) -> InMessage {
|
||||||
|
let mut msg = InMessage::default();
|
||||||
|
msg.body.messages = messages;
|
||||||
|
msg
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Header
|
||||||
|
pub fn header(&self) -> Option<&Header> {
|
||||||
|
self.header.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set message header
|
||||||
|
pub fn set_header(mut self, header: Header) -> Self {
|
||||||
|
self.header = Some(header);
|
||||||
|
self.size.set(0);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Message properties
|
||||||
|
pub fn properties(&self) -> Option<&Properties> {
|
||||||
|
self.properties.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add property
|
||||||
|
pub fn set_properties<F>(mut self, f: F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(&mut Properties),
|
||||||
|
{
|
||||||
|
if let Some(ref mut props) = self.properties {
|
||||||
|
f(props);
|
||||||
|
} else {
|
||||||
|
let mut props = Properties::default();
|
||||||
|
f(&mut props);
|
||||||
|
self.properties = Some(props);
|
||||||
|
}
|
||||||
|
self.size.set(0);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get application property
|
||||||
|
pub fn app_property(&self, key: &str) -> Option<&Variant> {
|
||||||
|
if let Some(ref props) = self.application_properties {
|
||||||
|
props.get(key)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get application properties
|
||||||
|
pub fn app_properties(&self) -> Option<&StringVariantMap> {
|
||||||
|
self.application_properties.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get message annotation
|
||||||
|
pub fn message_annotation(&self, key: &str) -> Option<&Variant> {
|
||||||
|
if let Some(ref props) = self.message_annotations {
|
||||||
|
props.get(key)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add application property
|
||||||
|
pub fn set_app_property<K: Into<Str>, V: Into<Variant>>(mut self, key: K, value: V) -> Self {
|
||||||
|
if let Some(ref mut props) = self.application_properties {
|
||||||
|
props.insert(key.into(), value.into());
|
||||||
|
} else {
|
||||||
|
let mut props = FxHashMap::default();
|
||||||
|
props.insert(key.into(), value.into());
|
||||||
|
self.application_properties = Some(props);
|
||||||
|
}
|
||||||
|
self.size.set(0);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call closure with message reference
|
||||||
|
pub fn update<F>(self, f: F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(Self) -> Self,
|
||||||
|
{
|
||||||
|
self.size.set(0);
|
||||||
|
f(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call closure if value is Some value
|
||||||
|
pub fn if_some<T, F>(self, value: &Option<T>, f: F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(Self, &T) -> Self,
|
||||||
|
{
|
||||||
|
if let Some(ref val) = value {
|
||||||
|
self.size.set(0);
|
||||||
|
f(self, val)
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Message body
|
||||||
|
pub fn body(&self) -> &MessageBody {
|
||||||
|
&self.body
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Message value
|
||||||
|
pub fn value(&self) -> Option<&Variant> {
|
||||||
|
self.body.value.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set message body value
|
||||||
|
pub fn set_value<V: Into<Variant>>(mut self, v: V) -> Self {
|
||||||
|
self.body.value = Some(v.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set message body
|
||||||
|
pub fn set_body<F>(mut self, f: F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(&mut MessageBody),
|
||||||
|
{
|
||||||
|
f(&mut self.body);
|
||||||
|
self.size.set(0);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create new message and set `correlation_id` property
|
||||||
|
pub fn reply_message(&self) -> OutMessage {
|
||||||
|
let mut msg = OutMessage::default().if_some(&self.properties, |mut msg, data| {
|
||||||
|
msg.set_properties(|props| props.correlation_id = data.message_id.clone());
|
||||||
|
msg
|
||||||
|
});
|
||||||
|
msg.message_format = self.message_format;
|
||||||
|
msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode for InMessage {
|
||||||
|
fn decode(mut input: &[u8]) -> Result<(&[u8], InMessage), AmqpParseError> {
|
||||||
|
let mut message = InMessage::default();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let (buf, sec) = Section::decode(input)?;
|
||||||
|
match sec {
|
||||||
|
Section::Header(val) => {
|
||||||
|
message.header = Some(val);
|
||||||
|
}
|
||||||
|
Section::DeliveryAnnotations(val) => {
|
||||||
|
message.delivery_annotations = Some(val);
|
||||||
|
}
|
||||||
|
Section::MessageAnnotations(val) => {
|
||||||
|
message.message_annotations = Some(val);
|
||||||
|
}
|
||||||
|
Section::ApplicationProperties(val) => {
|
||||||
|
message.application_properties = Some(val);
|
||||||
|
}
|
||||||
|
Section::Footer(val) => {
|
||||||
|
message.footer = Some(val);
|
||||||
|
}
|
||||||
|
Section::Properties(val) => {
|
||||||
|
message.properties = Some(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
// body
|
||||||
|
Section::AmqpSequence(val) => {
|
||||||
|
message.body.sequence.push(val);
|
||||||
|
}
|
||||||
|
Section::AmqpValue(val) => {
|
||||||
|
message.body.value = Some(val);
|
||||||
|
}
|
||||||
|
Section::Data(val) => {
|
||||||
|
message.body.data.push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if buf.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
input = buf;
|
||||||
|
}
|
||||||
|
Ok((input, message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for InMessage {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
let size = self.size.get();
|
||||||
|
if size != 0 {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// body size, always add empty body if needed
|
||||||
|
let body_size = self.body.encoded_size();
|
||||||
|
let mut size = if body_size == 0 {
|
||||||
|
// empty bytes
|
||||||
|
SECTION_PREFIX_LENGTH + 2
|
||||||
|
} else {
|
||||||
|
body_size
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(ref h) = self.header {
|
||||||
|
size += h.encoded_size() + SECTION_PREFIX_LENGTH;
|
||||||
|
}
|
||||||
|
if let Some(ref da) = self.delivery_annotations {
|
||||||
|
size += da.encoded_size() + SECTION_PREFIX_LENGTH;
|
||||||
|
}
|
||||||
|
if let Some(ref ma) = self.message_annotations {
|
||||||
|
size += ma.encoded_size() + SECTION_PREFIX_LENGTH;
|
||||||
|
}
|
||||||
|
if let Some(ref p) = self.properties {
|
||||||
|
size += p.encoded_size();
|
||||||
|
}
|
||||||
|
if let Some(ref ap) = self.application_properties {
|
||||||
|
size += ap.encoded_size() + SECTION_PREFIX_LENGTH;
|
||||||
|
}
|
||||||
|
if let Some(ref f) = self.footer {
|
||||||
|
size += f.encoded_size() + SECTION_PREFIX_LENGTH;
|
||||||
|
}
|
||||||
|
self.size.set(size);
|
||||||
|
size
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, dst: &mut BytesMut) {
|
||||||
|
if let Some(ref h) = self.header {
|
||||||
|
h.encode(dst);
|
||||||
|
}
|
||||||
|
if let Some(ref da) = self.delivery_annotations {
|
||||||
|
Descriptor::Ulong(113).encode(dst);
|
||||||
|
da.encode(dst);
|
||||||
|
}
|
||||||
|
if let Some(ref ma) = self.message_annotations {
|
||||||
|
Descriptor::Ulong(114).encode(dst);
|
||||||
|
ma.encode(dst);
|
||||||
|
}
|
||||||
|
if let Some(ref p) = self.properties {
|
||||||
|
p.encode(dst);
|
||||||
|
}
|
||||||
|
if let Some(ref ap) = self.application_properties {
|
||||||
|
Descriptor::Ulong(116).encode(dst);
|
||||||
|
ap.encode(dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
// message body
|
||||||
|
if self.body.encoded_size() == 0 {
|
||||||
|
// special treatment for empty body
|
||||||
|
Descriptor::Ulong(117).encode(dst);
|
||||||
|
dst.put_u8(FORMATCODE_BINARY8);
|
||||||
|
dst.put_u8(0);
|
||||||
|
} else {
|
||||||
|
self.body.encode(dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
// message footer, always last item
|
||||||
|
if let Some(ref f) = self.footer {
|
||||||
|
Descriptor::Ulong(120).encode(dst);
|
||||||
|
f.encode(dst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use bytes::{Bytes, BytesMut};
|
||||||
|
use bytestring::ByteString;
|
||||||
|
|
||||||
|
use crate::codec::{Decode, Encode};
|
||||||
|
use crate::errors::AmqpCodecError;
|
||||||
|
use crate::protocol::Header;
|
||||||
|
use crate::types::Variant;
|
||||||
|
|
||||||
|
use super::InMessage;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_properties() -> Result<(), AmqpCodecError> {
|
||||||
|
let msg =
|
||||||
|
InMessage::with_body(Bytes::from_static(b"Hello world")).set_properties(|props| {
|
||||||
|
props.message_id = Some(Bytes::from_static(b"msg1").into());
|
||||||
|
props.content_type = Some("text".to_string().into());
|
||||||
|
props.correlation_id = Some(Bytes::from_static(b"no1").into());
|
||||||
|
props.content_encoding = Some("utf8+1".to_string().into());
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut buf = BytesMut::with_capacity(msg.encoded_size());
|
||||||
|
msg.encode(&mut buf);
|
||||||
|
|
||||||
|
let msg2 = InMessage::decode(&buf)?.1;
|
||||||
|
let props = msg2.properties.as_ref().unwrap();
|
||||||
|
assert_eq!(props.message_id, Some(Bytes::from_static(b"msg1").into()));
|
||||||
|
assert_eq!(
|
||||||
|
props.correlation_id,
|
||||||
|
Some(Bytes::from_static(b"no1").into())
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_app_properties() -> Result<(), AmqpCodecError> {
|
||||||
|
let msg = InMessage::default().set_app_property(ByteString::from("test"), 1);
|
||||||
|
|
||||||
|
let mut buf = BytesMut::with_capacity(msg.encoded_size());
|
||||||
|
msg.encode(&mut buf);
|
||||||
|
|
||||||
|
let msg2 = InMessage::decode(&buf)?.1;
|
||||||
|
let props = msg2.application_properties.as_ref().unwrap();
|
||||||
|
assert_eq!(*props.get("test").unwrap(), Variant::from(1));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_header() -> Result<(), AmqpCodecError> {
|
||||||
|
let hdr = Header {
|
||||||
|
durable: false,
|
||||||
|
priority: 1,
|
||||||
|
ttl: None,
|
||||||
|
first_acquirer: false,
|
||||||
|
delivery_count: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let msg = InMessage::default().set_header(hdr.clone());
|
||||||
|
let mut buf = BytesMut::with_capacity(msg.encoded_size());
|
||||||
|
msg.encode(&mut buf);
|
||||||
|
|
||||||
|
let msg2 = InMessage::decode(&buf)?.1;
|
||||||
|
assert_eq!(msg2.header().unwrap(), &hdr);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_data() -> Result<(), AmqpCodecError> {
|
||||||
|
let data = Bytes::from_static(b"test data");
|
||||||
|
|
||||||
|
let msg = InMessage::default().set_body(|body| body.set_data(data.clone()));
|
||||||
|
let mut buf = BytesMut::with_capacity(msg.encoded_size());
|
||||||
|
msg.encode(&mut buf);
|
||||||
|
|
||||||
|
let msg2 = InMessage::decode(&buf)?.1;
|
||||||
|
assert_eq!(msg2.body.data().unwrap(), &data);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_data_empty() -> Result<(), AmqpCodecError> {
|
||||||
|
let msg = InMessage::default();
|
||||||
|
let mut buf = BytesMut::with_capacity(msg.encoded_size());
|
||||||
|
msg.encode(&mut buf);
|
||||||
|
|
||||||
|
let msg2 = InMessage::decode(&buf)?.1;
|
||||||
|
assert_eq!(msg2.body.data().unwrap(), &Bytes::from_static(b""));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_messages() -> Result<(), AmqpCodecError> {
|
||||||
|
let msg1 = InMessage::default().set_properties(|props| props.message_id = Some(1.into()));
|
||||||
|
let msg2 = InMessage::default().set_properties(|props| props.message_id = Some(2.into()));
|
||||||
|
|
||||||
|
let msg = InMessage::default().set_body(|body| {
|
||||||
|
body.messages.push(msg1.clone().into());
|
||||||
|
body.messages.push(msg2.clone().into());
|
||||||
|
});
|
||||||
|
let mut buf = BytesMut::with_capacity(msg.encoded_size());
|
||||||
|
msg.encode(&mut buf);
|
||||||
|
|
||||||
|
let msg3 = InMessage::decode(&buf)?.1;
|
||||||
|
let msg4 = InMessage::decode(&msg3.body.data().unwrap())?.1;
|
||||||
|
assert_eq!(msg1.properties, msg4.properties);
|
||||||
|
|
||||||
|
let msg5 = InMessage::decode(&msg3.body.data[1])?.1;
|
||||||
|
assert_eq!(msg2.properties, msg5.properties);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
9
actix-amqp/codec/src/message/mod.rs
Executable file
9
actix-amqp/codec/src/message/mod.rs
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
mod body;
|
||||||
|
mod inmessage;
|
||||||
|
mod outmessage;
|
||||||
|
|
||||||
|
pub use self::body::MessageBody;
|
||||||
|
pub use self::inmessage::InMessage;
|
||||||
|
pub use self::outmessage::OutMessage;
|
||||||
|
|
||||||
|
pub(self) const SECTION_PREFIX_LENGTH: usize = 3;
|
440
actix-amqp/codec/src/message/outmessage.rs
Executable file
440
actix-amqp/codec/src/message/outmessage.rs
Executable file
@ -0,0 +1,440 @@
|
|||||||
|
use std::cell::Cell;
|
||||||
|
|
||||||
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
|
|
||||||
|
use crate::codec::{Decode, Encode, FORMATCODE_BINARY8};
|
||||||
|
use crate::errors::AmqpParseError;
|
||||||
|
use crate::protocol::{Annotations, Header, MessageFormat, Properties, Section, TransferBody};
|
||||||
|
use crate::types::{Descriptor, Str, Symbol, Variant, VecStringMap, VecSymbolMap};
|
||||||
|
|
||||||
|
use super::body::MessageBody;
|
||||||
|
use super::inmessage::InMessage;
|
||||||
|
use super::SECTION_PREFIX_LENGTH;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq)]
|
||||||
|
pub struct OutMessage {
|
||||||
|
pub message_format: Option<MessageFormat>,
|
||||||
|
header: Option<Header>,
|
||||||
|
delivery_annotations: Option<Annotations>,
|
||||||
|
message_annotations: Option<VecSymbolMap>,
|
||||||
|
properties: Option<Properties>,
|
||||||
|
application_properties: Option<VecStringMap>,
|
||||||
|
footer: Option<Annotations>,
|
||||||
|
body: MessageBody,
|
||||||
|
size: Cell<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutMessage {
|
||||||
|
/// Create new message and set body
|
||||||
|
pub fn with_body(body: Bytes) -> OutMessage {
|
||||||
|
let mut msg = OutMessage::default();
|
||||||
|
msg.body.data.push(body);
|
||||||
|
msg.message_format = Some(0);
|
||||||
|
msg
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create new message and set messages as body
|
||||||
|
pub fn with_messages(messages: Vec<TransferBody>) -> OutMessage {
|
||||||
|
let mut msg = OutMessage::default();
|
||||||
|
msg.body.messages = messages;
|
||||||
|
msg.message_format = Some(0);
|
||||||
|
msg
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Header
|
||||||
|
pub fn header(&self) -> Option<&Header> {
|
||||||
|
self.header.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set message header
|
||||||
|
pub fn set_header(&mut self, header: Header) -> &mut Self {
|
||||||
|
self.header = Some(header);
|
||||||
|
self.size.set(0);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Message properties
|
||||||
|
pub fn properties(&self) -> Option<&Properties> {
|
||||||
|
self.properties.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mutable reference to properties
|
||||||
|
pub fn properties_mut(&mut self) -> &mut Properties {
|
||||||
|
if self.properties.is_none() {
|
||||||
|
self.properties = Some(Properties::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.size.set(0);
|
||||||
|
self.properties.as_mut().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add property
|
||||||
|
pub fn set_properties<F>(&mut self, f: F) -> &mut Self
|
||||||
|
where
|
||||||
|
F: Fn(&mut Properties),
|
||||||
|
{
|
||||||
|
if let Some(ref mut props) = self.properties {
|
||||||
|
f(props);
|
||||||
|
} else {
|
||||||
|
let mut props = Properties::default();
|
||||||
|
f(&mut props);
|
||||||
|
self.properties = Some(props);
|
||||||
|
}
|
||||||
|
self.size.set(0);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get application property
|
||||||
|
pub fn app_properties(&self) -> Option<&VecStringMap> {
|
||||||
|
self.application_properties.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add application property
|
||||||
|
pub fn set_app_property<K, V>(&mut self, key: K, value: V) -> &mut Self
|
||||||
|
where
|
||||||
|
K: Into<Str>,
|
||||||
|
V: Into<Variant>,
|
||||||
|
{
|
||||||
|
if let Some(ref mut props) = self.application_properties {
|
||||||
|
props.push((key.into(), value.into()));
|
||||||
|
} else {
|
||||||
|
let mut props = VecStringMap::default();
|
||||||
|
props.push((key.into(), value.into()));
|
||||||
|
self.application_properties = Some(props);
|
||||||
|
}
|
||||||
|
self.size.set(0);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add message annotation
|
||||||
|
pub fn add_message_annotation<K, V>(&mut self, key: K, value: V) -> &mut Self
|
||||||
|
where
|
||||||
|
K: Into<Symbol>,
|
||||||
|
V: Into<Variant>,
|
||||||
|
{
|
||||||
|
if let Some(ref mut props) = self.message_annotations {
|
||||||
|
props.push((key.into(), value.into()));
|
||||||
|
} else {
|
||||||
|
let mut props = VecSymbolMap::default();
|
||||||
|
props.push((key.into(), value.into()));
|
||||||
|
self.message_annotations = Some(props);
|
||||||
|
}
|
||||||
|
self.size.set(0);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call closure with message reference
|
||||||
|
pub fn update<F>(self, f: F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(Self) -> Self,
|
||||||
|
{
|
||||||
|
self.size.set(0);
|
||||||
|
f(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call closure if value is Some value
|
||||||
|
pub fn if_some<T, F>(self, value: &Option<T>, f: F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(Self, &T) -> Self,
|
||||||
|
{
|
||||||
|
if let Some(ref val) = value {
|
||||||
|
self.size.set(0);
|
||||||
|
f(self, val)
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Message body
|
||||||
|
pub fn body(&self) -> &MessageBody {
|
||||||
|
&self.body
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Message value
|
||||||
|
pub fn value(&self) -> Option<&Variant> {
|
||||||
|
self.body.value.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set message body value
|
||||||
|
pub fn set_value<V: Into<Variant>>(&mut self, v: V) -> &mut Self {
|
||||||
|
self.body.value = Some(v.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set message body
|
||||||
|
pub fn set_body<F>(&mut self, f: F) -> &mut Self
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut MessageBody),
|
||||||
|
{
|
||||||
|
f(&mut self.body);
|
||||||
|
self.size.set(0);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create new message and set `correlation_id` property
|
||||||
|
pub fn reply_message(&self) -> OutMessage {
|
||||||
|
OutMessage::default().if_some(&self.properties, |mut msg, data| {
|
||||||
|
msg.set_properties(|props| props.correlation_id = data.message_id.clone());
|
||||||
|
msg
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<InMessage> for OutMessage {
|
||||||
|
fn from(from: InMessage) -> Self {
|
||||||
|
let mut msg = OutMessage {
|
||||||
|
message_format: from.message_format,
|
||||||
|
header: from.header,
|
||||||
|
properties: from.properties,
|
||||||
|
delivery_annotations: from.delivery_annotations,
|
||||||
|
message_annotations: from.message_annotations.map(|v| v.into()),
|
||||||
|
application_properties: from.application_properties.map(|v| v.into()),
|
||||||
|
footer: from.footer,
|
||||||
|
body: from.body,
|
||||||
|
size: Cell::new(0),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(ref mut props) = msg.properties {
|
||||||
|
props.correlation_id = props.message_id.clone();
|
||||||
|
};
|
||||||
|
|
||||||
|
msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode for OutMessage {
|
||||||
|
fn decode(mut input: &[u8]) -> Result<(&[u8], OutMessage), AmqpParseError> {
|
||||||
|
let mut message = OutMessage::default();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let (buf, sec) = Section::decode(input)?;
|
||||||
|
match sec {
|
||||||
|
Section::Header(val) => {
|
||||||
|
message.header = Some(val);
|
||||||
|
}
|
||||||
|
Section::DeliveryAnnotations(val) => {
|
||||||
|
message.delivery_annotations = Some(val);
|
||||||
|
}
|
||||||
|
Section::MessageAnnotations(val) => {
|
||||||
|
message.message_annotations = Some(VecSymbolMap(
|
||||||
|
val.into_iter().map(|(k, v)| (k.0.into(), v)).collect(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Section::ApplicationProperties(val) => {
|
||||||
|
message.application_properties = Some(VecStringMap(
|
||||||
|
val.into_iter().map(|(k, v)| (k.into(), v)).collect(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Section::Footer(val) => {
|
||||||
|
message.footer = Some(val);
|
||||||
|
}
|
||||||
|
Section::Properties(val) => {
|
||||||
|
message.properties = Some(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
// body
|
||||||
|
Section::AmqpSequence(val) => {
|
||||||
|
message.body.sequence.push(val);
|
||||||
|
}
|
||||||
|
Section::AmqpValue(val) => {
|
||||||
|
message.body.value = Some(val);
|
||||||
|
}
|
||||||
|
Section::Data(val) => {
|
||||||
|
message.body.data.push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if buf.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
input = buf;
|
||||||
|
}
|
||||||
|
Ok((input, message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for OutMessage {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
let size = self.size.get();
|
||||||
|
if size != 0 {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// body size, always add empty body if needed
|
||||||
|
let body_size = self.body.encoded_size();
|
||||||
|
let mut size = if body_size == 0 {
|
||||||
|
// empty bytes
|
||||||
|
SECTION_PREFIX_LENGTH + 2
|
||||||
|
} else {
|
||||||
|
body_size
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(ref h) = self.header {
|
||||||
|
size += h.encoded_size() + SECTION_PREFIX_LENGTH;
|
||||||
|
}
|
||||||
|
if let Some(ref da) = self.delivery_annotations {
|
||||||
|
size += da.encoded_size() + SECTION_PREFIX_LENGTH;
|
||||||
|
}
|
||||||
|
if let Some(ref ma) = self.message_annotations {
|
||||||
|
size += ma.encoded_size() + SECTION_PREFIX_LENGTH;
|
||||||
|
}
|
||||||
|
if let Some(ref p) = self.properties {
|
||||||
|
size += p.encoded_size();
|
||||||
|
}
|
||||||
|
if let Some(ref ap) = self.application_properties {
|
||||||
|
size += ap.encoded_size() + SECTION_PREFIX_LENGTH;
|
||||||
|
}
|
||||||
|
if let Some(ref f) = self.footer {
|
||||||
|
size += f.encoded_size() + SECTION_PREFIX_LENGTH;
|
||||||
|
}
|
||||||
|
self.size.set(size);
|
||||||
|
size
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, dst: &mut BytesMut) {
|
||||||
|
if let Some(ref h) = self.header {
|
||||||
|
h.encode(dst);
|
||||||
|
}
|
||||||
|
if let Some(ref da) = self.delivery_annotations {
|
||||||
|
Descriptor::Ulong(113).encode(dst);
|
||||||
|
da.encode(dst);
|
||||||
|
}
|
||||||
|
if let Some(ref ma) = self.message_annotations {
|
||||||
|
Descriptor::Ulong(114).encode(dst);
|
||||||
|
ma.encode(dst);
|
||||||
|
}
|
||||||
|
if let Some(ref p) = self.properties {
|
||||||
|
p.encode(dst);
|
||||||
|
}
|
||||||
|
if let Some(ref ap) = self.application_properties {
|
||||||
|
Descriptor::Ulong(116).encode(dst);
|
||||||
|
ap.encode(dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
// message body
|
||||||
|
if self.body.encoded_size() == 0 {
|
||||||
|
// special treatment for empty body
|
||||||
|
Descriptor::Ulong(117).encode(dst);
|
||||||
|
dst.put_u8(FORMATCODE_BINARY8);
|
||||||
|
dst.put_u8(0);
|
||||||
|
} else {
|
||||||
|
self.body.encode(dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
// message footer, always last item
|
||||||
|
if let Some(ref f) = self.footer {
|
||||||
|
Descriptor::Ulong(120).encode(dst);
|
||||||
|
f.encode(dst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use bytes::{Bytes, BytesMut};
|
||||||
|
use bytestring::ByteString;
|
||||||
|
|
||||||
|
use crate::codec::{Decode, Encode};
|
||||||
|
use crate::errors::AmqpCodecError;
|
||||||
|
use crate::protocol::Header;
|
||||||
|
use crate::types::Variant;
|
||||||
|
|
||||||
|
use super::OutMessage;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_properties() -> Result<(), AmqpCodecError> {
|
||||||
|
let mut msg = OutMessage::default();
|
||||||
|
msg.set_properties(|props| props.message_id = Some(1.into()));
|
||||||
|
|
||||||
|
let mut buf = BytesMut::with_capacity(msg.encoded_size());
|
||||||
|
msg.encode(&mut buf);
|
||||||
|
|
||||||
|
let msg2 = OutMessage::decode(&buf)?.1;
|
||||||
|
let props = msg2.properties.as_ref().unwrap();
|
||||||
|
assert_eq!(props.message_id, Some(1.into()));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_app_properties() -> Result<(), AmqpCodecError> {
|
||||||
|
let mut msg = OutMessage::default();
|
||||||
|
msg.set_app_property(ByteString::from("test"), 1);
|
||||||
|
|
||||||
|
let mut buf = BytesMut::with_capacity(msg.encoded_size());
|
||||||
|
msg.encode(&mut buf);
|
||||||
|
|
||||||
|
let msg2 = OutMessage::decode(&buf)?.1;
|
||||||
|
let props = msg2.application_properties.as_ref().unwrap();
|
||||||
|
assert_eq!(props[0].0.as_str(), "test");
|
||||||
|
assert_eq!(props[0].1, Variant::from(1));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_header() -> Result<(), AmqpCodecError> {
|
||||||
|
let hdr = Header {
|
||||||
|
durable: false,
|
||||||
|
priority: 1,
|
||||||
|
ttl: None,
|
||||||
|
first_acquirer: false,
|
||||||
|
delivery_count: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut msg = OutMessage::default();
|
||||||
|
msg.set_header(hdr.clone());
|
||||||
|
let mut buf = BytesMut::with_capacity(msg.encoded_size());
|
||||||
|
msg.encode(&mut buf);
|
||||||
|
|
||||||
|
let msg2 = OutMessage::decode(&buf)?.1;
|
||||||
|
assert_eq!(msg2.header().unwrap(), &hdr);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_data() -> Result<(), AmqpCodecError> {
|
||||||
|
let data = Bytes::from_static(b"test data");
|
||||||
|
|
||||||
|
let mut msg = OutMessage::default();
|
||||||
|
msg.set_body(|body| body.set_data(data.clone()));
|
||||||
|
let mut buf = BytesMut::with_capacity(msg.encoded_size());
|
||||||
|
msg.encode(&mut buf);
|
||||||
|
|
||||||
|
let msg2 = OutMessage::decode(&buf)?.1;
|
||||||
|
assert_eq!(msg2.body.data().unwrap(), &data);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_data_empty() -> Result<(), AmqpCodecError> {
|
||||||
|
let msg = OutMessage::default();
|
||||||
|
let mut buf = BytesMut::with_capacity(msg.encoded_size());
|
||||||
|
msg.encode(&mut buf);
|
||||||
|
|
||||||
|
let msg2 = OutMessage::decode(&buf)?.1;
|
||||||
|
assert_eq!(msg2.body.data().unwrap(), &Bytes::from_static(b""));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_messages() -> Result<(), AmqpCodecError> {
|
||||||
|
let mut msg1 = OutMessage::default();
|
||||||
|
msg1.set_properties(|props| props.message_id = Some(1.into()));
|
||||||
|
let mut msg2 = OutMessage::default();
|
||||||
|
msg2.set_properties(|props| props.message_id = Some(2.into()));
|
||||||
|
|
||||||
|
let mut msg = OutMessage::default();
|
||||||
|
msg.set_body(|body| {
|
||||||
|
body.messages.push(msg1.clone().into());
|
||||||
|
body.messages.push(msg2.clone().into());
|
||||||
|
});
|
||||||
|
let mut buf = BytesMut::with_capacity(msg.encoded_size());
|
||||||
|
msg.encode(&mut buf);
|
||||||
|
|
||||||
|
let msg3 = OutMessage::decode(&buf)?.1;
|
||||||
|
let msg4 = OutMessage::decode(&msg3.body.data().unwrap())?.1;
|
||||||
|
assert_eq!(msg1.properties, msg4.properties);
|
||||||
|
|
||||||
|
let msg5 = OutMessage::decode(&msg3.body.data[1])?.1;
|
||||||
|
assert_eq!(msg2.properties, msg5.properties);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
4661
actix-amqp/codec/src/protocol/definitions.rs
Executable file
4661
actix-amqp/codec/src/protocol/definitions.rs
Executable file
File diff suppressed because it is too large
Load Diff
268
actix-amqp/codec/src/protocol/mod.rs
Executable file
268
actix-amqp/codec/src/protocol/mod.rs
Executable file
@ -0,0 +1,268 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
|
use bytestring::ByteString;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use derive_more::From;
|
||||||
|
use fxhash::FxHashMap;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use super::codec::{self, DecodeFormatted, Encode};
|
||||||
|
use super::errors::AmqpParseError;
|
||||||
|
use super::message::{InMessage, OutMessage};
|
||||||
|
use super::types::*;
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct CompoundHeader {
|
||||||
|
pub size: u32,
|
||||||
|
pub count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompoundHeader {
|
||||||
|
pub fn empty() -> CompoundHeader {
|
||||||
|
CompoundHeader { size: 0, count: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||||
|
pub enum ProtocolId {
|
||||||
|
Amqp = 0,
|
||||||
|
AmqpTls = 2,
|
||||||
|
AmqpSasl = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Map = FxHashMap<Variant, Variant>;
|
||||||
|
pub type StringVariantMap = FxHashMap<Str, Variant>;
|
||||||
|
pub type Fields = FxHashMap<Symbol, Variant>;
|
||||||
|
pub type FilterSet = FxHashMap<Symbol, Option<ByteString>>;
|
||||||
|
pub type Timestamp = DateTime<Utc>;
|
||||||
|
pub type Symbols = Multiple<Symbol>;
|
||||||
|
pub type IetfLanguageTags = Multiple<IetfLanguageTag>;
|
||||||
|
pub type Annotations = FxHashMap<Symbol, Variant>;
|
||||||
|
|
||||||
|
mod definitions;
|
||||||
|
pub use self::definitions::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Clone, From, Display)]
|
||||||
|
pub enum MessageId {
|
||||||
|
#[display(fmt = "{}", _0)]
|
||||||
|
Ulong(u64),
|
||||||
|
#[display(fmt = "{}", _0)]
|
||||||
|
Uuid(Uuid),
|
||||||
|
#[display(fmt = "{:?}", _0)]
|
||||||
|
Binary(Bytes),
|
||||||
|
#[display(fmt = "{}", _0)]
|
||||||
|
String(ByteString),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<usize> for MessageId {
|
||||||
|
fn from(id: usize) -> MessageId {
|
||||||
|
MessageId::Ulong(id as u64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<i32> for MessageId {
|
||||||
|
fn from(id: i32) -> MessageId {
|
||||||
|
MessageId::Ulong(id as u64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for MessageId {
|
||||||
|
fn decode_with_format(input: &[u8], fmt: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
match fmt {
|
||||||
|
codec::FORMATCODE_SMALLULONG | codec::FORMATCODE_ULONG | codec::FORMATCODE_ULONG_0 => {
|
||||||
|
u64::decode_with_format(input, fmt).map(|(i, o)| (i, MessageId::Ulong(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_UUID => {
|
||||||
|
Uuid::decode_with_format(input, fmt).map(|(i, o)| (i, MessageId::Uuid(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_BINARY8 | codec::FORMATCODE_BINARY32 => {
|
||||||
|
Bytes::decode_with_format(input, fmt).map(|(i, o)| (i, MessageId::Binary(o)))
|
||||||
|
}
|
||||||
|
codec::FORMATCODE_STRING8 | codec::FORMATCODE_STRING32 => {
|
||||||
|
ByteString::decode_with_format(input, fmt).map(|(i, o)| (i, MessageId::String(o)))
|
||||||
|
}
|
||||||
|
_ => Err(AmqpParseError::InvalidFormatCode(fmt)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for MessageId {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
match *self {
|
||||||
|
MessageId::Ulong(v) => v.encoded_size(),
|
||||||
|
MessageId::Uuid(ref v) => v.encoded_size(),
|
||||||
|
MessageId::Binary(ref v) => v.encoded_size(),
|
||||||
|
MessageId::String(ref v) => v.encoded_size(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
match *self {
|
||||||
|
MessageId::Ulong(v) => v.encode(buf),
|
||||||
|
MessageId::Uuid(ref v) => v.encode(buf),
|
||||||
|
MessageId::Binary(ref v) => v.encode(buf),
|
||||||
|
MessageId::String(ref v) => v.encode(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, From)]
|
||||||
|
pub enum ErrorCondition {
|
||||||
|
AmqpError(AmqpError),
|
||||||
|
ConnectionError(ConnectionError),
|
||||||
|
SessionError(SessionError),
|
||||||
|
LinkError(LinkError),
|
||||||
|
Custom(Symbol),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for ErrorCondition {
|
||||||
|
#[inline]
|
||||||
|
fn decode_with_format(input: &[u8], format: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
let (input, result) = Symbol::decode_with_format(input, format)?;
|
||||||
|
if let Ok(r) = AmqpError::try_from(&result) {
|
||||||
|
return Ok((input, ErrorCondition::AmqpError(r)));
|
||||||
|
}
|
||||||
|
if let Ok(r) = ConnectionError::try_from(&result) {
|
||||||
|
return Ok((input, ErrorCondition::ConnectionError(r)));
|
||||||
|
}
|
||||||
|
if let Ok(r) = SessionError::try_from(&result) {
|
||||||
|
return Ok((input, ErrorCondition::SessionError(r)));
|
||||||
|
}
|
||||||
|
if let Ok(r) = LinkError::try_from(&result) {
|
||||||
|
return Ok((input, ErrorCondition::LinkError(r)));
|
||||||
|
}
|
||||||
|
Ok((input, ErrorCondition::Custom(result)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for ErrorCondition {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
match *self {
|
||||||
|
ErrorCondition::AmqpError(ref v) => v.encoded_size(),
|
||||||
|
ErrorCondition::ConnectionError(ref v) => v.encoded_size(),
|
||||||
|
ErrorCondition::SessionError(ref v) => v.encoded_size(),
|
||||||
|
ErrorCondition::LinkError(ref v) => v.encoded_size(),
|
||||||
|
ErrorCondition::Custom(ref v) => v.encoded_size(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
match *self {
|
||||||
|
ErrorCondition::AmqpError(ref v) => v.encode(buf),
|
||||||
|
ErrorCondition::ConnectionError(ref v) => v.encode(buf),
|
||||||
|
ErrorCondition::SessionError(ref v) => v.encode(buf),
|
||||||
|
ErrorCondition::LinkError(ref v) => v.encode(buf),
|
||||||
|
ErrorCondition::Custom(ref v) => v.encode(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum DistributionMode {
|
||||||
|
Move,
|
||||||
|
Copy,
|
||||||
|
Custom(Symbol),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFormatted for DistributionMode {
|
||||||
|
fn decode_with_format(input: &[u8], format: u8) -> Result<(&[u8], Self), AmqpParseError> {
|
||||||
|
let (input, result) = Symbol::decode_with_format(input, format)?;
|
||||||
|
let result = match result.as_str() {
|
||||||
|
"move" => DistributionMode::Move,
|
||||||
|
"copy" => DistributionMode::Copy,
|
||||||
|
_ => DistributionMode::Custom(result),
|
||||||
|
};
|
||||||
|
Ok((input, result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for DistributionMode {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
match *self {
|
||||||
|
DistributionMode::Move => 6,
|
||||||
|
DistributionMode::Copy => 6,
|
||||||
|
DistributionMode::Custom(ref v) => v.encoded_size(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(&self, buf: &mut BytesMut) {
|
||||||
|
match *self {
|
||||||
|
DistributionMode::Move => Symbol::from("move").encode(buf),
|
||||||
|
DistributionMode::Copy => Symbol::from("copy").encode(buf),
|
||||||
|
DistributionMode::Custom(ref v) => v.encode(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SaslInit {
|
||||||
|
pub fn prepare_response(authz_id: &str, authn_id: &str, password: &str) -> Bytes {
|
||||||
|
Bytes::from(format!("{}\x00{}\x00{}", authz_id, authn_id, password))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Properties {
|
||||||
|
fn default() -> Properties {
|
||||||
|
Properties {
|
||||||
|
message_id: None,
|
||||||
|
user_id: None,
|
||||||
|
to: None,
|
||||||
|
subject: None,
|
||||||
|
reply_to: None,
|
||||||
|
correlation_id: None,
|
||||||
|
content_type: None,
|
||||||
|
content_encoding: None,
|
||||||
|
absolute_expiry_time: None,
|
||||||
|
creation_time: None,
|
||||||
|
group_id: None,
|
||||||
|
group_sequence: None,
|
||||||
|
reply_to_group_id: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, From, PartialEq)]
|
||||||
|
pub enum TransferBody {
|
||||||
|
Data(Bytes),
|
||||||
|
MessageIn(InMessage),
|
||||||
|
MessageOut(OutMessage),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransferBody {
|
||||||
|
#[inline]
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.encoded_size()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn message_format(&self) -> Option<MessageFormat> {
|
||||||
|
match self {
|
||||||
|
TransferBody::Data(_) => None,
|
||||||
|
TransferBody::MessageIn(ref data) => data.message_format,
|
||||||
|
TransferBody::MessageOut(ref data) => data.message_format,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for TransferBody {
|
||||||
|
fn encoded_size(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
TransferBody::Data(ref data) => data.len(),
|
||||||
|
TransferBody::MessageIn(ref data) => data.encoded_size(),
|
||||||
|
TransferBody::MessageOut(ref data) => data.encoded_size(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn encode(&self, dst: &mut BytesMut) {
|
||||||
|
match *self {
|
||||||
|
TransferBody::Data(ref data) => dst.put_slice(&data),
|
||||||
|
TransferBody::MessageIn(ref data) => data.encode(dst),
|
||||||
|
TransferBody::MessageOut(ref data) => data.encode(dst),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
194
actix-amqp/codec/src/types/mod.rs
Executable file
194
actix-amqp/codec/src/types/mod.rs
Executable file
@ -0,0 +1,194 @@
|
|||||||
|
use std::{borrow, fmt, hash, ops};
|
||||||
|
|
||||||
|
use bytestring::ByteString;
|
||||||
|
|
||||||
|
mod symbol;
|
||||||
|
mod variant;
|
||||||
|
|
||||||
|
pub use self::symbol::{StaticSymbol, Symbol};
|
||||||
|
pub use self::variant::{Variant, VariantMap, VecStringMap, VecSymbolMap};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Hash, Display)]
|
||||||
|
pub enum Descriptor {
|
||||||
|
Ulong(u64),
|
||||||
|
Symbol(Symbol),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Hash, From)]
|
||||||
|
pub struct Multiple<T>(pub Vec<T>);
|
||||||
|
|
||||||
|
impl<T> Multiple<T> {
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.0.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> ::std::slice::Iter<T> {
|
||||||
|
self.0.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for Multiple<T> {
|
||||||
|
fn default() -> Multiple<T> {
|
||||||
|
Multiple(Vec::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ops::Deref for Multiple<T> {
|
||||||
|
type Target = Vec<T>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ops::DerefMut for Multiple<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||||
|
pub struct List(pub Vec<Variant>);
|
||||||
|
|
||||||
|
impl List {
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.0.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> ::std::slice::Iter<Variant> {
|
||||||
|
self.0.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Display, Clone, Eq, Ord, PartialOrd)]
|
||||||
|
pub enum Str {
|
||||||
|
String(String),
|
||||||
|
ByteStr(ByteString),
|
||||||
|
Static(&'static str),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Str {
|
||||||
|
pub fn from_str(s: &str) -> Str {
|
||||||
|
Str::ByteStr(ByteString::from(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
|
match self {
|
||||||
|
Str::String(s) => s.as_ref(),
|
||||||
|
Str::ByteStr(s) => s.as_ref(),
|
||||||
|
Str::Static(s) => s.as_bytes(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Str::String(s) => s.as_str(),
|
||||||
|
Str::ByteStr(s) => s.as_ref(),
|
||||||
|
Str::Static(s) => s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes_str(&self) -> ByteString {
|
||||||
|
match self {
|
||||||
|
Str::String(s) => ByteString::from(s.as_str()),
|
||||||
|
Str::ByteStr(s) => s.clone(),
|
||||||
|
Str::Static(s) => ByteString::from_static(s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Str::String(s) => s.len(),
|
||||||
|
Str::ByteStr(s) => s.len(),
|
||||||
|
Str::Static(s) => s.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static str> for Str {
|
||||||
|
fn from(s: &'static str) -> Str {
|
||||||
|
Str::Static(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ByteString> for Str {
|
||||||
|
fn from(s: ByteString) -> Str {
|
||||||
|
Str::ByteStr(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for Str {
|
||||||
|
fn from(s: String) -> Str {
|
||||||
|
Str::String(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl hash::Hash for Str {
|
||||||
|
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||||||
|
match self {
|
||||||
|
Str::String(s) => (&*s).hash(state),
|
||||||
|
Str::ByteStr(s) => (&*s).hash(state),
|
||||||
|
Str::Static(s) => s.hash(state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl borrow::Borrow<str> for Str {
|
||||||
|
fn borrow(&self) -> &str {
|
||||||
|
self.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<Str> for Str {
|
||||||
|
fn eq(&self, other: &Str) -> bool {
|
||||||
|
match self {
|
||||||
|
Str::String(s) => match other {
|
||||||
|
Str::String(o) => s == o,
|
||||||
|
Str::ByteStr(o) => o == s.as_str(),
|
||||||
|
Str::Static(o) => s == *o,
|
||||||
|
},
|
||||||
|
Str::ByteStr(s) => match other {
|
||||||
|
Str::String(o) => s == o.as_str(),
|
||||||
|
Str::ByteStr(o) => s == o,
|
||||||
|
Str::Static(o) => s == *o,
|
||||||
|
},
|
||||||
|
Str::Static(s) => match other {
|
||||||
|
Str::String(o) => o == s,
|
||||||
|
Str::ByteStr(o) => o == *s,
|
||||||
|
Str::Static(o) => s == o,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<str> for Str {
|
||||||
|
fn eq(&self, other: &str) -> bool {
|
||||||
|
match self {
|
||||||
|
Str::String(ref s) => s == other,
|
||||||
|
Str::ByteStr(ref s) => {
|
||||||
|
// workaround for possible compiler bug
|
||||||
|
let t: &str = &*s;
|
||||||
|
t == other
|
||||||
|
}
|
||||||
|
Str::Static(s) => *s == other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Str {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Str::String(s) => write!(f, "ST:\"{}\"", s),
|
||||||
|
Str::ByteStr(s) => write!(f, "B:\"{}\"", &*s),
|
||||||
|
Str::Static(s) => write!(f, "S:\"{}\"", s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
actix-amqp/codec/src/types/symbol.rs
Executable file
75
actix-amqp/codec/src/types/symbol.rs
Executable file
@ -0,0 +1,75 @@
|
|||||||
|
use std::{borrow, str};
|
||||||
|
|
||||||
|
use bytestring::ByteString;
|
||||||
|
|
||||||
|
use super::Str;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, Display)]
|
||||||
|
pub struct Symbol(pub Str);
|
||||||
|
|
||||||
|
impl Symbol {
|
||||||
|
pub fn from_slice(s: &str) -> Symbol {
|
||||||
|
Symbol(Str::ByteStr(ByteString::from(s)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
|
self.0.as_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
self.0.as_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes_str(&self) -> ByteString {
|
||||||
|
self.0.to_bytes_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static str> for Symbol {
|
||||||
|
fn from(s: &'static str) -> Symbol {
|
||||||
|
Symbol(Str::Static(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Str> for Symbol {
|
||||||
|
fn from(s: Str) -> Symbol {
|
||||||
|
Symbol(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::string::String> for Symbol {
|
||||||
|
fn from(s: std::string::String) -> Symbol {
|
||||||
|
Symbol(Str::from(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ByteString> for Symbol {
|
||||||
|
fn from(s: ByteString) -> Symbol {
|
||||||
|
Symbol(Str::ByteStr(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl borrow::Borrow<str> for Symbol {
|
||||||
|
fn borrow(&self) -> &str {
|
||||||
|
self.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<str> for Symbol {
|
||||||
|
fn eq(&self, other: &str) -> bool {
|
||||||
|
self.0 == *other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Display)]
|
||||||
|
pub struct StaticSymbol(pub &'static str);
|
||||||
|
|
||||||
|
impl From<&'static str> for StaticSymbol {
|
||||||
|
fn from(s: &'static str) -> StaticSymbol {
|
||||||
|
StaticSymbol(s)
|
||||||
|
}
|
||||||
|
}
|
268
actix-amqp/codec/src/types/variant.rs
Executable file
268
actix-amqp/codec/src/types/variant.rs
Executable file
@ -0,0 +1,268 @@
|
|||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
|
use bytes::Bytes;
|
||||||
|
use bytestring::ByteString;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use fxhash::FxHashMap;
|
||||||
|
use ordered_float::OrderedFloat;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::protocol::Annotations;
|
||||||
|
use crate::types::{Descriptor, List, StaticSymbol, Str, Symbol};
|
||||||
|
|
||||||
|
/// Represents an AMQP type for use in polymorphic collections
|
||||||
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, Display, From)]
|
||||||
|
pub enum Variant {
|
||||||
|
/// Indicates an empty value.
|
||||||
|
Null,
|
||||||
|
|
||||||
|
/// Represents a true or false value.
|
||||||
|
Boolean(bool),
|
||||||
|
|
||||||
|
/// Integer in the range 0 to 2^8 - 1 inclusive.
|
||||||
|
Ubyte(u8),
|
||||||
|
|
||||||
|
/// Integer in the range 0 to 2^16 - 1 inclusive.
|
||||||
|
Ushort(u16),
|
||||||
|
|
||||||
|
/// Integer in the range 0 to 2^32 - 1 inclusive.
|
||||||
|
Uint(u32),
|
||||||
|
|
||||||
|
/// Integer in the range 0 to 2^64 - 1 inclusive.
|
||||||
|
Ulong(u64),
|
||||||
|
|
||||||
|
/// Integer in the range 0 to 2^7 - 1 inclusive.
|
||||||
|
Byte(i8),
|
||||||
|
|
||||||
|
/// Integer in the range 0 to 2^15 - 1 inclusive.
|
||||||
|
Short(i16),
|
||||||
|
|
||||||
|
/// Integer in the range 0 to 2^32 - 1 inclusive.
|
||||||
|
Int(i32),
|
||||||
|
|
||||||
|
/// Integer in the range 0 to 2^64 - 1 inclusive.
|
||||||
|
Long(i64),
|
||||||
|
|
||||||
|
/// 32-bit floating point number (IEEE 754-2008 binary32).
|
||||||
|
Float(OrderedFloat<f32>),
|
||||||
|
|
||||||
|
/// 64-bit floating point number (IEEE 754-2008 binary64).
|
||||||
|
Double(OrderedFloat<f64>),
|
||||||
|
|
||||||
|
// Decimal32(d32),
|
||||||
|
// Decimal64(d64),
|
||||||
|
// Decimal128(d128),
|
||||||
|
/// A single Unicode character.
|
||||||
|
Char(char),
|
||||||
|
|
||||||
|
/// An absolute point in time.
|
||||||
|
/// Represents an approximate point in time using the Unix time encoding of
|
||||||
|
/// UTC with a precision of milliseconds. For example, 1311704463521
|
||||||
|
/// represents the moment 2011-07-26T18:21:03.521Z.
|
||||||
|
Timestamp(DateTime<Utc>),
|
||||||
|
|
||||||
|
/// A universally unique identifier as defined by RFC-4122 section 4.1.2
|
||||||
|
Uuid(Uuid),
|
||||||
|
|
||||||
|
/// A sequence of octets.
|
||||||
|
#[display(fmt = "Binary({:?})", _0)]
|
||||||
|
Binary(Bytes),
|
||||||
|
|
||||||
|
/// A sequence of Unicode characters
|
||||||
|
String(Str),
|
||||||
|
|
||||||
|
/// Symbolic values from a constrained domain.
|
||||||
|
Symbol(Symbol),
|
||||||
|
|
||||||
|
/// Same as Symbol but for static refs
|
||||||
|
StaticSymbol(StaticSymbol),
|
||||||
|
|
||||||
|
/// List
|
||||||
|
#[display(fmt = "List({:?})", _0)]
|
||||||
|
List(List),
|
||||||
|
|
||||||
|
/// Map
|
||||||
|
Map(VariantMap),
|
||||||
|
|
||||||
|
/// Described value
|
||||||
|
#[display(fmt = "Described{:?}", _0)]
|
||||||
|
Described((Descriptor, Box<Variant>)),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ByteString> for Variant {
|
||||||
|
fn from(s: ByteString) -> Self {
|
||||||
|
Str::from(s).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for Variant {
|
||||||
|
fn from(s: String) -> Self {
|
||||||
|
Str::from(ByteString::from(s)).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static str> for Variant {
|
||||||
|
fn from(s: &'static str) -> Self {
|
||||||
|
Str::from(s).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<str> for Variant {
|
||||||
|
fn eq(&self, other: &str) -> bool {
|
||||||
|
match self {
|
||||||
|
Variant::String(s) => s == other,
|
||||||
|
Variant::Symbol(s) => s == other,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Variant {
|
||||||
|
pub fn as_str(&self) -> Option<&str> {
|
||||||
|
match self {
|
||||||
|
Variant::String(s) => Some(s.as_str()),
|
||||||
|
Variant::Symbol(s) => Some(s.as_str()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_int(&self) -> Option<i32> {
|
||||||
|
match self {
|
||||||
|
Variant::Int(v) => Some(*v as i32),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_long(&self) -> Option<i64> {
|
||||||
|
match self {
|
||||||
|
Variant::Ubyte(v) => Some(*v as i64),
|
||||||
|
Variant::Ushort(v) => Some(*v as i64),
|
||||||
|
Variant::Uint(v) => Some(*v as i64),
|
||||||
|
Variant::Ulong(v) => Some(*v as i64),
|
||||||
|
Variant::Byte(v) => Some(*v as i64),
|
||||||
|
Variant::Short(v) => Some(*v as i64),
|
||||||
|
Variant::Int(v) => Some(*v as i64),
|
||||||
|
Variant::Long(v) => Some(*v as i64),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes_str(&self) -> Option<ByteString> {
|
||||||
|
match self {
|
||||||
|
Variant::String(s) => Some(s.to_bytes_str()),
|
||||||
|
Variant::Symbol(s) => Some(s.to_bytes_str()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone, Debug, Display)]
|
||||||
|
#[display(fmt = "{:?}", map)]
|
||||||
|
pub struct VariantMap {
|
||||||
|
pub map: FxHashMap<Variant, Variant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VariantMap {
|
||||||
|
pub fn new(map: FxHashMap<Variant, Variant>) -> VariantMap {
|
||||||
|
VariantMap { map }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for VariantMap {
|
||||||
|
fn hash<H: Hasher>(&self, _state: &mut H) {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Debug, Display)]
|
||||||
|
#[display(fmt = "{:?}", _0)]
|
||||||
|
pub struct VecSymbolMap(pub Vec<(Symbol, Variant)>);
|
||||||
|
|
||||||
|
impl Default for VecSymbolMap {
|
||||||
|
fn default() -> Self {
|
||||||
|
VecSymbolMap(Vec::with_capacity(8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Annotations> for VecSymbolMap {
|
||||||
|
fn from(anns: Annotations) -> VecSymbolMap {
|
||||||
|
VecSymbolMap(anns.into_iter().collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for VecSymbolMap {
|
||||||
|
type Target = Vec<(Symbol, Variant)>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::DerefMut for VecSymbolMap {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Debug, Display)]
|
||||||
|
#[display(fmt = "{:?}", _0)]
|
||||||
|
pub struct VecStringMap(pub Vec<(Str, Variant)>);
|
||||||
|
|
||||||
|
impl Default for VecStringMap {
|
||||||
|
fn default() -> Self {
|
||||||
|
VecStringMap(Vec::with_capacity(8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FxHashMap<Str, Variant>> for VecStringMap {
|
||||||
|
fn from(map: FxHashMap<Str, Variant>) -> VecStringMap {
|
||||||
|
VecStringMap(map.into_iter().collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for VecStringMap {
|
||||||
|
type Target = Vec<(Str, Variant)>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::DerefMut for VecStringMap {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bytes_eq() {
|
||||||
|
let bytes1 = Variant::Binary(Bytes::from(&b"hello"[..]));
|
||||||
|
let bytes2 = Variant::Binary(Bytes::from(&b"hello"[..]));
|
||||||
|
let bytes3 = Variant::Binary(Bytes::from(&b"world"[..]));
|
||||||
|
|
||||||
|
assert_eq!(bytes1, bytes2);
|
||||||
|
assert!(bytes1 != bytes3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_eq() {
|
||||||
|
let a = Variant::String(ByteString::from("hello").into());
|
||||||
|
let b = Variant::String(ByteString::from("world!").into());
|
||||||
|
|
||||||
|
assert_eq!(Variant::String(ByteString::from("hello").into()), a);
|
||||||
|
assert!(a != b);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn symbol_eq() {
|
||||||
|
let a = Variant::Symbol(Symbol::from("hello"));
|
||||||
|
let b = Variant::Symbol(Symbol::from("world!"));
|
||||||
|
|
||||||
|
assert_eq!(Variant::Symbol(Symbol::from("hello")), a);
|
||||||
|
assert!(a != b);
|
||||||
|
}
|
||||||
|
}
|
72
actix-amqp/src/cell.rs
Executable file
72
actix-amqp/src/cell.rs
Executable file
@ -0,0 +1,72 @@
|
|||||||
|
//! Custom cell impl
|
||||||
|
use std::cell::UnsafeCell;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::rc::{Rc, Weak};
|
||||||
|
|
||||||
|
pub(crate) struct Cell<T> {
|
||||||
|
inner: Rc<UnsafeCell<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct WeakCell<T> {
|
||||||
|
inner: Weak<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 downgrade(&self) -> WeakCell<T> {
|
||||||
|
WeakCell {
|
||||||
|
inner: Rc::downgrade(&self.inner),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ref(&self) -> &T {
|
||||||
|
unsafe { &*self.inner.as_ref().get() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&self) -> &mut T {
|
||||||
|
unsafe { &mut *self.inner.as_ref().get() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: std::fmt::Debug> std::fmt::Debug for WeakCell<T> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
self.inner.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> WeakCell<T> {
|
||||||
|
pub fn upgrade(&self) -> Option<Cell<T>> {
|
||||||
|
if let Some(inner) = self.inner.upgrade() {
|
||||||
|
Some(Cell { inner })
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
actix-amqp/src/client/connect.rs
Executable file
11
actix-amqp/src/client/connect.rs
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
use actix_codec::Framed;
|
||||||
|
|
||||||
|
use crate::Configuration;
|
||||||
|
|
||||||
|
trait IntoFramed<T, U: Default> {
|
||||||
|
fn into_framed(self) -> Framed<T, U>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Handshake {
|
||||||
|
_cfg: Configuration,
|
||||||
|
}
|
5
actix-amqp/src/client/mod.rs
Executable file
5
actix-amqp/src/client/mod.rs
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
mod connect;
|
||||||
|
mod protocol;
|
||||||
|
|
||||||
|
pub use self::connect::Handshake;
|
||||||
|
pub use self::protocol::ProtocolNegotiation;
|
78
actix-amqp/src/client/protocol.rs
Executable file
78
actix-amqp/src/client/protocol.rs
Executable file
@ -0,0 +1,78 @@
|
|||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||||
|
use actix_service::Service;
|
||||||
|
use futures::{Future, SinkExt, StreamExt};
|
||||||
|
|
||||||
|
use amqp_codec::protocol::ProtocolId;
|
||||||
|
use amqp_codec::{ProtocolIdCodec, ProtocolIdError};
|
||||||
|
|
||||||
|
pub struct ProtocolNegotiation<Io> {
|
||||||
|
proto: ProtocolId,
|
||||||
|
_r: PhantomData<Io>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io> Clone for ProtocolNegotiation<Io> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
ProtocolNegotiation {
|
||||||
|
proto: self.proto.clone(),
|
||||||
|
_r: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io> ProtocolNegotiation<Io> {
|
||||||
|
pub fn new(proto: ProtocolId) -> Self {
|
||||||
|
ProtocolNegotiation {
|
||||||
|
proto,
|
||||||
|
_r: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn framed(stream: Io) -> Framed<Io, ProtocolIdCodec>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite,
|
||||||
|
{
|
||||||
|
Framed::new(stream, ProtocolIdCodec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io> Default for ProtocolNegotiation<Io> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new(ProtocolId::Amqp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io> Service for ProtocolNegotiation<Io>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
{
|
||||||
|
type Request = Framed<Io, ProtocolIdCodec>;
|
||||||
|
type Response = Framed<Io, ProtocolIdCodec>;
|
||||||
|
type Error = ProtocolIdError;
|
||||||
|
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, mut framed: Framed<Io, ProtocolIdCodec>) -> Self::Future {
|
||||||
|
let proto = self.proto;
|
||||||
|
|
||||||
|
Box::pin(async move {
|
||||||
|
framed.send(proto).await?;
|
||||||
|
let protocol = framed.next().await.ok_or(ProtocolIdError::Disconnected)??;
|
||||||
|
|
||||||
|
if proto == protocol {
|
||||||
|
Ok(framed)
|
||||||
|
} else {
|
||||||
|
Err(ProtocolIdError::Unexpected {
|
||||||
|
exp: proto,
|
||||||
|
got: protocol,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
577
actix-amqp/src/connection.rs
Executable file
577
actix-amqp/src/connection.rs
Executable file
@ -0,0 +1,577 @@
|
|||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||||
|
use actix_utils::oneshot;
|
||||||
|
use actix_utils::task::LocalWaker;
|
||||||
|
use actix_utils::time::LowResTimeService;
|
||||||
|
use futures::future::{err, Either};
|
||||||
|
use futures::{future, Sink, Stream};
|
||||||
|
use fxhash::FxHashMap;
|
||||||
|
|
||||||
|
use amqp_codec::protocol::{Begin, Close, End, Error, Frame};
|
||||||
|
use amqp_codec::{AmqpCodec, AmqpCodecError, AmqpFrame};
|
||||||
|
|
||||||
|
use crate::cell::{Cell, WeakCell};
|
||||||
|
use crate::errors::AmqpTransportError;
|
||||||
|
use crate::hb::{Heartbeat, HeartbeatAction};
|
||||||
|
use crate::session::{Session, SessionInner};
|
||||||
|
use crate::Configuration;
|
||||||
|
|
||||||
|
pub struct Connection<T: AsyncRead + AsyncWrite> {
|
||||||
|
inner: Cell<ConnectionInner>,
|
||||||
|
framed: Framed<T, AmqpCodec<AmqpFrame>>,
|
||||||
|
hb: Heartbeat,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum ChannelState {
|
||||||
|
Opening(Option<oneshot::Sender<Session>>, WeakCell<ConnectionInner>),
|
||||||
|
Established(Cell<SessionInner>),
|
||||||
|
Closing(Option<oneshot::Sender<Result<(), AmqpTransportError>>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChannelState {
|
||||||
|
fn is_opening(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
ChannelState::Opening(_, _) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct ConnectionInner {
|
||||||
|
local: Configuration,
|
||||||
|
remote: Configuration,
|
||||||
|
write_queue: VecDeque<AmqpFrame>,
|
||||||
|
write_task: LocalWaker,
|
||||||
|
sessions: slab::Slab<ChannelState>,
|
||||||
|
sessions_map: FxHashMap<u16, usize>,
|
||||||
|
error: Option<AmqpTransportError>,
|
||||||
|
state: State,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
enum State {
|
||||||
|
Normal,
|
||||||
|
Closing,
|
||||||
|
RemoteClose,
|
||||||
|
Drop,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsyncRead + AsyncWrite> Connection<T> {
|
||||||
|
pub fn new(
|
||||||
|
framed: Framed<T, AmqpCodec<AmqpFrame>>,
|
||||||
|
local: Configuration,
|
||||||
|
remote: Configuration,
|
||||||
|
time: Option<LowResTimeService>,
|
||||||
|
) -> Connection<T> {
|
||||||
|
Connection {
|
||||||
|
framed,
|
||||||
|
hb: Heartbeat::new(
|
||||||
|
local.timeout().unwrap(),
|
||||||
|
remote.timeout(),
|
||||||
|
time.unwrap_or_else(|| LowResTimeService::with(Duration::from_secs(1))),
|
||||||
|
),
|
||||||
|
inner: Cell::new(ConnectionInner::new(local, remote)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn new_server(
|
||||||
|
framed: Framed<T, AmqpCodec<AmqpFrame>>,
|
||||||
|
inner: Cell<ConnectionInner>,
|
||||||
|
time: Option<LowResTimeService>,
|
||||||
|
) -> Connection<T> {
|
||||||
|
let l_timeout = inner.get_ref().local.timeout().unwrap();
|
||||||
|
let r_timeout = inner.get_ref().remote.timeout();
|
||||||
|
Connection {
|
||||||
|
framed,
|
||||||
|
inner,
|
||||||
|
hb: Heartbeat::new(
|
||||||
|
l_timeout,
|
||||||
|
r_timeout,
|
||||||
|
time.unwrap_or_else(|| LowResTimeService::with(Duration::from_secs(1))),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connection controller
|
||||||
|
pub fn controller(&self) -> ConnectionController {
|
||||||
|
ConnectionController(self.inner.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get remote configuration
|
||||||
|
pub fn remote_config(&self) -> &Configuration {
|
||||||
|
&self.inner.get_ref().remote
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gracefully close connection
|
||||||
|
pub fn close(&mut self) -> impl Future<Output = Result<(), AmqpTransportError>> {
|
||||||
|
future::ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: implement
|
||||||
|
/// Close connection with error
|
||||||
|
pub fn close_with_error(
|
||||||
|
&mut self,
|
||||||
|
_err: Error,
|
||||||
|
) -> impl Future<Output = Result<(), AmqpTransportError>> {
|
||||||
|
future::ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opens the session
|
||||||
|
pub fn open_session(&mut self) -> impl Future<Output = Result<Session, AmqpTransportError>> {
|
||||||
|
let cell = self.inner.downgrade();
|
||||||
|
let inner = self.inner.clone();
|
||||||
|
|
||||||
|
async move {
|
||||||
|
let inner = inner.get_mut();
|
||||||
|
|
||||||
|
if let Some(ref e) = inner.error {
|
||||||
|
Err(e.clone())
|
||||||
|
} else {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|
||||||
|
let entry = inner.sessions.vacant_entry();
|
||||||
|
let token = entry.key();
|
||||||
|
|
||||||
|
if token >= inner.local.channel_max {
|
||||||
|
Err(AmqpTransportError::TooManyChannels)
|
||||||
|
} else {
|
||||||
|
entry.insert(ChannelState::Opening(Some(tx), cell));
|
||||||
|
|
||||||
|
let begin = Begin {
|
||||||
|
remote_channel: None,
|
||||||
|
next_outgoing_id: 1,
|
||||||
|
incoming_window: std::u32::MAX,
|
||||||
|
outgoing_window: std::u32::MAX,
|
||||||
|
handle_max: std::u32::MAX,
|
||||||
|
offered_capabilities: None,
|
||||||
|
desired_capabilities: None,
|
||||||
|
properties: None,
|
||||||
|
};
|
||||||
|
inner.post_frame(AmqpFrame::new(token as u16, begin.into()));
|
||||||
|
|
||||||
|
rx.await.map_err(|_| AmqpTransportError::Disconnected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get session by id. This method panics if session does not exists or in opening/closing state.
|
||||||
|
pub(crate) fn get_session(&self, id: usize) -> Cell<SessionInner> {
|
||||||
|
if let Some(channel) = self.inner.get_ref().sessions.get(id) {
|
||||||
|
if let ChannelState::Established(ref session) = channel {
|
||||||
|
return session.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic!("Session not found: {}", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn register_remote_session(&mut self, channel_id: u16, begin: &Begin) {
|
||||||
|
trace!("remote session opened: {:?}", channel_id);
|
||||||
|
|
||||||
|
let cell = self.inner.clone();
|
||||||
|
let inner = self.inner.get_mut();
|
||||||
|
let entry = inner.sessions.vacant_entry();
|
||||||
|
let token = entry.key();
|
||||||
|
|
||||||
|
let session = Cell::new(SessionInner::new(
|
||||||
|
token,
|
||||||
|
false,
|
||||||
|
ConnectionController(cell),
|
||||||
|
token as u16,
|
||||||
|
begin.next_outgoing_id(),
|
||||||
|
begin.incoming_window(),
|
||||||
|
begin.outgoing_window(),
|
||||||
|
));
|
||||||
|
entry.insert(ChannelState::Established(session));
|
||||||
|
inner.sessions_map.insert(channel_id, token);
|
||||||
|
|
||||||
|
let begin = Begin {
|
||||||
|
remote_channel: Some(channel_id),
|
||||||
|
next_outgoing_id: 1,
|
||||||
|
incoming_window: std::u32::MAX,
|
||||||
|
outgoing_window: begin.incoming_window(),
|
||||||
|
handle_max: std::u32::MAX,
|
||||||
|
offered_capabilities: None,
|
||||||
|
desired_capabilities: None,
|
||||||
|
properties: None,
|
||||||
|
};
|
||||||
|
inner.post_frame(AmqpFrame::new(token as u16, begin.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn send_frame(&mut self, frame: AmqpFrame) {
|
||||||
|
self.inner.get_mut().post_frame(frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn register_write_task(&self, cx: &mut Context) {
|
||||||
|
self.inner.write_task.register(cx.waker());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn poll_outgoing(&mut self, cx: &mut Context) -> Poll<Result<(), AmqpCodecError>> {
|
||||||
|
let inner = self.inner.get_mut();
|
||||||
|
let mut update = false;
|
||||||
|
loop {
|
||||||
|
while !self.framed.is_write_buf_full() {
|
||||||
|
if let Some(frame) = inner.pop_next_frame() {
|
||||||
|
trace!("outgoing: {:#?}", frame);
|
||||||
|
update = true;
|
||||||
|
if let Err(e) = self.framed.write(frame) {
|
||||||
|
inner.set_error(e.clone().into());
|
||||||
|
return Poll::Ready(Err(e));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.framed.is_write_buf_empty() {
|
||||||
|
match self.framed.flush(cx) {
|
||||||
|
Poll::Pending => break,
|
||||||
|
Poll::Ready(Err(e)) => {
|
||||||
|
trace!("error sending data: {}", e);
|
||||||
|
inner.set_error(e.clone().into());
|
||||||
|
return Poll::Ready(Err(e));
|
||||||
|
}
|
||||||
|
Poll::Ready(_) => (),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.hb.update_remote(update);
|
||||||
|
|
||||||
|
if inner.state == State::Drop {
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
} else if inner.state == State::RemoteClose
|
||||||
|
&& inner.write_queue.is_empty()
|
||||||
|
&& self.framed.is_write_buf_empty()
|
||||||
|
{
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
} else {
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn poll_incoming(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut Context,
|
||||||
|
) -> Poll<Option<Result<AmqpFrame, AmqpCodecError>>> {
|
||||||
|
let inner = self.inner.get_mut();
|
||||||
|
|
||||||
|
let mut update = false;
|
||||||
|
loop {
|
||||||
|
match Pin::new(&mut self.framed).poll_next(cx) {
|
||||||
|
Poll::Ready(Some(Ok(frame))) => {
|
||||||
|
trace!("incoming: {:#?}", frame);
|
||||||
|
|
||||||
|
update = true;
|
||||||
|
|
||||||
|
if let Frame::Empty = frame.performative() {
|
||||||
|
self.hb.update_local(update);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle connection close
|
||||||
|
if let Frame::Close(ref close) = frame.performative() {
|
||||||
|
inner.set_error(AmqpTransportError::Closed(close.error.clone()));
|
||||||
|
|
||||||
|
if inner.state == State::Closing {
|
||||||
|
inner.sessions.clear();
|
||||||
|
return Poll::Ready(None);
|
||||||
|
} else {
|
||||||
|
let close = Close { error: None };
|
||||||
|
inner.post_frame(AmqpFrame::new(0, close.into()));
|
||||||
|
inner.state = State::RemoteClose;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if inner.error.is_some() {
|
||||||
|
error!("connection closed but new framed is received: {:?}", frame);
|
||||||
|
return Poll::Ready(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get local session id
|
||||||
|
let channel_id =
|
||||||
|
if let Some(token) = inner.sessions_map.get(&frame.channel_id()) {
|
||||||
|
*token
|
||||||
|
} else {
|
||||||
|
// we dont have channel info, only Begin frame is allowed on new channel
|
||||||
|
if let Frame::Begin(ref begin) = frame.performative() {
|
||||||
|
if begin.remote_channel().is_some() {
|
||||||
|
inner.complete_session_creation(frame.channel_id(), begin);
|
||||||
|
} else {
|
||||||
|
return Poll::Ready(Some(Ok(frame)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Unexpected frame: {:#?}", frame);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// handle session frames
|
||||||
|
if let Some(channel) = inner.sessions.get_mut(channel_id) {
|
||||||
|
match channel {
|
||||||
|
ChannelState::Opening(_, _) => {
|
||||||
|
error!("Unexpected opening state: {}", channel_id);
|
||||||
|
}
|
||||||
|
ChannelState::Established(ref mut session) => {
|
||||||
|
match frame.performative() {
|
||||||
|
Frame::Attach(attach) => {
|
||||||
|
let cell = session.clone();
|
||||||
|
if !session.get_mut().handle_attach(attach, cell) {
|
||||||
|
return Poll::Ready(Some(Ok(frame)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Frame::Flow(_) | Frame::Detach(_) => {
|
||||||
|
return Poll::Ready(Some(Ok(frame)));
|
||||||
|
}
|
||||||
|
Frame::End(remote_end) => {
|
||||||
|
trace!("Remote session end: {}", frame.channel_id());
|
||||||
|
let end = End { error: None };
|
||||||
|
session.get_mut().set_error(
|
||||||
|
AmqpTransportError::SessionEnded(
|
||||||
|
remote_end.error.clone(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let id = session.get_mut().id();
|
||||||
|
inner.post_frame(AmqpFrame::new(id, end.into()));
|
||||||
|
inner.sessions.remove(channel_id);
|
||||||
|
inner.sessions_map.remove(&frame.channel_id());
|
||||||
|
}
|
||||||
|
_ => session.get_mut().handle_frame(frame.into_parts().1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ChannelState::Closing(ref mut tx) => match frame.performative() {
|
||||||
|
Frame::End(_) => {
|
||||||
|
if let Some(tx) = tx.take() {
|
||||||
|
let _ = tx.send(Ok(()));
|
||||||
|
}
|
||||||
|
inner.sessions.remove(channel_id);
|
||||||
|
inner.sessions_map.remove(&frame.channel_id());
|
||||||
|
}
|
||||||
|
frm => trace!("Got frame after initiated session end: {:?}", frm),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("Can not find channel: {}", channel_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Poll::Ready(None) => {
|
||||||
|
inner.set_error(AmqpTransportError::Disconnected);
|
||||||
|
return Poll::Ready(None);
|
||||||
|
}
|
||||||
|
Poll::Pending => {
|
||||||
|
self.hb.update_local(update);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Poll::Ready(Some(Err(e))) => {
|
||||||
|
trace!("error reading: {:?}", e);
|
||||||
|
inner.set_error(e.clone().into());
|
||||||
|
return Poll::Ready(Some(Err(e.into())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsyncRead + AsyncWrite> Drop for Connection<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.inner
|
||||||
|
.get_mut()
|
||||||
|
.set_error(AmqpTransportError::Disconnected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsyncRead + AsyncWrite> Future for Connection<T> {
|
||||||
|
type Output = Result<(), AmqpCodecError>;
|
||||||
|
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||||
|
// connection heartbeat
|
||||||
|
match self.hb.poll(cx) {
|
||||||
|
Ok(act) => match act {
|
||||||
|
HeartbeatAction::None => (),
|
||||||
|
HeartbeatAction::Close => {
|
||||||
|
self.inner.get_mut().set_error(AmqpTransportError::Timeout);
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
HeartbeatAction::Heartbeat => {
|
||||||
|
self.inner
|
||||||
|
.get_mut()
|
||||||
|
.write_queue
|
||||||
|
.push_back(AmqpFrame::new(0, Frame::Empty));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
self.inner.get_mut().set_error(e);
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match self.poll_incoming(cx) {
|
||||||
|
Poll::Ready(None) => return Poll::Ready(Ok(())),
|
||||||
|
Poll::Ready(Some(Ok(frame))) => {
|
||||||
|
if let Some(channel) = self.inner.sessions.get(frame.channel_id() as usize) {
|
||||||
|
if let ChannelState::Established(ref session) = channel {
|
||||||
|
session.get_mut().handle_frame(frame.into_parts().1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
warn!("Unexpected frame: {:?}", frame);
|
||||||
|
}
|
||||||
|
Poll::Ready(Some(Err(e))) => return Poll::Ready(Err(e)),
|
||||||
|
Poll::Pending => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = self.poll_outgoing(cx)?;
|
||||||
|
self.register_write_task(cx);
|
||||||
|
|
||||||
|
match self.poll_incoming(cx) {
|
||||||
|
Poll::Ready(None) => return Poll::Ready(Ok(())),
|
||||||
|
Poll::Ready(Some(Ok(frame))) => {
|
||||||
|
if let Some(channel) = self.inner.sessions.get(frame.channel_id() as usize) {
|
||||||
|
if let ChannelState::Established(ref session) = channel {
|
||||||
|
session.get_mut().handle_frame(frame.into_parts().1);
|
||||||
|
return Poll::Pending;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
warn!("Unexpected frame: {:?}", frame);
|
||||||
|
}
|
||||||
|
Poll::Ready(Some(Err(e))) => return Poll::Ready(Err(e)),
|
||||||
|
Poll::Pending => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ConnectionController(pub(crate) Cell<ConnectionInner>);
|
||||||
|
|
||||||
|
impl ConnectionController {
|
||||||
|
pub(crate) fn new(local: Configuration) -> ConnectionController {
|
||||||
|
ConnectionController(Cell::new(ConnectionInner {
|
||||||
|
local,
|
||||||
|
remote: Configuration::default(),
|
||||||
|
write_queue: VecDeque::new(),
|
||||||
|
write_task: LocalWaker::new(),
|
||||||
|
sessions: slab::Slab::with_capacity(8),
|
||||||
|
sessions_map: FxHashMap::default(),
|
||||||
|
error: None,
|
||||||
|
state: State::Normal,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_remote(&mut self, remote: Configuration) {
|
||||||
|
self.0.get_mut().remote = remote;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Get remote connection configuration
|
||||||
|
pub fn remote_config(&self) -> &Configuration {
|
||||||
|
&self.0.get_ref().remote
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Drop connection
|
||||||
|
pub fn drop_connection(&mut self) {
|
||||||
|
let inner = self.0.get_mut();
|
||||||
|
inner.state = State::Drop;
|
||||||
|
inner.write_task.wake()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn post_frame(&mut self, frame: AmqpFrame) {
|
||||||
|
self.0.get_mut().post_frame(frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn drop_session_copy(&mut self, _id: usize) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConnectionInner {
|
||||||
|
pub(crate) fn new(local: Configuration, remote: Configuration) -> ConnectionInner {
|
||||||
|
ConnectionInner {
|
||||||
|
local,
|
||||||
|
remote,
|
||||||
|
write_queue: VecDeque::new(),
|
||||||
|
write_task: LocalWaker::new(),
|
||||||
|
sessions: slab::Slab::with_capacity(8),
|
||||||
|
sessions_map: FxHashMap::default(),
|
||||||
|
error: None,
|
||||||
|
state: State::Normal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_error(&mut self, err: AmqpTransportError) {
|
||||||
|
for (_, channel) in self.sessions.iter_mut() {
|
||||||
|
match channel {
|
||||||
|
ChannelState::Opening(_, _) | ChannelState::Closing(_) => (),
|
||||||
|
ChannelState::Established(ref mut ses) => {
|
||||||
|
ses.get_mut().set_error(err.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.sessions.clear();
|
||||||
|
self.sessions_map.clear();
|
||||||
|
|
||||||
|
self.error = Some(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_next_frame(&mut self) -> Option<AmqpFrame> {
|
||||||
|
self.write_queue.pop_front()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn post_frame(&mut self, frame: AmqpFrame) {
|
||||||
|
// trace!("POST-FRAME: {:#?}", frame.performative());
|
||||||
|
self.write_queue.push_back(frame);
|
||||||
|
self.write_task.wake();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete_session_creation(&mut self, channel_id: u16, begin: &Begin) {
|
||||||
|
trace!(
|
||||||
|
"session opened: {:?} {:?}",
|
||||||
|
channel_id,
|
||||||
|
begin.remote_channel()
|
||||||
|
);
|
||||||
|
|
||||||
|
let id = begin.remote_channel().unwrap() as usize;
|
||||||
|
|
||||||
|
if let Some(channel) = self.sessions.get_mut(id) {
|
||||||
|
if channel.is_opening() {
|
||||||
|
if let ChannelState::Opening(tx, cell) = channel {
|
||||||
|
let cell = cell.upgrade().unwrap();
|
||||||
|
let session = Cell::new(SessionInner::new(
|
||||||
|
id,
|
||||||
|
true,
|
||||||
|
ConnectionController(cell),
|
||||||
|
channel_id,
|
||||||
|
begin.next_outgoing_id(),
|
||||||
|
begin.incoming_window(),
|
||||||
|
begin.outgoing_window(),
|
||||||
|
));
|
||||||
|
self.sessions_map.insert(channel_id, id);
|
||||||
|
|
||||||
|
if tx
|
||||||
|
.take()
|
||||||
|
.unwrap()
|
||||||
|
.send(Session::new(session.clone()))
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
// todo: send end session
|
||||||
|
}
|
||||||
|
*channel = ChannelState::Established(session)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// send error response
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// todo: rogue begin right now - do nothing. in future might indicate incoming attach
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
actix-amqp/src/errors.rs
Executable file
31
actix-amqp/src/errors.rs
Executable file
@ -0,0 +1,31 @@
|
|||||||
|
use amqp_codec::{protocol, AmqpCodecError, ProtocolIdError};
|
||||||
|
|
||||||
|
#[derive(Debug, Display, Clone)]
|
||||||
|
pub enum AmqpTransportError {
|
||||||
|
Codec(AmqpCodecError),
|
||||||
|
TooManyChannels,
|
||||||
|
Disconnected,
|
||||||
|
Timeout,
|
||||||
|
#[display(fmt = "Connection closed, error: {:?}", _0)]
|
||||||
|
Closed(Option<protocol::Error>),
|
||||||
|
#[display(fmt = "Session ended, error: {:?}", _0)]
|
||||||
|
SessionEnded(Option<protocol::Error>),
|
||||||
|
#[display(fmt = "Link detached, error: {:?}", _0)]
|
||||||
|
LinkDetached(Option<protocol::Error>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AmqpCodecError> for AmqpTransportError {
|
||||||
|
fn from(err: AmqpCodecError) -> Self {
|
||||||
|
AmqpTransportError::Codec(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Display, From)]
|
||||||
|
pub enum SaslConnectError {
|
||||||
|
Protocol(ProtocolIdError),
|
||||||
|
AmqpError(AmqpCodecError),
|
||||||
|
#[display(fmt = "Sasl error code: {:?}", _0)]
|
||||||
|
Sasl(protocol::SaslCode),
|
||||||
|
ExpectedOpenFrame,
|
||||||
|
Disconnected,
|
||||||
|
}
|
93
actix-amqp/src/hb.rs
Executable file
93
actix-amqp/src/hb.rs
Executable file
@ -0,0 +1,93 @@
|
|||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use actix_rt::time::{delay_until, Delay, Instant};
|
||||||
|
use actix_utils::time::LowResTimeService;
|
||||||
|
|
||||||
|
use crate::errors::AmqpTransportError;
|
||||||
|
|
||||||
|
pub(crate) enum HeartbeatAction {
|
||||||
|
None,
|
||||||
|
Heartbeat,
|
||||||
|
Close,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Heartbeat {
|
||||||
|
expire_local: Instant,
|
||||||
|
expire_remote: Instant,
|
||||||
|
local: Duration,
|
||||||
|
remote: Option<Duration>,
|
||||||
|
time: LowResTimeService,
|
||||||
|
delay: Delay,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Heartbeat {
|
||||||
|
pub(crate) fn new(local: Duration, remote: Option<Duration>, time: LowResTimeService) -> Self {
|
||||||
|
let now = Instant::from_std(time.now());
|
||||||
|
let delay = if let Some(remote) = remote {
|
||||||
|
delay_until(now + std::cmp::min(local, remote))
|
||||||
|
} else {
|
||||||
|
delay_until(now + local)
|
||||||
|
};
|
||||||
|
|
||||||
|
Heartbeat {
|
||||||
|
expire_local: now,
|
||||||
|
expire_remote: now,
|
||||||
|
local,
|
||||||
|
remote,
|
||||||
|
time,
|
||||||
|
delay,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn update_local(&mut self, update: bool) {
|
||||||
|
if update {
|
||||||
|
self.expire_local = Instant::from_std(self.time.now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn update_remote(&mut self, update: bool) {
|
||||||
|
if update && self.remote.is_some() {
|
||||||
|
self.expire_remote = Instant::from_std(self.time.now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_expire(&self) -> Instant {
|
||||||
|
if let Some(remote) = self.remote {
|
||||||
|
let t1 = self.expire_local + self.local;
|
||||||
|
let t2 = self.expire_remote + remote;
|
||||||
|
if t1 < t2 {
|
||||||
|
t1
|
||||||
|
} else {
|
||||||
|
t2
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.expire_local + self.local
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn poll(&mut self, cx: &mut Context) -> Result<HeartbeatAction, AmqpTransportError> {
|
||||||
|
match Pin::new(&mut self.delay).poll(cx) {
|
||||||
|
Poll::Ready(_) => {
|
||||||
|
let mut act = HeartbeatAction::None;
|
||||||
|
let dl = self.delay.deadline();
|
||||||
|
if dl >= self.expire_local + self.local {
|
||||||
|
// close connection
|
||||||
|
return Ok(HeartbeatAction::Close);
|
||||||
|
}
|
||||||
|
if let Some(remote) = self.remote {
|
||||||
|
if dl >= self.expire_remote + remote {
|
||||||
|
// send heartbeat
|
||||||
|
act = HeartbeatAction::Heartbeat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.delay.reset(self.next_expire());
|
||||||
|
let _ = Pin::new(&mut self.delay).poll(cx);
|
||||||
|
Ok(act)
|
||||||
|
}
|
||||||
|
Poll::Pending => Ok(HeartbeatAction::None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
167
actix-amqp/src/lib.rs
Executable file
167
actix-amqp/src/lib.rs
Executable file
@ -0,0 +1,167 @@
|
|||||||
|
#![allow(unused_imports, dead_code)]
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate derive_more;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use actix_utils::oneshot;
|
||||||
|
use amqp_codec::protocol::{Disposition, Handle, Milliseconds, Open};
|
||||||
|
use bytes::Bytes;
|
||||||
|
use bytestring::ByteString;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
mod cell;
|
||||||
|
pub mod client;
|
||||||
|
mod connection;
|
||||||
|
mod errors;
|
||||||
|
mod hb;
|
||||||
|
mod rcvlink;
|
||||||
|
pub mod sasl;
|
||||||
|
pub mod server;
|
||||||
|
mod service;
|
||||||
|
mod session;
|
||||||
|
mod sndlink;
|
||||||
|
|
||||||
|
pub use self::connection::{Connection, ConnectionController};
|
||||||
|
pub use self::errors::AmqpTransportError;
|
||||||
|
pub use self::rcvlink::{ReceiverLink, ReceiverLinkBuilder};
|
||||||
|
pub use self::session::Session;
|
||||||
|
pub use self::sndlink::{SenderLink, SenderLinkBuilder};
|
||||||
|
|
||||||
|
pub enum Delivery {
|
||||||
|
Resolved(Result<Disposition, AmqpTransportError>),
|
||||||
|
Pending(oneshot::Receiver<Result<Disposition, AmqpTransportError>>),
|
||||||
|
Gone,
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeliveryPromise = oneshot::Sender<Result<Disposition, AmqpTransportError>>;
|
||||||
|
|
||||||
|
impl Future for Delivery {
|
||||||
|
type Output = Result<Disposition, AmqpTransportError>;
|
||||||
|
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||||
|
if let Delivery::Pending(ref mut receiver) = *self {
|
||||||
|
return match Pin::new(receiver).poll(cx) {
|
||||||
|
Poll::Ready(Ok(r)) => Poll::Ready(r.map(|state| state)),
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
Poll::Ready(Err(e)) => {
|
||||||
|
trace!("delivery oneshot is gone: {:?}", e);
|
||||||
|
Poll::Ready(Err(AmqpTransportError::Disconnected))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let old_v = ::std::mem::replace(&mut *self, Delivery::Gone);
|
||||||
|
if let Delivery::Resolved(r) = old_v {
|
||||||
|
return match r {
|
||||||
|
Ok(state) => Poll::Ready(Ok(state)),
|
||||||
|
Err(e) => Poll::Ready(Err(e)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
panic!("Polling Delivery after it was polled as ready is an error.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Amqp1 transport configuration.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Configuration {
|
||||||
|
pub max_frame_size: u32,
|
||||||
|
pub channel_max: usize,
|
||||||
|
pub idle_time_out: Option<Milliseconds>,
|
||||||
|
pub hostname: Option<ByteString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Configuration {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Configuration {
|
||||||
|
/// Create connection configuration.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Configuration {
|
||||||
|
max_frame_size: std::u16::MAX as u32,
|
||||||
|
channel_max: 1024,
|
||||||
|
idle_time_out: Some(120000),
|
||||||
|
hostname: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The channel-max value is the highest channel number that
|
||||||
|
/// may be used on the Connection. This value plus one is the maximum
|
||||||
|
/// number of Sessions that can be simultaneously active on the Connection.
|
||||||
|
///
|
||||||
|
/// By default channel max value is set to 1024
|
||||||
|
pub fn channel_max(&mut self, num: u16) -> &mut Self {
|
||||||
|
self.channel_max = num as usize;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set max frame size for the connection.
|
||||||
|
///
|
||||||
|
/// By default max size is set to 64kb
|
||||||
|
pub fn max_frame_size(&mut self, size: u32) -> &mut Self {
|
||||||
|
self.max_frame_size = size;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get max frame size for the connection.
|
||||||
|
pub fn get_max_frame_size(&self) -> usize {
|
||||||
|
self.max_frame_size as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set idle time-out for the connection in milliseconds
|
||||||
|
///
|
||||||
|
/// By default idle time-out is set to 120000 milliseconds
|
||||||
|
pub fn idle_timeout(&mut self, timeout: u32) -> &mut Self {
|
||||||
|
self.idle_time_out = Some(timeout as Milliseconds);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set connection hostname
|
||||||
|
///
|
||||||
|
/// Hostname is not set by default
|
||||||
|
pub fn hostname(&mut self, hostname: &str) -> &mut Self {
|
||||||
|
self.hostname = Some(ByteString::from(hostname));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create `Open` performative for this configuration.
|
||||||
|
pub fn to_open(&self) -> Open {
|
||||||
|
Open {
|
||||||
|
container_id: ByteString::from(Uuid::new_v4().to_simple().to_string()),
|
||||||
|
hostname: self.hostname.clone(),
|
||||||
|
max_frame_size: self.max_frame_size,
|
||||||
|
channel_max: self.channel_max as u16,
|
||||||
|
idle_time_out: self.idle_time_out,
|
||||||
|
outgoing_locales: None,
|
||||||
|
incoming_locales: None,
|
||||||
|
offered_capabilities: None,
|
||||||
|
desired_capabilities: None,
|
||||||
|
properties: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn timeout(&self) -> Option<Duration> {
|
||||||
|
self.idle_time_out
|
||||||
|
.map(|v| Duration::from_millis(((v as f32) * 0.8) as u64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a Open> for Configuration {
|
||||||
|
fn from(open: &'a Open) -> Self {
|
||||||
|
Configuration {
|
||||||
|
max_frame_size: open.max_frame_size,
|
||||||
|
channel_max: open.channel_max as usize,
|
||||||
|
idle_time_out: open.idle_time_out,
|
||||||
|
hostname: open.hostname.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
263
actix-amqp/src/rcvlink.rs
Executable file
263
actix-amqp/src/rcvlink.rs
Executable file
@ -0,0 +1,263 @@
|
|||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
use std::u32;
|
||||||
|
|
||||||
|
use actix_utils::oneshot;
|
||||||
|
use actix_utils::task::LocalWaker;
|
||||||
|
use amqp_codec::protocol::{
|
||||||
|
Attach, DeliveryNumber, Disposition, Error, Handle, LinkError, ReceiverSettleMode, Role,
|
||||||
|
SenderSettleMode, Source, TerminusDurability, TerminusExpiryPolicy, Transfer,
|
||||||
|
};
|
||||||
|
use bytes::Bytes;
|
||||||
|
use bytestring::ByteString;
|
||||||
|
use futures::Stream;
|
||||||
|
|
||||||
|
use crate::cell::Cell;
|
||||||
|
use crate::errors::AmqpTransportError;
|
||||||
|
use crate::session::{Session, SessionInner};
|
||||||
|
use crate::Configuration;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ReceiverLink {
|
||||||
|
pub(crate) inner: Cell<ReceiverLinkInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReceiverLink {
|
||||||
|
pub(crate) fn new(inner: Cell<ReceiverLinkInner>) -> ReceiverLink {
|
||||||
|
ReceiverLink { inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle(&self) -> Handle {
|
||||||
|
self.inner.get_ref().handle as Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn credit(&self) -> u32 {
|
||||||
|
self.inner.get_ref().credit
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn session(&self) -> &Session {
|
||||||
|
&self.inner.get_ref().session
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn session_mut(&mut self) -> &mut Session {
|
||||||
|
&mut self.inner.get_mut().session
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn frame(&self) -> &Attach {
|
||||||
|
&self.inner.get_ref().attach
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open(&mut self) {
|
||||||
|
let inner = self.inner.get_mut();
|
||||||
|
inner
|
||||||
|
.session
|
||||||
|
.inner
|
||||||
|
.get_mut()
|
||||||
|
.confirm_receiver_link(inner.handle, &inner.attach);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_link_credit(&mut self, credit: u32) {
|
||||||
|
self.inner.get_mut().set_link_credit(credit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send disposition frame
|
||||||
|
pub fn send_disposition(&mut self, disp: Disposition) {
|
||||||
|
self.inner
|
||||||
|
.get_mut()
|
||||||
|
.session
|
||||||
|
.inner
|
||||||
|
.get_mut()
|
||||||
|
.post_frame(disp.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait for disposition with specified number
|
||||||
|
pub fn wait_disposition(
|
||||||
|
&mut self,
|
||||||
|
id: DeliveryNumber,
|
||||||
|
) -> impl Future<Output = Result<Disposition, AmqpTransportError>> {
|
||||||
|
self.inner.get_mut().session.wait_disposition(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close(&self) -> impl Future<Output = Result<(), AmqpTransportError>> {
|
||||||
|
self.inner.get_mut().close(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close_with_error(
|
||||||
|
&self,
|
||||||
|
error: Error,
|
||||||
|
) -> impl Future<Output = Result<(), AmqpTransportError>> {
|
||||||
|
self.inner.get_mut().close(Some(error))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Get remote connection configuration
|
||||||
|
pub fn remote_config(&self) -> &Configuration {
|
||||||
|
&self.inner.session.remote_config()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stream for ReceiverLink {
|
||||||
|
type Item = Result<Transfer, AmqpTransportError>;
|
||||||
|
|
||||||
|
fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
|
||||||
|
let inner = self.inner.get_mut();
|
||||||
|
|
||||||
|
if let Some(tr) = inner.queue.pop_front() {
|
||||||
|
Poll::Ready(Some(Ok(tr)))
|
||||||
|
} else {
|
||||||
|
if inner.closed {
|
||||||
|
Poll::Ready(None)
|
||||||
|
} else {
|
||||||
|
inner.reader_task.register(cx.waker());
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct ReceiverLinkInner {
|
||||||
|
handle: Handle,
|
||||||
|
attach: Attach,
|
||||||
|
session: Session,
|
||||||
|
closed: bool,
|
||||||
|
reader_task: LocalWaker,
|
||||||
|
queue: VecDeque<Transfer>,
|
||||||
|
credit: u32,
|
||||||
|
delivery_count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReceiverLinkInner {
|
||||||
|
pub(crate) fn new(
|
||||||
|
session: Cell<SessionInner>,
|
||||||
|
handle: Handle,
|
||||||
|
attach: Attach,
|
||||||
|
) -> ReceiverLinkInner {
|
||||||
|
ReceiverLinkInner {
|
||||||
|
handle,
|
||||||
|
session: Session::new(session),
|
||||||
|
closed: false,
|
||||||
|
reader_task: LocalWaker::new(),
|
||||||
|
queue: VecDeque::with_capacity(4),
|
||||||
|
credit: 0,
|
||||||
|
delivery_count: attach.initial_delivery_count().unwrap_or(0),
|
||||||
|
attach,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &ByteString {
|
||||||
|
&self.attach.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close(
|
||||||
|
&mut self,
|
||||||
|
error: Option<Error>,
|
||||||
|
) -> impl Future<Output = Result<(), AmqpTransportError>> {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
if self.closed {
|
||||||
|
let _ = tx.send(Ok(()));
|
||||||
|
} else {
|
||||||
|
self.session
|
||||||
|
.inner
|
||||||
|
.get_mut()
|
||||||
|
.detach_receiver_link(self.handle, true, error, tx);
|
||||||
|
}
|
||||||
|
async move {
|
||||||
|
match rx.await {
|
||||||
|
Ok(Ok(_)) => Ok(()),
|
||||||
|
Ok(Err(e)) => Err(e),
|
||||||
|
Err(_) => Err(AmqpTransportError::Disconnected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_link_credit(&mut self, credit: u32) {
|
||||||
|
self.credit += credit;
|
||||||
|
self.session
|
||||||
|
.inner
|
||||||
|
.get_mut()
|
||||||
|
.rcv_link_flow(self.handle as u32, self.delivery_count, credit);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_transfer(&mut self, transfer: Transfer) {
|
||||||
|
if self.credit == 0 {
|
||||||
|
// check link credit
|
||||||
|
let err = Error {
|
||||||
|
condition: LinkError::TransferLimitExceeded.into(),
|
||||||
|
description: None,
|
||||||
|
info: None,
|
||||||
|
};
|
||||||
|
let _ = self.close(Some(err));
|
||||||
|
} else {
|
||||||
|
self.credit -= 1;
|
||||||
|
self.delivery_count += 1;
|
||||||
|
self.queue.push_back(transfer);
|
||||||
|
if self.queue.len() == 1 {
|
||||||
|
self.reader_task.wake()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ReceiverLinkBuilder {
|
||||||
|
frame: Attach,
|
||||||
|
session: Cell<SessionInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReceiverLinkBuilder {
|
||||||
|
pub(crate) fn new(name: ByteString, address: ByteString, session: Cell<SessionInner>) -> Self {
|
||||||
|
let source = Source {
|
||||||
|
address: Some(address),
|
||||||
|
durable: TerminusDurability::None,
|
||||||
|
expiry_policy: TerminusExpiryPolicy::SessionEnd,
|
||||||
|
timeout: 0,
|
||||||
|
dynamic: false,
|
||||||
|
dynamic_node_properties: None,
|
||||||
|
distribution_mode: None,
|
||||||
|
filter: None,
|
||||||
|
default_outcome: None,
|
||||||
|
outcomes: None,
|
||||||
|
capabilities: None,
|
||||||
|
};
|
||||||
|
let frame = Attach {
|
||||||
|
name,
|
||||||
|
handle: 0 as Handle,
|
||||||
|
role: Role::Receiver,
|
||||||
|
snd_settle_mode: SenderSettleMode::Mixed,
|
||||||
|
rcv_settle_mode: ReceiverSettleMode::First,
|
||||||
|
source: Some(source),
|
||||||
|
target: None,
|
||||||
|
unsettled: None,
|
||||||
|
incomplete_unsettled: false,
|
||||||
|
initial_delivery_count: None,
|
||||||
|
max_message_size: Some(65536 * 4),
|
||||||
|
offered_capabilities: None,
|
||||||
|
desired_capabilities: None,
|
||||||
|
properties: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
ReceiverLinkBuilder { frame, session }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn max_message_size(mut self, size: u64) -> Self {
|
||||||
|
self.frame.max_message_size = Some(size);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn open(self) -> Result<ReceiverLink, AmqpTransportError> {
|
||||||
|
let cell = self.session.clone();
|
||||||
|
let res = self
|
||||||
|
.session
|
||||||
|
.get_mut()
|
||||||
|
.open_local_receiver_link(cell, self.frame)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(Ok(res)) => Ok(res),
|
||||||
|
Ok(Err(err)) => Err(err),
|
||||||
|
Err(_) => Err(AmqpTransportError::Disconnected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
190
actix-amqp/src/sasl.rs
Executable file
190
actix-amqp/src/sasl.rs
Executable file
@ -0,0 +1,190 @@
|
|||||||
|
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||||
|
use actix_connect::{Connect as TcpConnect, Connection as TcpConnection};
|
||||||
|
use actix_service::{apply_fn, pipeline, IntoService, Service};
|
||||||
|
use actix_utils::time::LowResTimeService;
|
||||||
|
use bytestring::ByteString;
|
||||||
|
use either::Either;
|
||||||
|
use futures::future::{ok, Future};
|
||||||
|
use futures::{FutureExt, Sink, SinkExt, Stream, StreamExt};
|
||||||
|
use http::Uri;
|
||||||
|
|
||||||
|
use amqp_codec::protocol::{Frame, ProtocolId, SaslCode, SaslFrameBody, SaslInit};
|
||||||
|
use amqp_codec::types::Symbol;
|
||||||
|
use amqp_codec::{AmqpCodec, AmqpFrame, ProtocolIdCodec, SaslFrame};
|
||||||
|
|
||||||
|
use crate::connection::Connection;
|
||||||
|
use crate::service::ProtocolNegotiation;
|
||||||
|
|
||||||
|
use super::Configuration;
|
||||||
|
pub use crate::errors::SaslConnectError;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Sasl connect request
|
||||||
|
pub struct SaslConnect {
|
||||||
|
pub uri: Uri,
|
||||||
|
pub config: Configuration,
|
||||||
|
pub auth: SaslAuth,
|
||||||
|
pub time: Option<LowResTimeService>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Sasl authentication parameters
|
||||||
|
pub struct SaslAuth {
|
||||||
|
pub authz_id: String,
|
||||||
|
pub authn_id: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create service that connects to amqp server and authenticate itself via sasl.
|
||||||
|
/// This service uses supplied connector service. Service resolves to
|
||||||
|
/// a `Connection<_>` instance.
|
||||||
|
pub fn connect_service<T, Io>(
|
||||||
|
connector: T,
|
||||||
|
) -> impl Service<
|
||||||
|
Request = SaslConnect,
|
||||||
|
Response = Connection<Io>,
|
||||||
|
Error = either::Either<SaslConnectError, T::Error>,
|
||||||
|
>
|
||||||
|
where
|
||||||
|
T: Service<Request = TcpConnect<Uri>, Response = TcpConnection<Uri, Io>>,
|
||||||
|
T::Error: 'static,
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
{
|
||||||
|
pipeline(|connect: SaslConnect| {
|
||||||
|
let SaslConnect {
|
||||||
|
uri,
|
||||||
|
config,
|
||||||
|
auth,
|
||||||
|
time,
|
||||||
|
} = connect;
|
||||||
|
ok::<_, either::Either<SaslConnectError, T::Error>>((uri, config, auth, time))
|
||||||
|
})
|
||||||
|
// connect to host
|
||||||
|
.and_then(apply_fn(
|
||||||
|
connector.map_err(|e| either::Right(e)),
|
||||||
|
|(uri, config, auth, time): (Uri, Configuration, _, _), srv| {
|
||||||
|
let fut = srv.call(uri.clone().into());
|
||||||
|
async move {
|
||||||
|
fut.await.map(|stream| {
|
||||||
|
let (io, _) = stream.into_parts();
|
||||||
|
(io, uri, config, auth, time)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
))
|
||||||
|
// sasl protocol negotiation
|
||||||
|
.and_then(apply_fn(
|
||||||
|
ProtocolNegotiation::new(ProtocolId::AmqpSasl)
|
||||||
|
.map_err(|e| Either::Left(SaslConnectError::from(e))),
|
||||||
|
|(io, uri, config, auth, time): (Io, _, _, _, _), srv| {
|
||||||
|
let framed = Framed::new(io, ProtocolIdCodec);
|
||||||
|
let fut = srv.call(framed);
|
||||||
|
async move {
|
||||||
|
fut.await
|
||||||
|
.map(move |framed| (framed, uri, config, auth, time))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
))
|
||||||
|
// sasl auth
|
||||||
|
.and_then(apply_fn(
|
||||||
|
sasl_connect.into_service().map_err(Either::Left),
|
||||||
|
|(framed, uri, config, auth, time): (_, Uri, _, _, _), srv| {
|
||||||
|
let fut = srv.call((framed, uri.clone(), auth));
|
||||||
|
async move { fut.await.map(move |framed| (uri, config, framed, time)) }
|
||||||
|
},
|
||||||
|
))
|
||||||
|
// re-negotiate amqp protocol negotiation
|
||||||
|
.and_then(apply_fn(
|
||||||
|
ProtocolNegotiation::new(ProtocolId::Amqp)
|
||||||
|
.map_err(|e| Either::Left(SaslConnectError::from(e))),
|
||||||
|
|(uri, config, framed, time): (_, _, Framed<Io, ProtocolIdCodec>, _), srv| {
|
||||||
|
let fut = srv.call(framed);
|
||||||
|
async move { fut.await.map(move |framed| (uri, config, framed, time)) }
|
||||||
|
},
|
||||||
|
))
|
||||||
|
// open connection
|
||||||
|
.and_then(
|
||||||
|
|(uri, mut config, framed, time): (Uri, Configuration, Framed<Io, ProtocolIdCodec>, _)| {
|
||||||
|
async move {
|
||||||
|
let mut framed = framed.into_framed(AmqpCodec::<AmqpFrame>::new());
|
||||||
|
if let Some(hostname) = uri.host() {
|
||||||
|
config.hostname(hostname);
|
||||||
|
}
|
||||||
|
let open = config.to_open();
|
||||||
|
trace!("Open connection: {:?}", open);
|
||||||
|
framed
|
||||||
|
.send(AmqpFrame::new(0, Frame::Open(open)))
|
||||||
|
.await
|
||||||
|
.map_err(|e| Either::Left(SaslConnectError::from(e)))
|
||||||
|
.map(move |_| (config, framed, time))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
// read open frame
|
||||||
|
.and_then(
|
||||||
|
move |(config, mut framed, time): (Configuration, Framed<_, AmqpCodec<AmqpFrame>>, _)| {
|
||||||
|
async move {
|
||||||
|
let frame = framed
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.ok_or(Either::Left(SaslConnectError::Disconnected))?
|
||||||
|
.map_err(|e| Either::Left(SaslConnectError::from(e)))?;
|
||||||
|
|
||||||
|
if let Frame::Open(open) = frame.performative() {
|
||||||
|
trace!("Open confirmed: {:?}", open);
|
||||||
|
Ok(Connection::new(framed, config, open.into(), time))
|
||||||
|
} else {
|
||||||
|
Err(Either::Left(SaslConnectError::ExpectedOpenFrame))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sasl_connect<Io: AsyncRead + AsyncWrite>(
|
||||||
|
(framed, uri, auth): (Framed<Io, ProtocolIdCodec>, Uri, SaslAuth),
|
||||||
|
) -> Result<Framed<Io, ProtocolIdCodec>, SaslConnectError> {
|
||||||
|
let mut sasl_io = framed.into_framed(AmqpCodec::<SaslFrame>::new());
|
||||||
|
|
||||||
|
// processing sasl-mechanisms
|
||||||
|
let _ = sasl_io
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.ok_or(SaslConnectError::Disconnected)?
|
||||||
|
.map_err(SaslConnectError::from)?;
|
||||||
|
|
||||||
|
let initial_response =
|
||||||
|
SaslInit::prepare_response(&auth.authz_id, &auth.authn_id, &auth.password);
|
||||||
|
|
||||||
|
let hostname = uri.host().map(|host| ByteString::from(host));
|
||||||
|
|
||||||
|
let sasl_init = SaslInit {
|
||||||
|
hostname,
|
||||||
|
mechanism: Symbol::from("PLAIN"),
|
||||||
|
initial_response: Some(initial_response),
|
||||||
|
};
|
||||||
|
|
||||||
|
sasl_io
|
||||||
|
.send(sasl_init.into())
|
||||||
|
.await
|
||||||
|
.map_err(SaslConnectError::from)?;
|
||||||
|
|
||||||
|
// processing sasl-outcome
|
||||||
|
let sasl_frame = sasl_io
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.ok_or(SaslConnectError::Disconnected)?
|
||||||
|
.map_err(SaslConnectError::from)?;
|
||||||
|
|
||||||
|
if let SaslFrame {
|
||||||
|
body: SaslFrameBody::SaslOutcome(outcome),
|
||||||
|
} = sasl_frame
|
||||||
|
{
|
||||||
|
if outcome.code() != SaslCode::Ok {
|
||||||
|
return Err(SaslConnectError::Sasl(outcome.code()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(SaslConnectError::Disconnected);
|
||||||
|
}
|
||||||
|
Ok(sasl_io.into_framed(ProtocolIdCodec))
|
||||||
|
}
|
263
actix-amqp/src/server/app.rs
Executable file
263
actix-amqp/src/server/app.rs
Executable file
@ -0,0 +1,263 @@
|
|||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
use actix_router::{IntoPattern, Router};
|
||||||
|
use actix_service::{boxed, fn_factory_with_config, IntoServiceFactory, Service, ServiceFactory};
|
||||||
|
use amqp_codec::protocol::{DeliveryNumber, DeliveryState, Disposition, Error, Rejected, Role};
|
||||||
|
use futures::future::{err, ok, Either, Ready};
|
||||||
|
use futures::{Stream, StreamExt};
|
||||||
|
|
||||||
|
use crate::cell::Cell;
|
||||||
|
use crate::rcvlink::ReceiverLink;
|
||||||
|
|
||||||
|
use super::errors::LinkError;
|
||||||
|
use super::link::Link;
|
||||||
|
use super::message::{Message, Outcome};
|
||||||
|
use super::State;
|
||||||
|
|
||||||
|
type Handle<S> = boxed::BoxServiceFactory<Link<S>, Message<S>, Outcome, Error, Error>;
|
||||||
|
|
||||||
|
pub struct App<S = ()>(Vec<(Vec<String>, Handle<S>)>);
|
||||||
|
|
||||||
|
impl<S: 'static> App<S> {
|
||||||
|
pub fn new() -> App<S> {
|
||||||
|
App(Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn service<T, F, U: 'static>(mut self, address: T, service: F) -> Self
|
||||||
|
where
|
||||||
|
T: IntoPattern,
|
||||||
|
F: IntoServiceFactory<U>,
|
||||||
|
U: ServiceFactory<Config = Link<S>, Request = Message<S>, Response = Outcome>,
|
||||||
|
U::Error: Into<Error>,
|
||||||
|
U::InitError: Into<Error>,
|
||||||
|
{
|
||||||
|
self.0.push((
|
||||||
|
address.patterns(),
|
||||||
|
boxed::factory(
|
||||||
|
service
|
||||||
|
.into_factory()
|
||||||
|
.map_init_err(|e| e.into())
|
||||||
|
.map_err(|e| e.into()),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish(
|
||||||
|
self,
|
||||||
|
) -> impl ServiceFactory<
|
||||||
|
Config = State<S>,
|
||||||
|
Request = Link<S>,
|
||||||
|
Response = (),
|
||||||
|
Error = Error,
|
||||||
|
InitError = Error,
|
||||||
|
> {
|
||||||
|
let mut router = Router::build();
|
||||||
|
for (addr, hnd) in self.0 {
|
||||||
|
router.path(addr, hnd);
|
||||||
|
}
|
||||||
|
let router = Cell::new(router.finish());
|
||||||
|
|
||||||
|
fn_factory_with_config(move |_: State<S>| {
|
||||||
|
ok(AppService {
|
||||||
|
router: router.clone(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AppService<S> {
|
||||||
|
router: Cell<Router<Handle<S>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: 'static> Service for AppService<S> {
|
||||||
|
type Request = Link<S>;
|
||||||
|
type Response = ();
|
||||||
|
type Error = Error;
|
||||||
|
type Future = Either<Ready<Result<(), Error>>, AppServiceResponse<S>>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, mut link: Link<S>) -> Self::Future {
|
||||||
|
let path = link
|
||||||
|
.frame()
|
||||||
|
.target
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|target| target.address.as_ref().map(|addr| addr.clone()));
|
||||||
|
|
||||||
|
if let Some(path) = path {
|
||||||
|
link.path_mut().set(path);
|
||||||
|
if let Some((hnd, _info)) = self.router.recognize(link.path_mut()) {
|
||||||
|
let fut = hnd.new_service(link.clone());
|
||||||
|
Either::Right(AppServiceResponse {
|
||||||
|
link: link.link.clone(),
|
||||||
|
app_state: link.state.clone(),
|
||||||
|
state: AppServiceResponseState::NewService(fut),
|
||||||
|
// has_credit: true,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Either::Left(err(LinkError::force_detach()
|
||||||
|
.description(format!(
|
||||||
|
"Target address is not supported: {}",
|
||||||
|
link.path().get_ref()
|
||||||
|
))
|
||||||
|
.into()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Either::Left(err(LinkError::force_detach()
|
||||||
|
.description("Target address is required")
|
||||||
|
.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AppServiceResponse<S> {
|
||||||
|
link: ReceiverLink,
|
||||||
|
app_state: State<S>,
|
||||||
|
state: AppServiceResponseState<S>,
|
||||||
|
// has_credit: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AppServiceResponseState<S> {
|
||||||
|
Service(boxed::BoxService<Message<S>, Outcome, Error>),
|
||||||
|
NewService(
|
||||||
|
Pin<Box<dyn Future<Output = Result<boxed::BoxService<Message<S>, Outcome, Error>, Error>>>>,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Future for AppServiceResponse<S> {
|
||||||
|
type Output = Result<(), Error>;
|
||||||
|
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||||
|
let mut this = self.as_mut();
|
||||||
|
let mut link = this.link.clone();
|
||||||
|
let app_state = this.app_state.clone();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match this.state {
|
||||||
|
AppServiceResponseState::Service(ref mut srv) => {
|
||||||
|
// check readiness
|
||||||
|
match srv.poll_ready(cx) {
|
||||||
|
Poll::Ready(Ok(_)) => (),
|
||||||
|
Poll::Pending => return Poll::Pending,
|
||||||
|
Poll::Ready(Err(e)) => {
|
||||||
|
let _ = this.link.close_with_error(
|
||||||
|
LinkError::force_detach()
|
||||||
|
.description(format!("error: {}", e))
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match Pin::new(&mut link).poll_next(cx) {
|
||||||
|
Poll::Ready(Some(Ok(transfer))) => {
|
||||||
|
// #2.7.5 delivery_id MUST be set. batching is not supported atm
|
||||||
|
if transfer.delivery_id.is_none() {
|
||||||
|
let _ = this.link.close_with_error(
|
||||||
|
LinkError::force_detach()
|
||||||
|
.description("delivery_id MUST be set")
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
if link.credit() == 0 {
|
||||||
|
// self.has_credit = self.link.credit() != 0;
|
||||||
|
link.set_link_credit(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
let delivery_id = transfer.delivery_id.unwrap();
|
||||||
|
let msg = Message::new(app_state.clone(), transfer, link.clone());
|
||||||
|
|
||||||
|
let mut fut = srv.call(msg);
|
||||||
|
match Pin::new(&mut fut).poll(cx) {
|
||||||
|
Poll::Ready(Ok(outcome)) => settle(
|
||||||
|
&mut this.link,
|
||||||
|
delivery_id,
|
||||||
|
outcome.into_delivery_state(),
|
||||||
|
),
|
||||||
|
Poll::Pending => {
|
||||||
|
actix_rt::spawn(HandleMessage {
|
||||||
|
fut,
|
||||||
|
delivery_id,
|
||||||
|
link: this.link.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Poll::Ready(Err(e)) => settle(
|
||||||
|
&mut this.link,
|
||||||
|
delivery_id,
|
||||||
|
DeliveryState::Rejected(Rejected { error: Some(e) }),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Poll::Ready(None) => return Poll::Ready(Ok(())),
|
||||||
|
Poll::Pending => return Poll::Pending,
|
||||||
|
Poll::Ready(Some(Err(_))) => {
|
||||||
|
let _ = this.link.close_with_error(LinkError::force_detach().into());
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AppServiceResponseState::NewService(ref mut fut) => match Pin::new(fut).poll(cx) {
|
||||||
|
Poll::Ready(Ok(srv)) => {
|
||||||
|
this.link.open();
|
||||||
|
this.link.set_link_credit(50);
|
||||||
|
this.state = AppServiceResponseState::Service(srv);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
|
||||||
|
Poll::Pending => return Poll::Pending,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HandleMessage {
|
||||||
|
link: ReceiverLink,
|
||||||
|
delivery_id: DeliveryNumber,
|
||||||
|
fut: Pin<Box<dyn Future<Output = Result<Outcome, Error>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Future for HandleMessage {
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||||
|
let mut this = self.as_mut();
|
||||||
|
|
||||||
|
match Pin::new(&mut this.fut).poll(cx) {
|
||||||
|
Poll::Ready(Ok(outcome)) => {
|
||||||
|
let delivery_id = this.delivery_id;
|
||||||
|
settle(&mut this.link, delivery_id, outcome.into_delivery_state());
|
||||||
|
Poll::Ready(())
|
||||||
|
}
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
Poll::Ready(Err(e)) => {
|
||||||
|
let delivery_id = this.delivery_id;
|
||||||
|
settle(
|
||||||
|
&mut this.link,
|
||||||
|
delivery_id,
|
||||||
|
DeliveryState::Rejected(Rejected { error: Some(e) }),
|
||||||
|
);
|
||||||
|
Poll::Ready(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn settle(link: &mut ReceiverLink, id: DeliveryNumber, state: DeliveryState) {
|
||||||
|
let disposition = Disposition {
|
||||||
|
state: Some(state),
|
||||||
|
role: Role::Receiver,
|
||||||
|
first: id,
|
||||||
|
last: None,
|
||||||
|
settled: true,
|
||||||
|
batchable: false,
|
||||||
|
};
|
||||||
|
link.send_disposition(disposition);
|
||||||
|
}
|
120
actix-amqp/src/server/connect.rs
Executable file
120
actix-amqp/src/server/connect.rs
Executable file
@ -0,0 +1,120 @@
|
|||||||
|
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||||
|
use amqp_codec::protocol::{Frame, Open};
|
||||||
|
use amqp_codec::{AmqpCodec, AmqpFrame, ProtocolIdCodec};
|
||||||
|
use futures::{Future, StreamExt};
|
||||||
|
|
||||||
|
use super::errors::ServerError;
|
||||||
|
use crate::connection::ConnectionController;
|
||||||
|
|
||||||
|
/// Open new connection
|
||||||
|
pub struct Connect<Io> {
|
||||||
|
conn: Framed<Io, ProtocolIdCodec>,
|
||||||
|
controller: ConnectionController,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io> Connect<Io> {
|
||||||
|
pub(crate) fn new(conn: Framed<Io, ProtocolIdCodec>, controller: ConnectionController) -> Self {
|
||||||
|
Self { conn, controller }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns reference to io object
|
||||||
|
pub fn get_ref(&self) -> &Io {
|
||||||
|
self.conn.get_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns mutable reference to io object
|
||||||
|
pub fn get_mut(&mut self) -> &mut Io {
|
||||||
|
self.conn.get_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io: AsyncRead + AsyncWrite> Connect<Io> {
|
||||||
|
/// Wait for connection open frame
|
||||||
|
pub async fn open(self) -> Result<ConnectOpened<Io>, ServerError<()>> {
|
||||||
|
let mut framed = self.conn.into_framed(AmqpCodec::<AmqpFrame>::new());
|
||||||
|
let mut controller = self.controller;
|
||||||
|
|
||||||
|
let frame = framed
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.ok_or(ServerError::Disconnected)?
|
||||||
|
.map_err(ServerError::from)?;
|
||||||
|
|
||||||
|
let frame = frame.into_parts().1;
|
||||||
|
match frame {
|
||||||
|
Frame::Open(frame) => {
|
||||||
|
trace!("Got open frame: {:?}", frame);
|
||||||
|
controller.set_remote((&frame).into());
|
||||||
|
Ok(ConnectOpened {
|
||||||
|
frame,
|
||||||
|
framed,
|
||||||
|
controller,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
frame => Err(ServerError::Unexpected(frame)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connection is opened
|
||||||
|
pub struct ConnectOpened<Io> {
|
||||||
|
frame: Open,
|
||||||
|
framed: Framed<Io, AmqpCodec<AmqpFrame>>,
|
||||||
|
controller: ConnectionController,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io> ConnectOpened<Io> {
|
||||||
|
pub(crate) fn new(
|
||||||
|
frame: Open,
|
||||||
|
framed: Framed<Io, AmqpCodec<AmqpFrame>>,
|
||||||
|
controller: ConnectionController,
|
||||||
|
) -> Self {
|
||||||
|
ConnectOpened {
|
||||||
|
frame,
|
||||||
|
framed,
|
||||||
|
controller,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get reference to remote `Open` frame
|
||||||
|
pub fn frame(&self) -> &Open {
|
||||||
|
&self.frame
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns reference to io object
|
||||||
|
pub fn get_ref(&self) -> &Io {
|
||||||
|
self.framed.get_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns mutable reference to io object
|
||||||
|
pub fn get_mut(&mut self) -> &mut Io {
|
||||||
|
self.framed.get_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connection controller
|
||||||
|
pub fn connection(&self) -> &ConnectionController {
|
||||||
|
&self.controller
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ack connect message and set state
|
||||||
|
pub fn ack<St>(self, state: St) -> ConnectAck<Io, St> {
|
||||||
|
ConnectAck {
|
||||||
|
state,
|
||||||
|
framed: self.framed,
|
||||||
|
controller: self.controller,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ack connect message
|
||||||
|
pub struct ConnectAck<Io, St> {
|
||||||
|
state: St,
|
||||||
|
framed: Framed<Io, AmqpCodec<AmqpFrame>>,
|
||||||
|
controller: ConnectionController,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io, St> ConnectAck<Io, St> {
|
||||||
|
pub(crate) fn into_inner(self) -> (St, Framed<Io, AmqpCodec<AmqpFrame>>, ConnectionController) {
|
||||||
|
(self.state, self.framed, self.controller)
|
||||||
|
}
|
||||||
|
}
|
69
actix-amqp/src/server/control.rs
Executable file
69
actix-amqp/src/server/control.rs
Executable file
@ -0,0 +1,69 @@
|
|||||||
|
use actix_service::boxed::{BoxService, BoxServiceFactory};
|
||||||
|
use amqp_codec::protocol;
|
||||||
|
|
||||||
|
use crate::cell::Cell;
|
||||||
|
use crate::rcvlink::ReceiverLink;
|
||||||
|
use crate::session::Session;
|
||||||
|
use crate::sndlink::SenderLink;
|
||||||
|
|
||||||
|
use super::errors::LinkError;
|
||||||
|
use super::State;
|
||||||
|
|
||||||
|
pub(crate) type ControlFrameService<St> = BoxService<ControlFrame<St>, (), LinkError>;
|
||||||
|
pub(crate) type ControlFrameNewService<St> =
|
||||||
|
BoxServiceFactory<(), ControlFrame<St>, (), LinkError, ()>;
|
||||||
|
|
||||||
|
pub struct ControlFrame<St>(pub(super) Cell<FrameInner<St>>);
|
||||||
|
|
||||||
|
pub(super) struct FrameInner<St> {
|
||||||
|
pub(super) kind: ControlFrameKind,
|
||||||
|
pub(super) state: State<St>,
|
||||||
|
pub(super) session: Session,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ControlFrameKind {
|
||||||
|
Attach(protocol::Attach),
|
||||||
|
Flow(protocol::Flow, SenderLink),
|
||||||
|
DetachSender(protocol::Detach, SenderLink),
|
||||||
|
DetachReceiver(protocol::Detach, ReceiverLink),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<St> ControlFrame<St> {
|
||||||
|
pub(crate) fn new(state: State<St>, session: Session, kind: ControlFrameKind) -> Self {
|
||||||
|
ControlFrame(Cell::new(FrameInner {
|
||||||
|
state,
|
||||||
|
session,
|
||||||
|
kind,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn clone(&self) -> Self {
|
||||||
|
ControlFrame(self.0.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn state(&self) -> &St {
|
||||||
|
self.0.state.get_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn state_mut(&mut self) -> &mut St {
|
||||||
|
self.0.get_mut().state.get_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn session(&self) -> &Session {
|
||||||
|
&self.0.session
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn session_mut(&mut self) -> &mut Session {
|
||||||
|
&mut self.0.get_mut().session
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn frame(&self) -> &ControlFrameKind {
|
||||||
|
&self.0.kind
|
||||||
|
}
|
||||||
|
}
|
287
actix-amqp/src/server/dispatcher.rs
Executable file
287
actix-amqp/src/server/dispatcher.rs
Executable file
@ -0,0 +1,287 @@
|
|||||||
|
use std::fmt;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
use actix_codec::{AsyncRead, AsyncWrite};
|
||||||
|
use actix_service::Service;
|
||||||
|
use amqp_codec::protocol::{Error, Frame, Role};
|
||||||
|
use amqp_codec::AmqpCodecError;
|
||||||
|
use slab::Slab;
|
||||||
|
|
||||||
|
use crate::cell::Cell;
|
||||||
|
use crate::connection::{ChannelState, Connection};
|
||||||
|
use crate::rcvlink::ReceiverLink;
|
||||||
|
use crate::session::Session;
|
||||||
|
|
||||||
|
use super::control::{ControlFrame, ControlFrameKind, ControlFrameService};
|
||||||
|
use super::errors::LinkError;
|
||||||
|
use super::{Link, State};
|
||||||
|
|
||||||
|
/// Amqp server connection dispatcher.
|
||||||
|
#[pin_project::pin_project]
|
||||||
|
pub struct Dispatcher<Io, St, Sr>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite,
|
||||||
|
Sr: Service<Request = Link<St>, Response = ()>,
|
||||||
|
{
|
||||||
|
conn: Connection<Io>,
|
||||||
|
state: State<St>,
|
||||||
|
service: Sr,
|
||||||
|
control_srv: Option<ControlFrameService<St>>,
|
||||||
|
control_frame: Option<ControlFrame<St>>,
|
||||||
|
#[pin]
|
||||||
|
control_fut: Option<<ControlFrameService<St> as Service>::Future>,
|
||||||
|
receivers: Vec<(ReceiverLink, Sr::Future)>,
|
||||||
|
_channels: slab::Slab<ChannelState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum IncomingResult {
|
||||||
|
Control,
|
||||||
|
Done,
|
||||||
|
Disconnect,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io, St, Sr> Dispatcher<Io, St, Sr>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite,
|
||||||
|
Sr: Service<Request = Link<St>, Response = ()>,
|
||||||
|
Sr::Error: fmt::Display + Into<Error>,
|
||||||
|
{
|
||||||
|
pub(crate) fn new(
|
||||||
|
conn: Connection<Io>,
|
||||||
|
state: State<St>,
|
||||||
|
service: Sr,
|
||||||
|
control_srv: Option<ControlFrameService<St>>,
|
||||||
|
) -> Self {
|
||||||
|
Dispatcher {
|
||||||
|
conn,
|
||||||
|
service,
|
||||||
|
state,
|
||||||
|
control_srv,
|
||||||
|
control_frame: None,
|
||||||
|
control_fut: None,
|
||||||
|
receivers: Vec::with_capacity(16),
|
||||||
|
_channels: Slab::with_capacity(16),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_control_fut(&mut self, cx: &mut Context) -> bool {
|
||||||
|
// process control frame
|
||||||
|
if let Some(ref mut fut) = self.control_fut {
|
||||||
|
match Pin::new(fut).poll(cx) {
|
||||||
|
Poll::Ready(Ok(_)) => {
|
||||||
|
self.control_fut.take();
|
||||||
|
let frame = self.control_frame.take().unwrap();
|
||||||
|
self.handle_control_frame(&frame, None);
|
||||||
|
}
|
||||||
|
Poll::Pending => return false,
|
||||||
|
Poll::Ready(Err(e)) => {
|
||||||
|
let _ = self.control_fut.take();
|
||||||
|
let frame = self.control_frame.take().unwrap();
|
||||||
|
self.handle_control_frame(&frame, Some(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_control_frame(&self, frame: &ControlFrame<St>, err: Option<LinkError>) {
|
||||||
|
if let Some(e) = err {
|
||||||
|
error!("Error in link handler: {}", e);
|
||||||
|
} else {
|
||||||
|
match frame.0.kind {
|
||||||
|
ControlFrameKind::Attach(ref frm) => {
|
||||||
|
let cell = frame.0.session.inner.clone();
|
||||||
|
frame
|
||||||
|
.0
|
||||||
|
.session
|
||||||
|
.inner
|
||||||
|
.get_mut()
|
||||||
|
.confirm_sender_link(cell, &frm);
|
||||||
|
}
|
||||||
|
ControlFrameKind::Flow(ref frm, ref link) => {
|
||||||
|
if let Some(err) = err {
|
||||||
|
let _ = link.close_with_error(err.into());
|
||||||
|
} else {
|
||||||
|
frame.0.session.inner.get_mut().apply_flow(frm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ControlFrameKind::DetachSender(ref frm, ref link) => {
|
||||||
|
if let Some(err) = err {
|
||||||
|
let _ = link.close_with_error(err.into());
|
||||||
|
} else {
|
||||||
|
frame.0.session.inner.get_mut().handle_detach(&frm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ControlFrameKind::DetachReceiver(ref frm, ref link) => {
|
||||||
|
if let Some(err) = err {
|
||||||
|
let _ = link.close_with_error(err.into());
|
||||||
|
} else {
|
||||||
|
frame.0.session.inner.get_mut().handle_detach(frm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_incoming(&mut self, cx: &mut Context) -> Result<IncomingResult, AmqpCodecError> {
|
||||||
|
loop {
|
||||||
|
// handle remote begin and attach
|
||||||
|
match self.conn.poll_incoming(cx) {
|
||||||
|
Poll::Ready(Some(Ok(frame))) => {
|
||||||
|
let (channel_id, frame) = frame.into_parts();
|
||||||
|
let channel_id = channel_id as usize;
|
||||||
|
|
||||||
|
match frame {
|
||||||
|
Frame::Begin(frm) => {
|
||||||
|
self.conn.register_remote_session(channel_id as u16, &frm);
|
||||||
|
}
|
||||||
|
Frame::Flow(frm) => {
|
||||||
|
// apply flow to specific link
|
||||||
|
let session = self.conn.get_session(channel_id);
|
||||||
|
if self.control_srv.is_some() {
|
||||||
|
if let Some(link) =
|
||||||
|
session.get_sender_link_by_handle(frm.handle.unwrap())
|
||||||
|
{
|
||||||
|
self.control_frame = Some(ControlFrame::new(
|
||||||
|
self.state.clone(),
|
||||||
|
Session::new(session.clone()),
|
||||||
|
ControlFrameKind::Flow(frm, link.clone()),
|
||||||
|
));
|
||||||
|
return Ok(IncomingResult::Control);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
session.get_mut().apply_flow(&frm);
|
||||||
|
}
|
||||||
|
Frame::Attach(attach) => match attach.role {
|
||||||
|
Role::Receiver => {
|
||||||
|
// remotly opened sender link
|
||||||
|
let session = self.conn.get_session(channel_id);
|
||||||
|
let cell = session.clone();
|
||||||
|
if self.control_srv.is_some() {
|
||||||
|
self.control_frame = Some(ControlFrame::new(
|
||||||
|
self.state.clone(),
|
||||||
|
Session::new(cell.clone()),
|
||||||
|
ControlFrameKind::Attach(attach),
|
||||||
|
));
|
||||||
|
return Ok(IncomingResult::Control);
|
||||||
|
} else {
|
||||||
|
session.get_mut().confirm_sender_link(cell, &attach);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Role::Sender => {
|
||||||
|
// receiver link
|
||||||
|
let session = self.conn.get_session(channel_id);
|
||||||
|
let cell = session.clone();
|
||||||
|
let link = session.get_mut().open_receiver_link(cell, attach);
|
||||||
|
let fut = self
|
||||||
|
.service
|
||||||
|
.call(Link::new(link.clone(), self.state.clone()));
|
||||||
|
self.receivers.push((link, fut));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Frame::Detach(frm) => {
|
||||||
|
let session = self.conn.get_session(channel_id);
|
||||||
|
let cell = session.clone();
|
||||||
|
|
||||||
|
if self.control_srv.is_some() {
|
||||||
|
if let Some(link) = session.get_sender_link_by_handle(frm.handle) {
|
||||||
|
self.control_frame = Some(ControlFrame::new(
|
||||||
|
self.state.clone(),
|
||||||
|
Session::new(cell.clone()),
|
||||||
|
ControlFrameKind::DetachSender(frm, link.clone()),
|
||||||
|
));
|
||||||
|
|
||||||
|
return Ok(IncomingResult::Control);
|
||||||
|
} else if let Some(link) =
|
||||||
|
session.get_receiver_link_by_handle(frm.handle)
|
||||||
|
{
|
||||||
|
self.control_frame = Some(ControlFrame::new(
|
||||||
|
self.state.clone(),
|
||||||
|
Session::new(cell.clone()),
|
||||||
|
ControlFrameKind::DetachReceiver(frm, link.clone()),
|
||||||
|
));
|
||||||
|
|
||||||
|
return Ok(IncomingResult::Control);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
session.get_mut().handle_frame(Frame::Detach(frm));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
trace!("Unexpected frame {:#?}", frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Poll::Pending => break,
|
||||||
|
Poll::Ready(None) => return Ok(IncomingResult::Disconnect),
|
||||||
|
Poll::Ready(Some(Err(e))) => return Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(IncomingResult::Done)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io, St, Sr> Future for Dispatcher<Io, St, Sr>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite,
|
||||||
|
Sr: Service<Request = Link<St>, Response = ()>,
|
||||||
|
Sr::Error: fmt::Display + Into<Error>,
|
||||||
|
{
|
||||||
|
type Output = Result<(), AmqpCodecError>;
|
||||||
|
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||||
|
// process control frame
|
||||||
|
if !self.handle_control_fut(cx) {
|
||||||
|
return Poll::Pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check control frames service
|
||||||
|
if self.control_frame.is_some() {
|
||||||
|
let srv = self.control_srv.as_mut().unwrap();
|
||||||
|
match srv.poll_ready(cx) {
|
||||||
|
Poll::Ready(Ok(_)) => {
|
||||||
|
let frame = self.control_frame.as_ref().unwrap().clone();
|
||||||
|
let srv = self.control_srv.as_mut().unwrap();
|
||||||
|
self.control_fut = Some(srv.call(frame));
|
||||||
|
if !self.handle_control_fut(cx) {
|
||||||
|
return Poll::Pending;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Poll::Pending => return Poll::Pending,
|
||||||
|
Poll::Ready(Err(e)) => {
|
||||||
|
let frame = self.control_frame.take().unwrap();
|
||||||
|
self.handle_control_frame(&frame, Some(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.poll_incoming(cx)? {
|
||||||
|
IncomingResult::Control => return self.poll(cx),
|
||||||
|
IncomingResult::Disconnect => return Poll::Ready(Ok(())),
|
||||||
|
IncomingResult::Done => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
// process service responses
|
||||||
|
let mut idx = 0;
|
||||||
|
while idx < self.receivers.len() {
|
||||||
|
match unsafe { Pin::new_unchecked(&mut self.receivers[idx].1) }.poll(cx) {
|
||||||
|
Poll::Ready(Ok(_detach)) => {
|
||||||
|
let (link, _) = self.receivers.swap_remove(idx);
|
||||||
|
let _ = link.close();
|
||||||
|
}
|
||||||
|
Poll::Pending => idx += 1,
|
||||||
|
Poll::Ready(Err(e)) => {
|
||||||
|
let (link, _) = self.receivers.swap_remove(idx);
|
||||||
|
error!("Error in link handler: {}", e);
|
||||||
|
let _ = link.close_with_error(e.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = self.conn.poll_outgoing(cx);
|
||||||
|
self.conn.register_write_task(cx);
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
185
actix-amqp/src/server/errors.rs
Executable file
185
actix-amqp/src/server/errors.rs
Executable file
@ -0,0 +1,185 @@
|
|||||||
|
use std::io;
|
||||||
|
|
||||||
|
use amqp_codec::{protocol, AmqpCodecError, ProtocolIdError, SaslFrame};
|
||||||
|
use bytestring::ByteString;
|
||||||
|
use derive_more::Display;
|
||||||
|
|
||||||
|
pub use amqp_codec::protocol::Error;
|
||||||
|
|
||||||
|
/// Errors which can occur when attempting to handle amqp connection.
|
||||||
|
#[derive(Debug, Display)]
|
||||||
|
pub enum ServerError<E> {
|
||||||
|
#[display(fmt = "Message handler service error")]
|
||||||
|
/// Message handler service error
|
||||||
|
Service(E),
|
||||||
|
/// Control service init error
|
||||||
|
ControlServiceInit,
|
||||||
|
#[display(fmt = "Amqp error: {}", _0)]
|
||||||
|
/// Amqp error
|
||||||
|
Amqp(AmqpError),
|
||||||
|
#[display(fmt = "Protocol negotiation error: {}", _0)]
|
||||||
|
/// Amqp protocol negotiation error
|
||||||
|
Handshake(ProtocolIdError),
|
||||||
|
/// Amqp handshake timeout
|
||||||
|
HandshakeTimeout,
|
||||||
|
/// Amqp codec error
|
||||||
|
#[display(fmt = "Amqp codec error: {:?}", _0)]
|
||||||
|
Protocol(AmqpCodecError),
|
||||||
|
#[display(fmt = "Protocol error: {}", _0)]
|
||||||
|
/// Amqp protocol error
|
||||||
|
ProtocolError(Error),
|
||||||
|
#[display(fmt = "Expected open frame, got: {:?}", _0)]
|
||||||
|
Unexpected(protocol::Frame),
|
||||||
|
#[display(fmt = "Unexpected sasl frame: {:?}", _0)]
|
||||||
|
UnexpectedSaslFrame(SaslFrame),
|
||||||
|
#[display(fmt = "Unexpected sasl frame body: {:?}", _0)]
|
||||||
|
UnexpectedSaslBodyFrame(protocol::SaslFrameBody),
|
||||||
|
/// Peer disconnect
|
||||||
|
Disconnected,
|
||||||
|
/// Unexpected io error
|
||||||
|
Io(io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E> Into<protocol::Error> for ServerError<E> {
|
||||||
|
fn into(self) -> protocol::Error {
|
||||||
|
protocol::Error {
|
||||||
|
condition: protocol::AmqpError::InternalError.into(),
|
||||||
|
description: Some(ByteString::from(format!("{}", self))),
|
||||||
|
info: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E> From<AmqpError> for ServerError<E> {
|
||||||
|
fn from(err: AmqpError) -> Self {
|
||||||
|
ServerError::Amqp(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E> From<AmqpCodecError> for ServerError<E> {
|
||||||
|
fn from(err: AmqpCodecError) -> Self {
|
||||||
|
ServerError::Protocol(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E> From<ProtocolIdError> for ServerError<E> {
|
||||||
|
fn from(err: ProtocolIdError) -> Self {
|
||||||
|
ServerError::Handshake(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E> From<SaslFrame> for ServerError<E> {
|
||||||
|
fn from(err: SaslFrame) -> Self {
|
||||||
|
ServerError::UnexpectedSaslFrame(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E> From<io::Error> for ServerError<E> {
|
||||||
|
fn from(err: io::Error) -> Self {
|
||||||
|
ServerError::Io(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Display)]
|
||||||
|
#[display(fmt = "Amqp error: {:?} {:?} ({:?})", err, description, info)]
|
||||||
|
pub struct AmqpError {
|
||||||
|
err: protocol::AmqpError,
|
||||||
|
description: Option<ByteString>,
|
||||||
|
info: Option<protocol::Fields>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AmqpError {
|
||||||
|
pub fn new(err: protocol::AmqpError) -> Self {
|
||||||
|
AmqpError {
|
||||||
|
err,
|
||||||
|
description: None,
|
||||||
|
info: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn internal_error() -> Self {
|
||||||
|
Self::new(protocol::AmqpError::InternalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn not_found() -> Self {
|
||||||
|
Self::new(protocol::AmqpError::NotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unauthorized_access() -> Self {
|
||||||
|
Self::new(protocol::AmqpError::UnauthorizedAccess)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode_error() -> Self {
|
||||||
|
Self::new(protocol::AmqpError::DecodeError)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn invalid_field() -> Self {
|
||||||
|
Self::new(protocol::AmqpError::InvalidField)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn not_allowed() -> Self {
|
||||||
|
Self::new(protocol::AmqpError::NotAllowed)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn not_implemented() -> Self {
|
||||||
|
Self::new(protocol::AmqpError::NotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn description<T: AsRef<str>>(mut self, text: T) -> Self {
|
||||||
|
self.description = Some(ByteString::from(text.as_ref()));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_description(mut self, text: ByteString) -> Self {
|
||||||
|
self.description = Some(text);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<protocol::Error> for AmqpError {
|
||||||
|
fn into(self) -> protocol::Error {
|
||||||
|
protocol::Error {
|
||||||
|
condition: self.err.into(),
|
||||||
|
description: self.description,
|
||||||
|
info: self.info,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Display)]
|
||||||
|
#[display(fmt = "Link error: {:?} {:?} ({:?})", err, description, info)]
|
||||||
|
pub struct LinkError {
|
||||||
|
err: protocol::LinkError,
|
||||||
|
description: Option<ByteString>,
|
||||||
|
info: Option<protocol::Fields>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LinkError {
|
||||||
|
pub fn force_detach() -> Self {
|
||||||
|
LinkError {
|
||||||
|
err: protocol::LinkError::DetachForced,
|
||||||
|
description: None,
|
||||||
|
info: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn description<T: AsRef<str>>(mut self, text: T) -> Self {
|
||||||
|
self.description = Some(ByteString::from(text.as_ref()));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_description(mut self, text: ByteString) -> Self {
|
||||||
|
self.description = Some(text);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<protocol::Error> for LinkError {
|
||||||
|
fn into(self) -> protocol::Error {
|
||||||
|
protocol::Error {
|
||||||
|
condition: self.err.into(),
|
||||||
|
description: self.description,
|
||||||
|
info: self.info,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
actix-amqp/src/server/handshake.rs
Executable file
50
actix-amqp/src/server/handshake.rs
Executable file
@ -0,0 +1,50 @@
|
|||||||
|
use actix_service::{IntoServiceFactory, ServiceFactory};
|
||||||
|
|
||||||
|
use super::connect::ConnectAck;
|
||||||
|
|
||||||
|
pub fn handshake<Io, St, A, F>(srv: F) -> Handshake<Io, St, A>
|
||||||
|
where
|
||||||
|
F: IntoServiceFactory<A>,
|
||||||
|
A: ServiceFactory<Config = (), Response = ConnectAck<Io, St>>,
|
||||||
|
{
|
||||||
|
Handshake::new(srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Handshake<Io, St, A> {
|
||||||
|
a: A,
|
||||||
|
_t: std::marker::PhantomData<(Io, St)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io, St, A> Handshake<Io, St, A>
|
||||||
|
where
|
||||||
|
A: ServiceFactory<Config = ()>,
|
||||||
|
{
|
||||||
|
pub fn new<F>(srv: F) -> Handshake<Io, St, A>
|
||||||
|
where
|
||||||
|
F: IntoServiceFactory<A>,
|
||||||
|
{
|
||||||
|
Handshake {
|
||||||
|
a: srv.into_factory(),
|
||||||
|
_t: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io, St, A> Handshake<Io, St, A>
|
||||||
|
where
|
||||||
|
A: ServiceFactory<Config = (), Response = ConnectAck<Io, St>>,
|
||||||
|
{
|
||||||
|
pub fn sasl<F, B>(self, srv: F) -> actix_utils::either::Either<A, B>
|
||||||
|
where
|
||||||
|
F: IntoServiceFactory<B>,
|
||||||
|
B: ServiceFactory<
|
||||||
|
Config = (),
|
||||||
|
Response = A::Response,
|
||||||
|
Error = A::Error,
|
||||||
|
InitError = A::InitError,
|
||||||
|
>,
|
||||||
|
B::Error: Into<amqp_codec::protocol::Error>,
|
||||||
|
{
|
||||||
|
actix_utils::either::Either::new(self.a, srv.into_factory())
|
||||||
|
}
|
||||||
|
}
|
87
actix-amqp/src/server/link.rs
Executable file
87
actix-amqp/src/server/link.rs
Executable file
@ -0,0 +1,87 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use actix_router::Path;
|
||||||
|
use amqp_codec::protocol::Attach;
|
||||||
|
use bytestring::ByteString;
|
||||||
|
|
||||||
|
use crate::cell::Cell;
|
||||||
|
use crate::rcvlink::ReceiverLink;
|
||||||
|
use crate::server::State;
|
||||||
|
use crate::session::Session;
|
||||||
|
use crate::{Configuration, Handle};
|
||||||
|
|
||||||
|
pub struct Link<S> {
|
||||||
|
pub(crate) state: State<S>,
|
||||||
|
pub(crate) link: ReceiverLink,
|
||||||
|
pub(crate) path: Path<ByteString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Link<S> {
|
||||||
|
pub(crate) fn new(link: ReceiverLink, state: State<S>) -> Self {
|
||||||
|
Link {
|
||||||
|
state,
|
||||||
|
link,
|
||||||
|
path: Path::new(ByteString::from_static("")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path(&self) -> &Path<ByteString> {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path_mut(&mut self) -> &mut Path<ByteString> {
|
||||||
|
&mut self.path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn frame(&self) -> &Attach {
|
||||||
|
self.link.frame()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state(&self) -> &S {
|
||||||
|
self.state.get_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state_mut(&mut self) -> &mut S {
|
||||||
|
self.state.get_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle(&self) -> Handle {
|
||||||
|
self.link.handle()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn session(&self) -> &Session {
|
||||||
|
self.link.session()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn session_mut(&mut self) -> &mut Session {
|
||||||
|
self.link.session_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn link_credit(mut self, credit: u32) {
|
||||||
|
self.link.set_link_credit(credit);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Get remote connection configuration
|
||||||
|
pub fn remote_config(&self) -> &Configuration {
|
||||||
|
&self.link.remote_config()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Clone for Link<S> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
state: self.state.clone(),
|
||||||
|
link: self.link.clone(),
|
||||||
|
path: self.path.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> fmt::Debug for Link<S> {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
fmt.debug_struct("Link<S>")
|
||||||
|
.field("frame", self.link.frame())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
96
actix-amqp/src/server/message.rs
Executable file
96
actix-amqp/src/server/message.rs
Executable file
@ -0,0 +1,96 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use amqp_codec::protocol::{Accepted, DeliveryState, Error, Rejected, Transfer, TransferBody};
|
||||||
|
use amqp_codec::Decode;
|
||||||
|
use bytes::Bytes;
|
||||||
|
|
||||||
|
use crate::rcvlink::ReceiverLink;
|
||||||
|
use crate::session::Session;
|
||||||
|
|
||||||
|
use super::errors::AmqpError;
|
||||||
|
use super::State;
|
||||||
|
|
||||||
|
pub struct Message<S> {
|
||||||
|
state: State<S>,
|
||||||
|
frame: Transfer,
|
||||||
|
link: ReceiverLink,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Outcome {
|
||||||
|
Accept,
|
||||||
|
Reject,
|
||||||
|
Error(Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<T> for Outcome
|
||||||
|
where
|
||||||
|
T: Into<Error>,
|
||||||
|
{
|
||||||
|
fn from(err: T) -> Self {
|
||||||
|
Outcome::Error(err.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Outcome {
|
||||||
|
pub(crate) fn into_delivery_state(self) -> DeliveryState {
|
||||||
|
match self {
|
||||||
|
Outcome::Accept => DeliveryState::Accepted(Accepted {}),
|
||||||
|
Outcome::Reject => DeliveryState::Rejected(Rejected { error: None }),
|
||||||
|
Outcome::Error(e) => DeliveryState::Rejected(Rejected { error: Some(e) }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Message<S> {
|
||||||
|
pub(crate) fn new(state: State<S>, frame: Transfer, link: ReceiverLink) -> Self {
|
||||||
|
Message { state, frame, link }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state(&self) -> &S {
|
||||||
|
self.state.get_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state_mut(&mut self) -> &mut S {
|
||||||
|
self.state.get_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn session(&self) -> &Session {
|
||||||
|
self.link.session()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn session_mut(&mut self) -> &mut Session {
|
||||||
|
self.link.session_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn frame(&self) -> &Transfer {
|
||||||
|
&self.frame
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn body(&self) -> Option<&Bytes> {
|
||||||
|
match self.frame.body {
|
||||||
|
Some(TransferBody::Data(ref b)) => Some(b),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_message<T: Decode>(&self) -> Result<T, AmqpError> {
|
||||||
|
if let Some(TransferBody::Data(ref b)) = self.frame.body {
|
||||||
|
if let Ok((_, msg)) = T::decode(b) {
|
||||||
|
Ok(msg)
|
||||||
|
} else {
|
||||||
|
Err(AmqpError::decode_error().description("Can not decode message"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(AmqpError::invalid_field().description("Unknown body"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> fmt::Debug for Message<S> {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
fmt.debug_struct("Message<S>")
|
||||||
|
.field("frame", &self.frame)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
55
actix-amqp/src/server/mod.rs
Executable file
55
actix-amqp/src/server/mod.rs
Executable file
@ -0,0 +1,55 @@
|
|||||||
|
mod app;
|
||||||
|
mod connect;
|
||||||
|
mod control;
|
||||||
|
mod dispatcher;
|
||||||
|
pub mod errors;
|
||||||
|
mod handshake;
|
||||||
|
mod link;
|
||||||
|
mod message;
|
||||||
|
pub mod sasl;
|
||||||
|
mod service;
|
||||||
|
|
||||||
|
pub use self::app::App;
|
||||||
|
pub use self::connect::{Connect, ConnectAck, ConnectOpened};
|
||||||
|
pub use self::control::{ControlFrame, ControlFrameKind};
|
||||||
|
pub use self::handshake::{handshake, Handshake};
|
||||||
|
pub use self::link::Link;
|
||||||
|
pub use self::message::{Message, Outcome};
|
||||||
|
pub use self::sasl::Sasl;
|
||||||
|
pub use self::service::Server;
|
||||||
|
|
||||||
|
use crate::cell::Cell;
|
||||||
|
|
||||||
|
pub struct State<St>(Cell<St>);
|
||||||
|
|
||||||
|
impl<St> State<St> {
|
||||||
|
pub(crate) fn new(st: St) -> Self {
|
||||||
|
State(Cell::new(st))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn clone(&self) -> Self {
|
||||||
|
State(self.0.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ref(&self) -> &St {
|
||||||
|
self.0.get_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&mut self) -> &mut St {
|
||||||
|
self.0.get_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<St> std::ops::Deref for State<St> {
|
||||||
|
type Target = St;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.get_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<St> std::ops::DerefMut for State<St> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
self.get_mut()
|
||||||
|
}
|
||||||
|
}
|
338
actix-amqp/src/server/sasl.rs
Executable file
338
actix-amqp/src/server/sasl.rs
Executable file
@ -0,0 +1,338 @@
|
|||||||
|
use std::fmt;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||||
|
use actix_service::{Service, ServiceFactory};
|
||||||
|
use amqp_codec::protocol::{
|
||||||
|
self, ProtocolId, SaslChallenge, SaslCode, SaslFrameBody, SaslMechanisms, SaslOutcome, Symbols,
|
||||||
|
};
|
||||||
|
use amqp_codec::{AmqpCodec, AmqpFrame, ProtocolIdCodec, ProtocolIdError, SaslFrame};
|
||||||
|
use bytes::Bytes;
|
||||||
|
use bytestring::ByteString;
|
||||||
|
use futures::future::{err, ok, Either, Ready};
|
||||||
|
use futures::{SinkExt, StreamExt};
|
||||||
|
|
||||||
|
use super::connect::{ConnectAck, ConnectOpened};
|
||||||
|
use super::errors::{AmqpError, ServerError};
|
||||||
|
use crate::connection::ConnectionController;
|
||||||
|
|
||||||
|
pub struct Sasl<Io> {
|
||||||
|
framed: Framed<Io, ProtocolIdCodec>,
|
||||||
|
mechanisms: Symbols,
|
||||||
|
controller: ConnectionController,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io> fmt::Debug for Sasl<Io> {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
fmt.debug_struct("SaslAuth")
|
||||||
|
.field("mechanisms", &self.mechanisms)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io> Sasl<Io> {
|
||||||
|
pub(crate) fn new(
|
||||||
|
framed: Framed<Io, ProtocolIdCodec>,
|
||||||
|
controller: ConnectionController,
|
||||||
|
) -> Self {
|
||||||
|
Sasl {
|
||||||
|
framed,
|
||||||
|
controller,
|
||||||
|
mechanisms: Symbols::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io> Sasl<Io>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite,
|
||||||
|
{
|
||||||
|
/// Returns reference to io object
|
||||||
|
pub fn get_ref(&self) -> &Io {
|
||||||
|
self.framed.get_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns mutable reference to io object
|
||||||
|
pub fn get_mut(&mut self) -> &mut Io {
|
||||||
|
self.framed.get_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add supported sasl mechanism
|
||||||
|
pub fn mechanism<U: Into<String>>(mut self, symbol: U) -> Self {
|
||||||
|
self.mechanisms.push(ByteString::from(symbol.into()).into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize sasl auth procedure
|
||||||
|
pub async fn init(self) -> Result<Init<Io>, ServerError<()>> {
|
||||||
|
let Sasl {
|
||||||
|
framed,
|
||||||
|
mechanisms,
|
||||||
|
controller,
|
||||||
|
..
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
let mut framed = framed.into_framed(AmqpCodec::<SaslFrame>::new());
|
||||||
|
let frame = SaslMechanisms {
|
||||||
|
sasl_server_mechanisms: mechanisms,
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
|
||||||
|
framed.send(frame).await.map_err(ServerError::from)?;
|
||||||
|
let frame = framed
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.ok_or(ServerError::Disconnected)?
|
||||||
|
.map_err(ServerError::from)?;
|
||||||
|
|
||||||
|
match frame.body {
|
||||||
|
SaslFrameBody::SaslInit(frame) => Ok(Init {
|
||||||
|
frame,
|
||||||
|
framed,
|
||||||
|
controller,
|
||||||
|
}),
|
||||||
|
body => Err(ServerError::UnexpectedSaslBodyFrame(body)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialization stage of sasl negotiation
|
||||||
|
pub struct Init<Io> {
|
||||||
|
frame: protocol::SaslInit,
|
||||||
|
framed: Framed<Io, AmqpCodec<SaslFrame>>,
|
||||||
|
controller: ConnectionController,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io> fmt::Debug for Init<Io> {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
fmt.debug_struct("SaslInit")
|
||||||
|
.field("frame", &self.frame)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io> Init<Io>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite,
|
||||||
|
{
|
||||||
|
/// Sasl mechanism
|
||||||
|
pub fn mechanism(&self) -> &str {
|
||||||
|
self.frame.mechanism.as_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sasl initial response
|
||||||
|
pub fn initial_response(&self) -> Option<&[u8]> {
|
||||||
|
self.frame.initial_response.as_ref().map(|b| b.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sasl initial response
|
||||||
|
pub fn hostname(&self) -> Option<&str> {
|
||||||
|
self.frame.hostname.as_ref().map(|b| b.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns reference to io object
|
||||||
|
pub fn get_ref(&self) -> &Io {
|
||||||
|
self.framed.get_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns mutable reference to io object
|
||||||
|
pub fn get_mut(&mut self) -> &mut Io {
|
||||||
|
self.framed.get_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initiate sasl challenge
|
||||||
|
pub async fn challenge(self) -> Result<Response<Io>, ServerError<()>> {
|
||||||
|
self.challenge_with(Bytes::new()).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initiate sasl challenge with challenge payload
|
||||||
|
pub async fn challenge_with(self, challenge: Bytes) -> Result<Response<Io>, ServerError<()>> {
|
||||||
|
let mut framed = self.framed;
|
||||||
|
let controller = self.controller;
|
||||||
|
let frame = SaslChallenge { challenge }.into();
|
||||||
|
|
||||||
|
framed.send(frame).await.map_err(ServerError::from)?;
|
||||||
|
let frame = framed
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.ok_or(ServerError::Disconnected)?
|
||||||
|
.map_err(ServerError::from)?;
|
||||||
|
|
||||||
|
match frame.body {
|
||||||
|
SaslFrameBody::SaslResponse(frame) => Ok(Response {
|
||||||
|
frame,
|
||||||
|
framed,
|
||||||
|
controller,
|
||||||
|
}),
|
||||||
|
body => Err(ServerError::UnexpectedSaslBodyFrame(body)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sasl challenge outcome
|
||||||
|
pub async fn outcome(self, code: SaslCode) -> Result<Success<Io>, ServerError<()>> {
|
||||||
|
let mut framed = self.framed;
|
||||||
|
let controller = self.controller;
|
||||||
|
|
||||||
|
let frame = SaslOutcome {
|
||||||
|
code,
|
||||||
|
additional_data: None,
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
framed.send(frame).await.map_err(ServerError::from)?;
|
||||||
|
|
||||||
|
Ok(Success { framed, controller })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Response<Io> {
|
||||||
|
frame: protocol::SaslResponse,
|
||||||
|
framed: Framed<Io, AmqpCodec<SaslFrame>>,
|
||||||
|
controller: ConnectionController,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io> fmt::Debug for Response<Io> {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
fmt.debug_struct("SaslResponse")
|
||||||
|
.field("frame", &self.frame)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io> Response<Io>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite,
|
||||||
|
{
|
||||||
|
/// Client response payload
|
||||||
|
pub fn response(&self) -> &[u8] {
|
||||||
|
&self.frame.response[..]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sasl challenge outcome
|
||||||
|
pub async fn outcome(self, code: SaslCode) -> Result<Success<Io>, ServerError<()>> {
|
||||||
|
let mut framed = self.framed;
|
||||||
|
let controller = self.controller;
|
||||||
|
let frame = SaslOutcome {
|
||||||
|
code,
|
||||||
|
additional_data: None,
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
|
||||||
|
framed.send(frame).await.map_err(ServerError::from)?;
|
||||||
|
framed
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.ok_or(ServerError::Disconnected)?
|
||||||
|
.map_err(|res| ServerError::from(res))?;
|
||||||
|
|
||||||
|
Ok(Success { framed, controller })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Success<Io> {
|
||||||
|
framed: Framed<Io, AmqpCodec<SaslFrame>>,
|
||||||
|
controller: ConnectionController,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io> Success<Io>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite,
|
||||||
|
{
|
||||||
|
/// Returns reference to io object
|
||||||
|
pub fn get_ref(&self) -> &Io {
|
||||||
|
self.framed.get_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns mutable reference to io object
|
||||||
|
pub fn get_mut(&mut self) -> &mut Io {
|
||||||
|
self.framed.get_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait for connection open frame
|
||||||
|
pub async fn open(self) -> Result<ConnectOpened<Io>, ServerError<()>> {
|
||||||
|
let mut framed = self.framed.into_framed(ProtocolIdCodec);
|
||||||
|
let mut controller = self.controller;
|
||||||
|
|
||||||
|
let protocol = framed
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.ok_or(ServerError::from(ProtocolIdError::Disconnected))?
|
||||||
|
.map_err(ServerError::from)?;
|
||||||
|
|
||||||
|
match protocol {
|
||||||
|
ProtocolId::Amqp => {
|
||||||
|
// confirm protocol
|
||||||
|
framed
|
||||||
|
.send(ProtocolId::Amqp)
|
||||||
|
.await
|
||||||
|
.map_err(ServerError::from)?;
|
||||||
|
|
||||||
|
// Wait for connection open frame
|
||||||
|
let mut framed = framed.into_framed(AmqpCodec::<AmqpFrame>::new());
|
||||||
|
let frame = framed
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.ok_or(ServerError::Disconnected)?
|
||||||
|
.map_err(ServerError::from)?;
|
||||||
|
|
||||||
|
let frame = frame.into_parts().1;
|
||||||
|
match frame {
|
||||||
|
protocol::Frame::Open(frame) => {
|
||||||
|
trace!("Got open frame: {:?}", frame);
|
||||||
|
controller.set_remote((&frame).into());
|
||||||
|
Ok(ConnectOpened::new(frame, framed, controller))
|
||||||
|
}
|
||||||
|
frame => Err(ServerError::Unexpected(frame)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
proto => Err(ProtocolIdError::Unexpected {
|
||||||
|
exp: ProtocolId::Amqp,
|
||||||
|
got: proto,
|
||||||
|
}
|
||||||
|
.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create service factory with disabled sasl support
|
||||||
|
pub fn no_sasl<Io, St, E>() -> NoSaslService<Io, St, E> {
|
||||||
|
NoSaslService::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NoSaslService<Io, St, E>(std::marker::PhantomData<(Io, St, E)>);
|
||||||
|
|
||||||
|
impl<Io, St, E> Default for NoSaslService<Io, St, E> {
|
||||||
|
fn default() -> Self {
|
||||||
|
NoSaslService(std::marker::PhantomData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io, St, E> ServiceFactory for NoSaslService<Io, St, E> {
|
||||||
|
type Config = ();
|
||||||
|
type Request = Sasl<Io>;
|
||||||
|
type Response = ConnectAck<Io, St>;
|
||||||
|
type Error = AmqpError;
|
||||||
|
type InitError = E;
|
||||||
|
type Service = NoSaslService<Io, St, E>;
|
||||||
|
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||||
|
|
||||||
|
fn new_service(&self, _: ()) -> Self::Future {
|
||||||
|
ok(NoSaslService(std::marker::PhantomData))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io, St, E> Service for NoSaslService<Io, St, E> {
|
||||||
|
type Request = Sasl<Io>;
|
||||||
|
type Response = ConnectAck<Io, St>;
|
||||||
|
type Error = AmqpError;
|
||||||
|
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, _: Self::Request) -> Self::Future {
|
||||||
|
err(AmqpError::not_implemented())
|
||||||
|
}
|
||||||
|
}
|
360
actix-amqp/src/server/service.rs
Executable file
360
actix-amqp/src/server/service.rs
Executable file
@ -0,0 +1,360 @@
|
|||||||
|
use std::future::Future;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
use std::{fmt, time};
|
||||||
|
|
||||||
|
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||||
|
use actix_service::{boxed, IntoServiceFactory, Service, ServiceFactory};
|
||||||
|
use amqp_codec::protocol::{Error, ProtocolId};
|
||||||
|
use amqp_codec::{AmqpCodecError, AmqpFrame, ProtocolIdCodec, ProtocolIdError};
|
||||||
|
use futures::future::{err, poll_fn, Either};
|
||||||
|
use futures::{FutureExt, SinkExt, StreamExt};
|
||||||
|
|
||||||
|
use crate::cell::Cell;
|
||||||
|
use crate::connection::{Connection, ConnectionController};
|
||||||
|
use crate::Configuration;
|
||||||
|
|
||||||
|
use super::connect::{Connect, ConnectAck};
|
||||||
|
use super::control::{ControlFrame, ControlFrameNewService};
|
||||||
|
use super::dispatcher::Dispatcher;
|
||||||
|
use super::errors::{LinkError, ServerError};
|
||||||
|
use super::link::Link;
|
||||||
|
use super::sasl::Sasl;
|
||||||
|
use super::State;
|
||||||
|
|
||||||
|
/// Amqp connection type
|
||||||
|
pub type AmqpConnect<Io> = either::Either<Connect<Io>, Sasl<Io>>;
|
||||||
|
|
||||||
|
/// Server dispatcher factory
|
||||||
|
pub struct Server<Io, St, Cn: ServiceFactory> {
|
||||||
|
connect: Cn,
|
||||||
|
config: Configuration,
|
||||||
|
control: Option<ControlFrameNewService<St>>,
|
||||||
|
disconnect: Option<Box<dyn Fn(&mut St, Option<&ServerError<Cn::Error>>)>>,
|
||||||
|
max_size: usize,
|
||||||
|
handshake_timeout: u64,
|
||||||
|
_t: PhantomData<(Io, St)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct ServerInner<St, Cn: ServiceFactory, Pb> {
|
||||||
|
connect: Cn,
|
||||||
|
publish: Pb,
|
||||||
|
config: Configuration,
|
||||||
|
control: Option<ControlFrameNewService<St>>,
|
||||||
|
disconnect: Option<Box<dyn Fn(&mut St, Option<&ServerError<Cn::Error>>)>>,
|
||||||
|
max_size: usize,
|
||||||
|
handshake_timeout: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io, St, Cn> Server<Io, St, Cn>
|
||||||
|
where
|
||||||
|
St: 'static,
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
Cn: ServiceFactory<Config = (), Request = AmqpConnect<Io>, Response = ConnectAck<Io, St>>
|
||||||
|
+ 'static,
|
||||||
|
{
|
||||||
|
/// Create server factory and provide connect service
|
||||||
|
pub fn new<F>(connect: F) -> Self
|
||||||
|
where
|
||||||
|
F: IntoServiceFactory<Cn>,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
connect: connect.into_factory(),
|
||||||
|
config: Configuration::default(),
|
||||||
|
control: None,
|
||||||
|
disconnect: None,
|
||||||
|
max_size: 0,
|
||||||
|
handshake_timeout: 0,
|
||||||
|
_t: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provide connection configuration
|
||||||
|
pub fn config(mut self, config: Configuration) -> Self {
|
||||||
|
self.config = config;
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set handshake timeout in millis.
|
||||||
|
///
|
||||||
|
/// By default handshake timeuot is disabled.
|
||||||
|
pub fn handshake_timeout(mut self, timeout: u64) -> Self {
|
||||||
|
self.handshake_timeout = timeout;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Service to call with control frames
|
||||||
|
pub fn control<F, S>(self, f: F) -> Self
|
||||||
|
where
|
||||||
|
F: IntoServiceFactory<S>,
|
||||||
|
S: ServiceFactory<Config = (), Request = ControlFrame<St>, Response = (), InitError = ()>
|
||||||
|
+ 'static,
|
||||||
|
S::Error: Into<LinkError>,
|
||||||
|
{
|
||||||
|
Server {
|
||||||
|
connect: self.connect,
|
||||||
|
config: self.config,
|
||||||
|
disconnect: self.disconnect,
|
||||||
|
control: Some(boxed::factory(
|
||||||
|
f.into_factory()
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
.map_init_err(|e| e.into()),
|
||||||
|
)),
|
||||||
|
max_size: self.max_size,
|
||||||
|
handshake_timeout: self.handshake_timeout,
|
||||||
|
_t: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Callback to execute on disconnect
|
||||||
|
///
|
||||||
|
/// Second parameter indicates error occured during disconnect.
|
||||||
|
pub fn disconnect<F, Out>(self, disconnect: F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(&mut St, Option<&ServerError<Cn::Error>>) -> Out + 'static,
|
||||||
|
Out: Future + 'static,
|
||||||
|
{
|
||||||
|
Server {
|
||||||
|
connect: self.connect,
|
||||||
|
config: self.config,
|
||||||
|
control: self.control,
|
||||||
|
disconnect: Some(Box::new(move |st, err| {
|
||||||
|
let fut = disconnect(st, err);
|
||||||
|
actix_rt::spawn(fut.map(|_| ()));
|
||||||
|
})),
|
||||||
|
max_size: self.max_size,
|
||||||
|
handshake_timeout: self.handshake_timeout,
|
||||||
|
_t: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set service to execute for incoming links and create service factory
|
||||||
|
pub fn finish<F, Pb>(
|
||||||
|
self,
|
||||||
|
service: F,
|
||||||
|
) -> impl ServiceFactory<Config = (), Request = Io, Response = (), Error = ServerError<Cn::Error>>
|
||||||
|
where
|
||||||
|
F: IntoServiceFactory<Pb>,
|
||||||
|
Pb: ServiceFactory<Config = State<St>, Request = Link<St>, Response = ()> + 'static,
|
||||||
|
Pb::Error: fmt::Display + Into<Error>,
|
||||||
|
Pb::InitError: fmt::Display + Into<Error>,
|
||||||
|
{
|
||||||
|
ServerImpl {
|
||||||
|
inner: Cell::new(ServerInner {
|
||||||
|
connect: self.connect,
|
||||||
|
config: self.config,
|
||||||
|
publish: service.into_factory(),
|
||||||
|
control: self.control,
|
||||||
|
disconnect: self.disconnect,
|
||||||
|
max_size: self.max_size,
|
||||||
|
handshake_timeout: self.handshake_timeout,
|
||||||
|
}),
|
||||||
|
_t: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ServerImpl<Io, St, Cn: ServiceFactory, Pb> {
|
||||||
|
inner: Cell<ServerInner<St, Cn, Pb>>,
|
||||||
|
_t: PhantomData<(Io,)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io, St, Cn, Pb> ServiceFactory for ServerImpl<Io, St, Cn, Pb>
|
||||||
|
where
|
||||||
|
St: 'static,
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
Cn: ServiceFactory<Config = (), Request = AmqpConnect<Io>, Response = ConnectAck<Io, St>>
|
||||||
|
+ 'static,
|
||||||
|
Pb: ServiceFactory<Config = State<St>, Request = Link<St>, Response = ()> + 'static,
|
||||||
|
Pb::Error: fmt::Display + Into<Error>,
|
||||||
|
Pb::InitError: fmt::Display + Into<Error>,
|
||||||
|
{
|
||||||
|
type Config = ();
|
||||||
|
type Request = Io;
|
||||||
|
type Response = ();
|
||||||
|
type Error = ServerError<Cn::Error>;
|
||||||
|
type Service = ServerImplService<Io, St, Cn, Pb>;
|
||||||
|
type InitError = Cn::InitError;
|
||||||
|
type Future = Pin<Box<dyn Future<Output = Result<Self::Service, Cn::InitError>>>>;
|
||||||
|
|
||||||
|
fn new_service(&self, _: ()) -> Self::Future {
|
||||||
|
let inner = self.inner.clone();
|
||||||
|
|
||||||
|
Box::pin(async move {
|
||||||
|
inner
|
||||||
|
.connect
|
||||||
|
.new_service(())
|
||||||
|
.await
|
||||||
|
.map(move |connect| ServerImplService {
|
||||||
|
inner,
|
||||||
|
connect: Cell::new(connect),
|
||||||
|
_t: PhantomData,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ServerImplService<Io, St, Cn: ServiceFactory, Pb> {
|
||||||
|
connect: Cell<Cn::Service>,
|
||||||
|
inner: Cell<ServerInner<St, Cn, Pb>>,
|
||||||
|
_t: PhantomData<(Io,)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io, St, Cn, Pb> Service for ServerImplService<Io, St, Cn, Pb>
|
||||||
|
where
|
||||||
|
St: 'static,
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
Cn: ServiceFactory<Config = (), Request = AmqpConnect<Io>, Response = ConnectAck<Io, St>>
|
||||||
|
+ 'static,
|
||||||
|
Pb: ServiceFactory<Config = State<St>, Request = Link<St>, Response = ()> + 'static,
|
||||||
|
Pb::Error: fmt::Display + Into<Error>,
|
||||||
|
Pb::InitError: fmt::Display + Into<Error>,
|
||||||
|
{
|
||||||
|
type Request = Io;
|
||||||
|
type Response = ();
|
||||||
|
type Error = ServerError<Cn::Error>;
|
||||||
|
type Future = Pin<Box<dyn Future<Output = 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(|res| res.map_err(|e| ServerError::Service(e)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: Self::Request) -> Self::Future {
|
||||||
|
let timeout = self.inner.handshake_timeout;
|
||||||
|
if timeout == 0 {
|
||||||
|
Box::pin(handshake(
|
||||||
|
self.inner.max_size,
|
||||||
|
self.connect.clone(),
|
||||||
|
self.inner.clone(),
|
||||||
|
req,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Box::pin(
|
||||||
|
actix_rt::time::timeout(
|
||||||
|
time::Duration::from_millis(timeout),
|
||||||
|
handshake(
|
||||||
|
self.inner.max_size,
|
||||||
|
self.connect.clone(),
|
||||||
|
self.inner.clone(),
|
||||||
|
req,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.map(|res| match res {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(_) => Err(ServerError::HandshakeTimeout),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handshake<Io, St, Cn: ServiceFactory, Pb>(
|
||||||
|
max_size: usize,
|
||||||
|
connect: Cell<Cn::Service>,
|
||||||
|
inner: Cell<ServerInner<St, Cn, Pb>>,
|
||||||
|
io: Io,
|
||||||
|
) -> Result<(), ServerError<Cn::Error>>
|
||||||
|
where
|
||||||
|
St: 'static,
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
Cn: ServiceFactory<Config = (), Request = AmqpConnect<Io>, Response = ConnectAck<Io, St>>,
|
||||||
|
Pb: ServiceFactory<Config = State<St>, Request = Link<St>, Response = ()> + 'static,
|
||||||
|
Pb::Error: fmt::Display + Into<Error>,
|
||||||
|
Pb::InitError: fmt::Display + Into<Error>,
|
||||||
|
{
|
||||||
|
let inner2 = inner.clone();
|
||||||
|
let mut framed = Framed::new(io, ProtocolIdCodec);
|
||||||
|
|
||||||
|
let protocol = framed
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.ok_or(ServerError::Disconnected)?
|
||||||
|
.map_err(ServerError::Handshake)?;
|
||||||
|
|
||||||
|
let (st, srv, conn) = match protocol {
|
||||||
|
// start amqp processing
|
||||||
|
ProtocolId::Amqp | ProtocolId::AmqpSasl => {
|
||||||
|
framed.send(protocol).await.map_err(ServerError::from)?;
|
||||||
|
|
||||||
|
let cfg = inner.get_ref().config.clone();
|
||||||
|
let controller = ConnectionController::new(cfg.clone());
|
||||||
|
|
||||||
|
let ack = connect
|
||||||
|
.get_mut()
|
||||||
|
.call(if protocol == ProtocolId::Amqp {
|
||||||
|
either::Either::Left(Connect::new(framed, controller))
|
||||||
|
} else {
|
||||||
|
either::Either::Right(Sasl::new(framed, controller))
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| ServerError::Service(e))?;
|
||||||
|
|
||||||
|
let (st, mut framed, controller) = ack.into_inner();
|
||||||
|
let st = State::new(st);
|
||||||
|
framed.get_codec_mut().max_size(max_size);
|
||||||
|
|
||||||
|
// confirm Open
|
||||||
|
let local = cfg.to_open();
|
||||||
|
framed
|
||||||
|
.send(AmqpFrame::new(0, local.into()))
|
||||||
|
.await
|
||||||
|
.map_err(ServerError::from)?;
|
||||||
|
|
||||||
|
let conn = Connection::new_server(framed, controller.0, None);
|
||||||
|
|
||||||
|
// create publish service
|
||||||
|
let srv = inner.publish.new_service(st.clone()).await.map_err(|e| {
|
||||||
|
error!("Can not construct app service");
|
||||||
|
ServerError::ProtocolError(e.into())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
(st, srv, conn)
|
||||||
|
}
|
||||||
|
ProtocolId::AmqpTls => {
|
||||||
|
return Err(ServerError::from(ProtocolIdError::Unexpected {
|
||||||
|
exp: ProtocolId::Amqp,
|
||||||
|
got: ProtocolId::AmqpTls,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut st2 = st.clone();
|
||||||
|
|
||||||
|
if let Some(ref control_srv) = inner2.control {
|
||||||
|
let control = control_srv
|
||||||
|
.new_service(())
|
||||||
|
.await
|
||||||
|
.map_err(|_| ServerError::ControlServiceInit)?;
|
||||||
|
|
||||||
|
let res = Dispatcher::new(conn, st, srv, Some(control))
|
||||||
|
.await
|
||||||
|
.map_err(ServerError::from);
|
||||||
|
|
||||||
|
if inner2.disconnect.is_some() {
|
||||||
|
(*inner2.get_mut().disconnect.as_mut().unwrap())(st2.get_mut(), res.as_ref().err())
|
||||||
|
}
|
||||||
|
res
|
||||||
|
} else {
|
||||||
|
let res = Dispatcher::new(conn, st, srv, None)
|
||||||
|
.await
|
||||||
|
.map_err(ServerError::from);
|
||||||
|
|
||||||
|
if inner2.disconnect.is_some() {
|
||||||
|
(*inner2.get_mut().disconnect.as_mut().unwrap())(st2.get_mut(), res.as_ref().err())
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
65
actix-amqp/src/service.rs
Executable file
65
actix-amqp/src/service.rs
Executable file
@ -0,0 +1,65 @@
|
|||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||||
|
use actix_service::Service;
|
||||||
|
use futures::{Future, SinkExt, StreamExt};
|
||||||
|
|
||||||
|
use amqp_codec::protocol::ProtocolId;
|
||||||
|
use amqp_codec::{ProtocolIdCodec, ProtocolIdError};
|
||||||
|
|
||||||
|
pub struct ProtocolNegotiation<T> {
|
||||||
|
proto: ProtocolId,
|
||||||
|
_r: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Clone for ProtocolNegotiation<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
ProtocolNegotiation {
|
||||||
|
proto: self.proto.clone(),
|
||||||
|
_r: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ProtocolNegotiation<T> {
|
||||||
|
pub fn new(proto: ProtocolId) -> Self {
|
||||||
|
ProtocolNegotiation {
|
||||||
|
proto,
|
||||||
|
_r: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Service for ProtocolNegotiation<T>
|
||||||
|
where
|
||||||
|
T: AsyncRead + AsyncWrite + 'static,
|
||||||
|
{
|
||||||
|
type Request = Framed<T, ProtocolIdCodec>;
|
||||||
|
type Response = Framed<T, ProtocolIdCodec>;
|
||||||
|
type Error = ProtocolIdError;
|
||||||
|
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, mut framed: Framed<T, ProtocolIdCodec>) -> Self::Future {
|
||||||
|
let proto = self.proto;
|
||||||
|
|
||||||
|
Box::pin(async move {
|
||||||
|
framed.send(proto).await?;
|
||||||
|
|
||||||
|
let protocol = framed.next().await.ok_or(ProtocolIdError::Disconnected)??;
|
||||||
|
if proto == protocol {
|
||||||
|
Ok(framed)
|
||||||
|
} else {
|
||||||
|
Err(ProtocolIdError::Unexpected {
|
||||||
|
exp: proto,
|
||||||
|
got: protocol,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
911
actix-amqp/src/session.rs
Executable file
911
actix-amqp/src/session.rs
Executable file
@ -0,0 +1,911 @@
|
|||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::future::Future;
|
||||||
|
|
||||||
|
use actix_utils::oneshot;
|
||||||
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
|
use bytestring::ByteString;
|
||||||
|
use either::Either;
|
||||||
|
use futures::future::ok;
|
||||||
|
use fxhash::FxHashMap;
|
||||||
|
use slab::Slab;
|
||||||
|
|
||||||
|
use amqp_codec::protocol::{
|
||||||
|
Accepted, Attach, DeliveryNumber, DeliveryState, Detach, Disposition, Error, Flow, Frame,
|
||||||
|
Handle, ReceiverSettleMode, Role, SenderSettleMode, Transfer, TransferBody, TransferNumber,
|
||||||
|
};
|
||||||
|
use amqp_codec::AmqpFrame;
|
||||||
|
|
||||||
|
use crate::cell::Cell;
|
||||||
|
use crate::connection::ConnectionController;
|
||||||
|
use crate::errors::AmqpTransportError;
|
||||||
|
use crate::rcvlink::{ReceiverLink, ReceiverLinkBuilder, ReceiverLinkInner};
|
||||||
|
use crate::sndlink::{SenderLink, SenderLinkBuilder, SenderLinkInner};
|
||||||
|
use crate::{Configuration, DeliveryPromise};
|
||||||
|
|
||||||
|
const INITIAL_OUTGOING_ID: TransferNumber = 0;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Session {
|
||||||
|
pub(crate) inner: Cell<SessionInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Session {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.inner.get_mut().drop_session()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for Session {
|
||||||
|
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
fmt.debug_struct("Session").finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Session {
|
||||||
|
pub(crate) fn new(inner: Cell<SessionInner>) -> Session {
|
||||||
|
Session { inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Get remote connection configuration
|
||||||
|
pub fn remote_config(&self) -> &Configuration {
|
||||||
|
self.inner.connection.remote_config()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close(&self) -> impl Future<Output = Result<(), AmqpTransportError>> {
|
||||||
|
ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_sender_link(&self, name: &str) -> Option<&SenderLink> {
|
||||||
|
let inner = self.inner.get_ref();
|
||||||
|
|
||||||
|
if let Some(id) = inner.links_by_name.get(name) {
|
||||||
|
if let Some(Either::Left(SenderLinkState::Established(ref link))) = inner.links.get(*id)
|
||||||
|
{
|
||||||
|
return Some(link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_sender_link_by_handle(&self, hnd: Handle) -> Option<&SenderLink> {
|
||||||
|
self.inner.get_ref().get_sender_link_by_handle(hnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_receiver_link_by_handle(&self, hnd: Handle) -> Option<&ReceiverLink> {
|
||||||
|
self.inner.get_ref().get_receiver_link_by_handle(hnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open sender link
|
||||||
|
pub fn build_sender_link<T: Into<String>, U: Into<String>>(
|
||||||
|
&mut self,
|
||||||
|
name: U,
|
||||||
|
address: T,
|
||||||
|
) -> SenderLinkBuilder {
|
||||||
|
let name = ByteString::from(name.into());
|
||||||
|
let address = ByteString::from(address.into());
|
||||||
|
SenderLinkBuilder::new(name, address, self.inner.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open receiver link
|
||||||
|
pub fn build_receiver_link<T: Into<String>, U: Into<String>>(
|
||||||
|
&mut self,
|
||||||
|
name: U,
|
||||||
|
address: T,
|
||||||
|
) -> ReceiverLinkBuilder {
|
||||||
|
let name = ByteString::from(name.into());
|
||||||
|
let address = ByteString::from(address.into());
|
||||||
|
ReceiverLinkBuilder::new(name, address, self.inner.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Detach receiver link
|
||||||
|
pub fn detach_receiver_link(
|
||||||
|
&mut self,
|
||||||
|
handle: Handle,
|
||||||
|
error: Option<Error>,
|
||||||
|
) -> impl Future<Output = Result<(), AmqpTransportError>> {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|
||||||
|
self.inner
|
||||||
|
.get_mut()
|
||||||
|
.detach_receiver_link(handle, false, error, tx);
|
||||||
|
|
||||||
|
async move {
|
||||||
|
match rx.await {
|
||||||
|
Ok(Ok(_)) => Ok(()),
|
||||||
|
Ok(Err(e)) => Err(e),
|
||||||
|
Err(_) => Err(AmqpTransportError::Disconnected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wait_disposition(
|
||||||
|
&mut self,
|
||||||
|
id: DeliveryNumber,
|
||||||
|
) -> impl Future<Output = Result<Disposition, AmqpTransportError>> {
|
||||||
|
self.inner.get_mut().wait_disposition(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum SenderLinkState {
|
||||||
|
Opening(oneshot::Sender<SenderLink>),
|
||||||
|
Established(SenderLink),
|
||||||
|
Closing(Option<oneshot::Sender<Result<(), AmqpTransportError>>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum ReceiverLinkState {
|
||||||
|
Opening(Option<Cell<ReceiverLinkInner>>),
|
||||||
|
OpeningLocal(
|
||||||
|
Option<(
|
||||||
|
Cell<ReceiverLinkInner>,
|
||||||
|
oneshot::Sender<Result<ReceiverLink, AmqpTransportError>>,
|
||||||
|
)>,
|
||||||
|
),
|
||||||
|
Established(ReceiverLink),
|
||||||
|
Closing(Option<oneshot::Sender<Result<(), AmqpTransportError>>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SenderLinkState {
|
||||||
|
fn is_opening(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
SenderLinkState::Opening(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReceiverLinkState {
|
||||||
|
fn is_opening(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
ReceiverLinkState::OpeningLocal(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct SessionInner {
|
||||||
|
id: usize,
|
||||||
|
connection: ConnectionController,
|
||||||
|
next_outgoing_id: TransferNumber,
|
||||||
|
local: bool,
|
||||||
|
|
||||||
|
remote_channel_id: u16,
|
||||||
|
next_incoming_id: TransferNumber,
|
||||||
|
remote_outgoing_window: u32,
|
||||||
|
remote_incoming_window: u32,
|
||||||
|
|
||||||
|
unsettled_deliveries: FxHashMap<DeliveryNumber, DeliveryPromise>,
|
||||||
|
|
||||||
|
links: Slab<Either<SenderLinkState, ReceiverLinkState>>,
|
||||||
|
links_by_name: FxHashMap<ByteString, usize>,
|
||||||
|
remote_handles: FxHashMap<Handle, usize>,
|
||||||
|
pending_transfers: VecDeque<PendingTransfer>,
|
||||||
|
disposition_subscribers: FxHashMap<DeliveryNumber, oneshot::Sender<Disposition>>,
|
||||||
|
error: Option<AmqpTransportError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PendingTransfer {
|
||||||
|
link_handle: Handle,
|
||||||
|
idx: u32,
|
||||||
|
body: Option<TransferBody>,
|
||||||
|
promise: DeliveryPromise,
|
||||||
|
tag: Option<Bytes>,
|
||||||
|
settled: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SessionInner {
|
||||||
|
pub fn new(
|
||||||
|
id: usize,
|
||||||
|
local: bool,
|
||||||
|
connection: ConnectionController,
|
||||||
|
remote_channel_id: u16,
|
||||||
|
next_incoming_id: DeliveryNumber,
|
||||||
|
remote_incoming_window: u32,
|
||||||
|
remote_outgoing_window: u32,
|
||||||
|
) -> SessionInner {
|
||||||
|
SessionInner {
|
||||||
|
id,
|
||||||
|
local,
|
||||||
|
connection,
|
||||||
|
next_incoming_id,
|
||||||
|
remote_channel_id,
|
||||||
|
remote_incoming_window,
|
||||||
|
remote_outgoing_window,
|
||||||
|
next_outgoing_id: INITIAL_OUTGOING_ID,
|
||||||
|
unsettled_deliveries: FxHashMap::default(),
|
||||||
|
links: Slab::new(),
|
||||||
|
links_by_name: FxHashMap::default(),
|
||||||
|
remote_handles: FxHashMap::default(),
|
||||||
|
pending_transfers: VecDeque::new(),
|
||||||
|
disposition_subscribers: FxHashMap::default(),
|
||||||
|
error: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Local channel id
|
||||||
|
pub fn id(&self) -> u16 {
|
||||||
|
self.id as u16
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set error. New operations will return error.
|
||||||
|
pub(crate) fn set_error(&mut self, err: AmqpTransportError) {
|
||||||
|
// drop pending transfers
|
||||||
|
for tr in self.pending_transfers.drain(..) {
|
||||||
|
let _ = tr.promise.send(Err(err.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// drop links
|
||||||
|
self.links_by_name.clear();
|
||||||
|
for (_, st) in self.links.iter_mut() {
|
||||||
|
match st {
|
||||||
|
Either::Left(SenderLinkState::Opening(_)) => (),
|
||||||
|
Either::Left(SenderLinkState::Established(ref mut link)) => {
|
||||||
|
link.inner.get_mut().set_error(err.clone())
|
||||||
|
}
|
||||||
|
Either::Left(SenderLinkState::Closing(ref mut link)) => {
|
||||||
|
if let Some(tx) = link.take() {
|
||||||
|
let _ = tx.send(Err(err.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.links.clear();
|
||||||
|
|
||||||
|
self.error = Some(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drop_session(&mut self) {
|
||||||
|
self.connection.drop_session_copy(self.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_disposition(
|
||||||
|
&mut self,
|
||||||
|
id: DeliveryNumber,
|
||||||
|
) -> impl Future<Output = Result<Disposition, AmqpTransportError>> {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
self.disposition_subscribers.insert(id, tx);
|
||||||
|
async move { rx.await.map_err(|_| AmqpTransportError::Disconnected) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register remote sender link
|
||||||
|
pub(crate) fn confirm_sender_link(&mut self, cell: Cell<SessionInner>, attach: &Attach) {
|
||||||
|
trace!("Remote sender link opened: {:?}", attach.name());
|
||||||
|
let entry = self.links.vacant_entry();
|
||||||
|
let token = entry.key();
|
||||||
|
let delivery_count = attach.initial_delivery_count.unwrap_or(0);
|
||||||
|
|
||||||
|
let mut name = None;
|
||||||
|
if let Some(ref source) = attach.source {
|
||||||
|
if let Some(ref addr) = source.address {
|
||||||
|
name = Some(addr.clone());
|
||||||
|
self.links_by_name.insert(addr.clone(), token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.remote_handles.insert(attach.handle(), token);
|
||||||
|
let link = Cell::new(SenderLinkInner::new(
|
||||||
|
token,
|
||||||
|
name.unwrap_or_else(|| ByteString::default()),
|
||||||
|
attach.handle(),
|
||||||
|
delivery_count,
|
||||||
|
cell,
|
||||||
|
));
|
||||||
|
entry.insert(Either::Left(SenderLinkState::Established(SenderLink::new(
|
||||||
|
link,
|
||||||
|
))));
|
||||||
|
|
||||||
|
let attach = Attach {
|
||||||
|
name: attach.name.clone(),
|
||||||
|
handle: token as Handle,
|
||||||
|
role: Role::Sender,
|
||||||
|
snd_settle_mode: SenderSettleMode::Mixed,
|
||||||
|
rcv_settle_mode: ReceiverSettleMode::First,
|
||||||
|
source: attach.source.clone(),
|
||||||
|
target: attach.target.clone(),
|
||||||
|
unsettled: None,
|
||||||
|
incomplete_unsettled: false,
|
||||||
|
initial_delivery_count: Some(delivery_count),
|
||||||
|
max_message_size: Some(65536),
|
||||||
|
offered_capabilities: None,
|
||||||
|
desired_capabilities: None,
|
||||||
|
properties: None,
|
||||||
|
};
|
||||||
|
self.post_frame(attach.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register receiver link
|
||||||
|
pub(crate) fn open_receiver_link(
|
||||||
|
&mut self,
|
||||||
|
cell: Cell<SessionInner>,
|
||||||
|
attach: Attach,
|
||||||
|
) -> ReceiverLink {
|
||||||
|
let handle = attach.handle();
|
||||||
|
let entry = self.links.vacant_entry();
|
||||||
|
let token = entry.key();
|
||||||
|
|
||||||
|
let inner = Cell::new(ReceiverLinkInner::new(cell, token as u32, attach));
|
||||||
|
entry.insert(Either::Right(ReceiverLinkState::Opening(Some(
|
||||||
|
inner.clone(),
|
||||||
|
))));
|
||||||
|
self.remote_handles.insert(handle, token);
|
||||||
|
ReceiverLink::new(inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn open_local_receiver_link(
|
||||||
|
&mut self,
|
||||||
|
cell: Cell<SessionInner>,
|
||||||
|
mut frame: Attach,
|
||||||
|
) -> oneshot::Receiver<Result<ReceiverLink, AmqpTransportError>> {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|
||||||
|
let entry = self.links.vacant_entry();
|
||||||
|
let token = entry.key();
|
||||||
|
|
||||||
|
let inner = Cell::new(ReceiverLinkInner::new(cell, token as u32, frame.clone()));
|
||||||
|
entry.insert(Either::Right(ReceiverLinkState::OpeningLocal(Some((
|
||||||
|
inner.clone(),
|
||||||
|
tx,
|
||||||
|
)))));
|
||||||
|
|
||||||
|
frame.handle = token as Handle;
|
||||||
|
|
||||||
|
self.links_by_name.insert(frame.name.clone(), token);
|
||||||
|
self.post_frame(Frame::Attach(frame));
|
||||||
|
rx
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn confirm_receiver_link(&mut self, token: Handle, attach: &Attach) {
|
||||||
|
if let Some(Either::Right(link)) = self.links.get_mut(token as usize) {
|
||||||
|
match link {
|
||||||
|
ReceiverLinkState::Opening(l) => {
|
||||||
|
let attach = Attach {
|
||||||
|
name: attach.name.clone(),
|
||||||
|
handle: token as Handle,
|
||||||
|
role: Role::Receiver,
|
||||||
|
snd_settle_mode: SenderSettleMode::Mixed,
|
||||||
|
rcv_settle_mode: ReceiverSettleMode::First,
|
||||||
|
source: attach.source.clone(),
|
||||||
|
target: attach.target.clone(),
|
||||||
|
unsettled: None,
|
||||||
|
incomplete_unsettled: false,
|
||||||
|
initial_delivery_count: Some(0),
|
||||||
|
max_message_size: Some(65536),
|
||||||
|
offered_capabilities: None,
|
||||||
|
desired_capabilities: None,
|
||||||
|
properties: None,
|
||||||
|
};
|
||||||
|
*link = ReceiverLinkState::Established(ReceiverLink::new(l.take().unwrap()));
|
||||||
|
self.post_frame(attach.into());
|
||||||
|
}
|
||||||
|
_ => error!("Unexpected receiver link state"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Close receiver link
|
||||||
|
pub(crate) fn detach_receiver_link(
|
||||||
|
&mut self,
|
||||||
|
id: Handle,
|
||||||
|
closed: bool,
|
||||||
|
error: Option<Error>,
|
||||||
|
tx: oneshot::Sender<Result<(), AmqpTransportError>>,
|
||||||
|
) {
|
||||||
|
if let Some(Either::Right(link)) = self.links.get_mut(id as usize) {
|
||||||
|
match link {
|
||||||
|
ReceiverLinkState::Opening(inner) => {
|
||||||
|
let attach = Attach {
|
||||||
|
name: inner.as_ref().unwrap().get_ref().name().clone(),
|
||||||
|
handle: id as Handle,
|
||||||
|
role: Role::Sender,
|
||||||
|
snd_settle_mode: SenderSettleMode::Mixed,
|
||||||
|
rcv_settle_mode: ReceiverSettleMode::First,
|
||||||
|
source: None,
|
||||||
|
target: None,
|
||||||
|
unsettled: None,
|
||||||
|
incomplete_unsettled: false,
|
||||||
|
initial_delivery_count: None,
|
||||||
|
max_message_size: None,
|
||||||
|
offered_capabilities: None,
|
||||||
|
desired_capabilities: None,
|
||||||
|
properties: None,
|
||||||
|
};
|
||||||
|
let detach = Detach {
|
||||||
|
handle: id,
|
||||||
|
closed,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
*link = ReceiverLinkState::Closing(Some(tx));
|
||||||
|
self.post_frame(attach.into());
|
||||||
|
self.post_frame(detach.into());
|
||||||
|
}
|
||||||
|
ReceiverLinkState::Established(_) => {
|
||||||
|
let detach = Detach {
|
||||||
|
handle: id,
|
||||||
|
closed,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
*link = ReceiverLinkState::Closing(Some(tx));
|
||||||
|
self.post_frame(detach.into());
|
||||||
|
}
|
||||||
|
ReceiverLinkState::Closing(_) => {
|
||||||
|
let _ = tx.send(Ok(()));
|
||||||
|
error!("Unexpected receiver link state: closing - {}", id);
|
||||||
|
}
|
||||||
|
ReceiverLinkState::OpeningLocal(_inner) => unimplemented!(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let _ = tx.send(Ok(()));
|
||||||
|
error!("Receiver link does not exist while detaching: {}", id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn detach_sender_link(
|
||||||
|
&mut self,
|
||||||
|
id: usize,
|
||||||
|
closed: bool,
|
||||||
|
error: Option<Error>,
|
||||||
|
tx: oneshot::Sender<Result<(), AmqpTransportError>>,
|
||||||
|
) {
|
||||||
|
if let Some(Either::Left(link)) = self.links.get_mut(id) {
|
||||||
|
match link {
|
||||||
|
SenderLinkState::Opening(_) => {
|
||||||
|
let detach = Detach {
|
||||||
|
handle: id as u32,
|
||||||
|
closed,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
*link = SenderLinkState::Closing(Some(tx));
|
||||||
|
self.post_frame(detach.into());
|
||||||
|
}
|
||||||
|
SenderLinkState::Established(_) => {
|
||||||
|
let detach = Detach {
|
||||||
|
handle: id as u32,
|
||||||
|
closed,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
*link = SenderLinkState::Closing(Some(tx));
|
||||||
|
self.post_frame(detach.into());
|
||||||
|
}
|
||||||
|
SenderLinkState::Closing(_) => {
|
||||||
|
let _ = tx.send(Ok(()));
|
||||||
|
error!("Unexpected receiver link state: closing - {}", id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let _ = tx.send(Ok(()));
|
||||||
|
error!("Receiver link does not exist while detaching: {}", id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_sender_link_by_handle(&self, hnd: Handle) -> Option<&SenderLink> {
|
||||||
|
if let Some(id) = self.remote_handles.get(&hnd) {
|
||||||
|
if let Some(Either::Left(SenderLinkState::Established(ref link))) = self.links.get(*id)
|
||||||
|
{
|
||||||
|
return Some(link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_receiver_link_by_handle(&self, hnd: Handle) -> Option<&ReceiverLink> {
|
||||||
|
if let Some(id) = self.remote_handles.get(&hnd) {
|
||||||
|
if let Some(Either::Right(ReceiverLinkState::Established(ref link))) =
|
||||||
|
self.links.get(*id)
|
||||||
|
{
|
||||||
|
return Some(link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_frame(&mut self, frame: Frame) {
|
||||||
|
if self.error.is_none() {
|
||||||
|
match frame {
|
||||||
|
Frame::Flow(flow) => self.apply_flow(&flow),
|
||||||
|
Frame::Disposition(disp) => {
|
||||||
|
if let Some(sender) = self.disposition_subscribers.remove(&disp.first) {
|
||||||
|
let _ = sender.send(disp);
|
||||||
|
} else {
|
||||||
|
self.settle_deliveries(disp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Frame::Transfer(transfer) => {
|
||||||
|
let idx = if let Some(idx) = self.remote_handles.get(&transfer.handle()) {
|
||||||
|
*idx
|
||||||
|
} else {
|
||||||
|
error!("Transfer's link {:?} is unknown", transfer.handle());
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(link) = self.links.get_mut(idx) {
|
||||||
|
match link {
|
||||||
|
Either::Left(_) => error!("Got trasfer from sender link"),
|
||||||
|
Either::Right(link) => match link {
|
||||||
|
ReceiverLinkState::Opening(_) => {
|
||||||
|
error!(
|
||||||
|
"Got transfer for opening link: {} -> {}",
|
||||||
|
transfer.handle(),
|
||||||
|
idx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ReceiverLinkState::OpeningLocal(_) => {
|
||||||
|
error!(
|
||||||
|
"Got transfer for opening link: {} -> {}",
|
||||||
|
transfer.handle(),
|
||||||
|
idx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ReceiverLinkState::Established(link) => {
|
||||||
|
// self.outgoing_window -= 1;
|
||||||
|
let _ = self.next_incoming_id.wrapping_add(1);
|
||||||
|
link.inner.get_mut().handle_transfer(transfer);
|
||||||
|
}
|
||||||
|
ReceiverLinkState::Closing(_) => (),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!(
|
||||||
|
"Remote link handle mapped to non-existing link: {} -> {}",
|
||||||
|
transfer.handle(),
|
||||||
|
idx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Frame::Detach(detach) => {
|
||||||
|
self.handle_detach(&detach);
|
||||||
|
}
|
||||||
|
frame => error!("Unexpected frame: {:?}", frame),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle `Attach` frame. return false if attach frame is remote and can not be handled
|
||||||
|
pub fn handle_attach(&mut self, attach: &Attach, cell: Cell<SessionInner>) -> bool {
|
||||||
|
let name = attach.name();
|
||||||
|
|
||||||
|
if let Some(index) = self.links_by_name.get(name) {
|
||||||
|
match self.links.get_mut(*index) {
|
||||||
|
Some(Either::Left(item)) => {
|
||||||
|
if item.is_opening() {
|
||||||
|
trace!(
|
||||||
|
"sender link opened: {:?} {} -> {}",
|
||||||
|
name,
|
||||||
|
index,
|
||||||
|
attach.handle()
|
||||||
|
);
|
||||||
|
|
||||||
|
self.remote_handles.insert(attach.handle(), *index);
|
||||||
|
let delivery_count = attach.initial_delivery_count.unwrap_or(0);
|
||||||
|
let link = Cell::new(SenderLinkInner::new(
|
||||||
|
*index,
|
||||||
|
name.clone(),
|
||||||
|
attach.handle(),
|
||||||
|
delivery_count,
|
||||||
|
cell,
|
||||||
|
));
|
||||||
|
let local_sender = std::mem::replace(
|
||||||
|
item,
|
||||||
|
SenderLinkState::Established(SenderLink::new(link.clone())),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let SenderLinkState::Opening(tx) = local_sender {
|
||||||
|
let _ = tx.send(SenderLink::new(link));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Either::Right(item)) => {
|
||||||
|
if item.is_opening() {
|
||||||
|
trace!(
|
||||||
|
"receiver link opened: {:?} {} -> {}",
|
||||||
|
name,
|
||||||
|
index,
|
||||||
|
attach.handle()
|
||||||
|
);
|
||||||
|
if let ReceiverLinkState::OpeningLocal(opt_item) = item {
|
||||||
|
let (link, tx) = opt_item.take().unwrap();
|
||||||
|
self.remote_handles.insert(attach.handle(), *index);
|
||||||
|
|
||||||
|
*item = ReceiverLinkState::Established(ReceiverLink::new(link.clone()));
|
||||||
|
let _ = tx.send(Ok(ReceiverLink::new(link)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// TODO: error in proto, have to close connection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
// cannot handle remote attach
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle `Detach` frame.
|
||||||
|
pub fn handle_detach(&mut self, detach: &Detach) {
|
||||||
|
// get local link instance
|
||||||
|
let idx = if let Some(idx) = self.remote_handles.get(&detach.handle()) {
|
||||||
|
*idx
|
||||||
|
} else {
|
||||||
|
// should not happen, error
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let remove = if let Some(link) = self.links.get_mut(idx) {
|
||||||
|
match link {
|
||||||
|
Either::Left(link) => match link {
|
||||||
|
SenderLinkState::Opening(_) => true,
|
||||||
|
SenderLinkState::Established(link) => {
|
||||||
|
// detach from remote endpoint
|
||||||
|
let detach = Detach {
|
||||||
|
handle: link.inner.get_ref().id(),
|
||||||
|
closed: true,
|
||||||
|
error: detach.error.clone(),
|
||||||
|
};
|
||||||
|
let err = AmqpTransportError::LinkDetached(detach.error.clone());
|
||||||
|
|
||||||
|
// remove name
|
||||||
|
self.links_by_name.remove(link.inner.name());
|
||||||
|
|
||||||
|
// drop pending transfers
|
||||||
|
let mut idx = 0;
|
||||||
|
let handle = link.inner.get_ref().remote_handle();
|
||||||
|
while idx < self.pending_transfers.len() {
|
||||||
|
if self.pending_transfers[idx].link_handle == handle {
|
||||||
|
let tr = self.pending_transfers.remove(idx).unwrap();
|
||||||
|
let _ = tr.promise.send(Err(err.clone()));
|
||||||
|
} else {
|
||||||
|
idx += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// detach snd link
|
||||||
|
link.inner.get_mut().detached(err);
|
||||||
|
self.connection
|
||||||
|
.post_frame(AmqpFrame::new(self.remote_channel_id, detach.into()));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
SenderLinkState::Closing(_) => true,
|
||||||
|
},
|
||||||
|
Either::Right(link) => match link {
|
||||||
|
ReceiverLinkState::Opening(_) => false,
|
||||||
|
ReceiverLinkState::OpeningLocal(_) => false,
|
||||||
|
ReceiverLinkState::Established(link) => {
|
||||||
|
// detach from remote endpoint
|
||||||
|
let detach = Detach {
|
||||||
|
handle: link.handle(),
|
||||||
|
closed: true,
|
||||||
|
error: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// detach rcv link
|
||||||
|
self.connection
|
||||||
|
.post_frame(AmqpFrame::new(self.remote_channel_id, detach.into()));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
ReceiverLinkState::Closing(tx) => {
|
||||||
|
// detach confirmation
|
||||||
|
if let Some(tx) = tx.take() {
|
||||||
|
if let Some(err) = detach.error.clone() {
|
||||||
|
let _ = tx.send(Err(AmqpTransportError::LinkDetached(Some(err))));
|
||||||
|
} else {
|
||||||
|
let _ = tx.send(Ok(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
if remove {
|
||||||
|
self.links.remove(idx);
|
||||||
|
self.remote_handles.remove(&detach.handle());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn settle_deliveries(&mut self, disposition: Disposition) {
|
||||||
|
trace!("settle delivery: {:#?}", disposition);
|
||||||
|
|
||||||
|
let from = disposition.first;
|
||||||
|
let to = disposition.last.unwrap_or(from);
|
||||||
|
|
||||||
|
if from == to {
|
||||||
|
let _ = self
|
||||||
|
.unsettled_deliveries
|
||||||
|
.remove(&from)
|
||||||
|
.unwrap()
|
||||||
|
.send(Ok(disposition));
|
||||||
|
} else {
|
||||||
|
for k in from..=to {
|
||||||
|
let _ = self
|
||||||
|
.unsettled_deliveries
|
||||||
|
.remove(&k)
|
||||||
|
.unwrap()
|
||||||
|
.send(Ok(disposition.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn apply_flow(&mut self, flow: &Flow) {
|
||||||
|
// # AMQP1.0 2.5.6
|
||||||
|
self.next_incoming_id = flow.next_outgoing_id();
|
||||||
|
self.remote_outgoing_window = flow.outgoing_window();
|
||||||
|
|
||||||
|
self.remote_incoming_window = flow
|
||||||
|
.next_incoming_id()
|
||||||
|
.unwrap_or(INITIAL_OUTGOING_ID)
|
||||||
|
.saturating_add(flow.incoming_window())
|
||||||
|
.saturating_sub(self.next_outgoing_id);
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"session received credit. window: {}, pending: {}",
|
||||||
|
self.remote_outgoing_window,
|
||||||
|
self.pending_transfers.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
while let Some(t) = self.pending_transfers.pop_front() {
|
||||||
|
self.send_transfer(t.link_handle, t.idx, t.body, t.promise, t.tag, t.settled);
|
||||||
|
if self.remote_outgoing_window == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply link flow
|
||||||
|
if let Some(Either::Left(link)) = flow.handle().and_then(|h| self.links.get_mut(h as usize))
|
||||||
|
{
|
||||||
|
match link {
|
||||||
|
SenderLinkState::Established(ref mut link) => {
|
||||||
|
link.inner.get_mut().apply_flow(&flow);
|
||||||
|
}
|
||||||
|
_ => warn!("Received flow frame"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if flow.echo() {
|
||||||
|
self.send_flow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_flow(&mut self) {
|
||||||
|
let flow = Flow {
|
||||||
|
next_incoming_id: if self.local {
|
||||||
|
Some(self.next_incoming_id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
incoming_window: std::u32::MAX,
|
||||||
|
next_outgoing_id: self.next_outgoing_id,
|
||||||
|
outgoing_window: self.remote_incoming_window,
|
||||||
|
handle: None,
|
||||||
|
delivery_count: None,
|
||||||
|
link_credit: None,
|
||||||
|
available: None,
|
||||||
|
drain: false,
|
||||||
|
echo: false,
|
||||||
|
properties: None,
|
||||||
|
};
|
||||||
|
self.post_frame(flow.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn rcv_link_flow(&mut self, handle: u32, delivery_count: u32, credit: u32) {
|
||||||
|
let flow = Flow {
|
||||||
|
next_incoming_id: if self.local {
|
||||||
|
Some(self.next_incoming_id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
incoming_window: std::u32::MAX,
|
||||||
|
next_outgoing_id: self.next_outgoing_id,
|
||||||
|
outgoing_window: self.remote_incoming_window,
|
||||||
|
handle: Some(handle),
|
||||||
|
delivery_count: Some(delivery_count),
|
||||||
|
link_credit: Some(credit),
|
||||||
|
available: None,
|
||||||
|
drain: false,
|
||||||
|
echo: false,
|
||||||
|
properties: None,
|
||||||
|
};
|
||||||
|
self.post_frame(flow.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn post_frame(&mut self, frame: Frame) {
|
||||||
|
self.connection
|
||||||
|
.post_frame(AmqpFrame::new(self.remote_channel_id, frame));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn open_sender_link(&mut self, mut frame: Attach) -> oneshot::Receiver<SenderLink> {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|
||||||
|
let entry = self.links.vacant_entry();
|
||||||
|
let token = entry.key();
|
||||||
|
entry.insert(Either::Left(SenderLinkState::Opening(tx)));
|
||||||
|
|
||||||
|
frame.handle = token as Handle;
|
||||||
|
|
||||||
|
self.links_by_name.insert(frame.name.clone(), token);
|
||||||
|
self.post_frame(Frame::Attach(frame));
|
||||||
|
rx
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_transfer(
|
||||||
|
&mut self,
|
||||||
|
link_handle: Handle,
|
||||||
|
idx: u32,
|
||||||
|
body: Option<TransferBody>,
|
||||||
|
promise: DeliveryPromise,
|
||||||
|
tag: Option<Bytes>,
|
||||||
|
settled: Option<bool>,
|
||||||
|
) {
|
||||||
|
if self.remote_incoming_window == 0 {
|
||||||
|
self.pending_transfers.push_back(PendingTransfer {
|
||||||
|
link_handle,
|
||||||
|
idx,
|
||||||
|
body,
|
||||||
|
promise,
|
||||||
|
tag,
|
||||||
|
settled,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let frame = self.prepare_transfer(link_handle, body, promise, tag, settled);
|
||||||
|
self.post_frame(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prepare_transfer(
|
||||||
|
&mut self,
|
||||||
|
link_handle: Handle,
|
||||||
|
body: Option<TransferBody>,
|
||||||
|
promise: DeliveryPromise,
|
||||||
|
delivery_tag: Option<Bytes>,
|
||||||
|
settled: Option<bool>,
|
||||||
|
) -> Frame {
|
||||||
|
let delivery_id = self.next_outgoing_id;
|
||||||
|
|
||||||
|
let tag = if let Some(tag) = delivery_tag {
|
||||||
|
tag
|
||||||
|
} else {
|
||||||
|
let mut buf = BytesMut::new();
|
||||||
|
buf.put_u32(delivery_id);
|
||||||
|
buf.freeze()
|
||||||
|
};
|
||||||
|
|
||||||
|
self.next_outgoing_id += 1;
|
||||||
|
self.remote_incoming_window -= 1;
|
||||||
|
|
||||||
|
let message_format = if let Some(ref body) = body {
|
||||||
|
body.message_format()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let settled2 = settled.clone().unwrap_or(false);
|
||||||
|
let state = if settled2 {
|
||||||
|
Some(DeliveryState::Accepted(Accepted {}))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let transfer = Transfer {
|
||||||
|
settled,
|
||||||
|
message_format,
|
||||||
|
handle: link_handle,
|
||||||
|
delivery_id: Some(delivery_id),
|
||||||
|
delivery_tag: Some(tag),
|
||||||
|
more: false,
|
||||||
|
rcv_settle_mode: None,
|
||||||
|
state, //: Some(DeliveryState::Accepted(Accepted {})),
|
||||||
|
resume: false,
|
||||||
|
aborted: false,
|
||||||
|
batchable: false,
|
||||||
|
body: body,
|
||||||
|
};
|
||||||
|
self.unsettled_deliveries.insert(delivery_id, promise);
|
||||||
|
|
||||||
|
Frame::Transfer(transfer)
|
||||||
|
}
|
||||||
|
}
|
326
actix-amqp/src/sndlink.rs
Executable file
326
actix-amqp/src/sndlink.rs
Executable file
@ -0,0 +1,326 @@
|
|||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::future::Future;
|
||||||
|
|
||||||
|
use actix_utils::oneshot;
|
||||||
|
use amqp_codec::protocol::{
|
||||||
|
Attach, DeliveryNumber, DeliveryState, Disposition, Error, Flow, ReceiverSettleMode, Role,
|
||||||
|
SenderSettleMode, SequenceNo, Target, TerminusDurability, TerminusExpiryPolicy, TransferBody,
|
||||||
|
};
|
||||||
|
use bytes::Bytes;
|
||||||
|
use bytestring::ByteString;
|
||||||
|
use futures::future::{ok, Either};
|
||||||
|
|
||||||
|
use crate::cell::Cell;
|
||||||
|
use crate::errors::AmqpTransportError;
|
||||||
|
use crate::session::{Session, SessionInner};
|
||||||
|
use crate::{Delivery, DeliveryPromise, Handle};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SenderLink {
|
||||||
|
pub(crate) inner: Cell<SenderLinkInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for SenderLink {
|
||||||
|
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
fmt.debug_tuple("SenderLink")
|
||||||
|
.field(&std::ops::Deref::deref(&self.inner.get_ref().name))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct SenderLinkInner {
|
||||||
|
id: usize,
|
||||||
|
idx: u32,
|
||||||
|
name: ByteString,
|
||||||
|
session: Session,
|
||||||
|
remote_handle: Handle,
|
||||||
|
delivery_count: SequenceNo,
|
||||||
|
link_credit: u32,
|
||||||
|
pending_transfers: VecDeque<PendingTransfer>,
|
||||||
|
error: Option<AmqpTransportError>,
|
||||||
|
closed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PendingTransfer {
|
||||||
|
idx: u32,
|
||||||
|
tag: Option<Bytes>,
|
||||||
|
body: Option<TransferBody>,
|
||||||
|
promise: DeliveryPromise,
|
||||||
|
settle: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SenderLink {
|
||||||
|
pub(crate) fn new(inner: Cell<SenderLinkInner>) -> SenderLink {
|
||||||
|
SenderLink { inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> u32 {
|
||||||
|
self.inner.id as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &ByteString {
|
||||||
|
&self.inner.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remote_handle(&self) -> Handle {
|
||||||
|
self.inner.remote_handle
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn session(&self) -> &Session {
|
||||||
|
&self.inner.get_ref().session
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn session_mut(&mut self) -> &mut Session {
|
||||||
|
&mut self.inner.get_mut().session
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send<T>(&self, body: T) -> impl Future<Output = Result<Disposition, AmqpTransportError>>
|
||||||
|
where
|
||||||
|
T: Into<TransferBody>,
|
||||||
|
{
|
||||||
|
self.inner.get_mut().send(body, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_with_tag<T>(
|
||||||
|
&self,
|
||||||
|
body: T,
|
||||||
|
tag: Bytes,
|
||||||
|
) -> impl Future<Output = Result<Disposition, AmqpTransportError>>
|
||||||
|
where
|
||||||
|
T: Into<TransferBody>,
|
||||||
|
{
|
||||||
|
self.inner.get_mut().send(body, Some(tag))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn settle_message(&self, id: DeliveryNumber, state: DeliveryState) {
|
||||||
|
self.inner.get_mut().settle_message(id, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close(&self) -> impl Future<Output = Result<(), AmqpTransportError>> {
|
||||||
|
self.inner.get_mut().close(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close_with_error(
|
||||||
|
&self,
|
||||||
|
error: Error,
|
||||||
|
) -> impl Future<Output = Result<(), AmqpTransportError>> {
|
||||||
|
self.inner.get_mut().close(Some(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SenderLinkInner {
|
||||||
|
pub(crate) fn new(
|
||||||
|
id: usize,
|
||||||
|
name: ByteString,
|
||||||
|
handle: Handle,
|
||||||
|
delivery_count: SequenceNo,
|
||||||
|
session: Cell<SessionInner>,
|
||||||
|
) -> SenderLinkInner {
|
||||||
|
SenderLinkInner {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
delivery_count,
|
||||||
|
idx: 0,
|
||||||
|
session: Session::new(session),
|
||||||
|
remote_handle: handle,
|
||||||
|
link_credit: 0,
|
||||||
|
pending_transfers: VecDeque::new(),
|
||||||
|
error: None,
|
||||||
|
closed: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> u32 {
|
||||||
|
self.id as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remote_handle(&self) -> Handle {
|
||||||
|
self.remote_handle
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn name(&self) -> &ByteString {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn detached(&mut self, err: AmqpTransportError) {
|
||||||
|
// drop pending transfers
|
||||||
|
for tr in self.pending_transfers.drain(..) {
|
||||||
|
let _ = tr.promise.send(Err(err.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.error = Some(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn close(
|
||||||
|
&self,
|
||||||
|
error: Option<Error>,
|
||||||
|
) -> impl Future<Output = Result<(), AmqpTransportError>> {
|
||||||
|
if self.closed {
|
||||||
|
Either::Left(ok(()))
|
||||||
|
} else {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|
||||||
|
self.session
|
||||||
|
.inner
|
||||||
|
.get_mut()
|
||||||
|
.detach_sender_link(self.id, true, error, tx);
|
||||||
|
|
||||||
|
Either::Right(async move {
|
||||||
|
match rx.await {
|
||||||
|
Ok(Ok(_)) => Ok(()),
|
||||||
|
Ok(Err(e)) => Err(e),
|
||||||
|
Err(_) => Err(AmqpTransportError::Disconnected),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_error(&mut self, err: AmqpTransportError) {
|
||||||
|
// drop pending transfers
|
||||||
|
for tr in self.pending_transfers.drain(..) {
|
||||||
|
let _ = tr.promise.send(Err(err.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.error = Some(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_flow(&mut self, flow: &Flow) {
|
||||||
|
// #2.7.6
|
||||||
|
if let Some(credit) = flow.link_credit() {
|
||||||
|
let delta = flow
|
||||||
|
.delivery_count
|
||||||
|
.unwrap_or(0)
|
||||||
|
.saturating_add(credit)
|
||||||
|
.saturating_sub(self.delivery_count);
|
||||||
|
|
||||||
|
let session = self.session.inner.get_mut();
|
||||||
|
|
||||||
|
// credit became available => drain pending_transfers
|
||||||
|
self.link_credit += delta;
|
||||||
|
while self.link_credit > 0 {
|
||||||
|
if let Some(transfer) = self.pending_transfers.pop_front() {
|
||||||
|
self.link_credit -= 1;
|
||||||
|
let _ = self.delivery_count.saturating_add(1);
|
||||||
|
session.send_transfer(
|
||||||
|
self.remote_handle,
|
||||||
|
transfer.idx,
|
||||||
|
transfer.body,
|
||||||
|
transfer.promise,
|
||||||
|
transfer.tag,
|
||||||
|
transfer.settle,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if flow.echo() {
|
||||||
|
// todo: send flow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send<T: Into<TransferBody>>(&mut self, body: T, tag: Option<Bytes>) -> Delivery {
|
||||||
|
if let Some(ref err) = self.error {
|
||||||
|
Delivery::Resolved(Err(err.clone()))
|
||||||
|
} else {
|
||||||
|
let body = body.into();
|
||||||
|
let (delivery_tx, delivery_rx) = oneshot::channel();
|
||||||
|
if self.link_credit == 0 {
|
||||||
|
self.pending_transfers.push_back(PendingTransfer {
|
||||||
|
tag,
|
||||||
|
settle: Some(false),
|
||||||
|
body: Some(body),
|
||||||
|
idx: self.idx,
|
||||||
|
promise: delivery_tx,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let session = self.session.inner.get_mut();
|
||||||
|
self.link_credit -= 1;
|
||||||
|
let _ = self.delivery_count.saturating_add(1);
|
||||||
|
session.send_transfer(
|
||||||
|
self.remote_handle,
|
||||||
|
self.idx,
|
||||||
|
Some(body),
|
||||||
|
delivery_tx,
|
||||||
|
tag,
|
||||||
|
Some(false),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let _ = self.idx.saturating_add(1);
|
||||||
|
Delivery::Pending(delivery_rx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn settle_message(&mut self, id: DeliveryNumber, state: DeliveryState) {
|
||||||
|
let _ = self.delivery_count.saturating_add(1);
|
||||||
|
|
||||||
|
let disp = Disposition {
|
||||||
|
role: Role::Sender,
|
||||||
|
first: id,
|
||||||
|
last: None,
|
||||||
|
settled: true,
|
||||||
|
state: Some(state),
|
||||||
|
batchable: false,
|
||||||
|
};
|
||||||
|
self.session.inner.get_mut().post_frame(disp.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SenderLinkBuilder {
|
||||||
|
frame: Attach,
|
||||||
|
session: Cell<SessionInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SenderLinkBuilder {
|
||||||
|
pub(crate) fn new(name: ByteString, address: ByteString, session: Cell<SessionInner>) -> Self {
|
||||||
|
let target = Target {
|
||||||
|
address: Some(address),
|
||||||
|
durable: TerminusDurability::None,
|
||||||
|
expiry_policy: TerminusExpiryPolicy::SessionEnd,
|
||||||
|
timeout: 0,
|
||||||
|
dynamic: false,
|
||||||
|
dynamic_node_properties: None,
|
||||||
|
capabilities: None,
|
||||||
|
};
|
||||||
|
let frame = Attach {
|
||||||
|
name,
|
||||||
|
handle: 0 as Handle,
|
||||||
|
role: Role::Sender,
|
||||||
|
snd_settle_mode: SenderSettleMode::Mixed,
|
||||||
|
rcv_settle_mode: ReceiverSettleMode::First,
|
||||||
|
source: None,
|
||||||
|
target: Some(target),
|
||||||
|
unsettled: None,
|
||||||
|
incomplete_unsettled: false,
|
||||||
|
initial_delivery_count: None,
|
||||||
|
max_message_size: Some(65536 * 4),
|
||||||
|
offered_capabilities: None,
|
||||||
|
desired_capabilities: None,
|
||||||
|
properties: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
SenderLinkBuilder { frame, session }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn max_message_size(mut self, size: u64) -> Self {
|
||||||
|
self.frame.max_message_size = Some(size);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_frame<F>(mut self, f: F) -> Self
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Attach),
|
||||||
|
{
|
||||||
|
f(&mut self.frame);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn open(self) -> Result<SenderLink, AmqpTransportError> {
|
||||||
|
self.session
|
||||||
|
.get_mut()
|
||||||
|
.open_sender_link(self.frame)
|
||||||
|
.await
|
||||||
|
.map_err(|_e| AmqpTransportError::Disconnected)
|
||||||
|
}
|
||||||
|
}
|
131
actix-amqp/tests/test_server.rs
Executable file
131
actix-amqp/tests/test_server.rs
Executable file
@ -0,0 +1,131 @@
|
|||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
use actix_amqp::server::{self, errors};
|
||||||
|
use actix_amqp::{sasl, Configuration};
|
||||||
|
use actix_codec::{AsyncRead, AsyncWrite};
|
||||||
|
use actix_connect::{default_connector, TcpConnector};
|
||||||
|
use actix_service::{fn_factory_with_config, pipeline_factory, Service};
|
||||||
|
use actix_testing::TestServer;
|
||||||
|
use futures::future::{err, Ready};
|
||||||
|
use futures::Future;
|
||||||
|
use http::Uri;
|
||||||
|
|
||||||
|
fn server(
|
||||||
|
link: server::Link<()>,
|
||||||
|
) -> impl Future<
|
||||||
|
Output = Result<
|
||||||
|
Box<
|
||||||
|
dyn Service<
|
||||||
|
Request = server::Message<()>,
|
||||||
|
Response = server::Outcome,
|
||||||
|
Error = errors::AmqpError,
|
||||||
|
Future = Ready<Result<server::Message<()>, server::Outcome>>,
|
||||||
|
> + 'static,
|
||||||
|
>,
|
||||||
|
errors::LinkError,
|
||||||
|
>,
|
||||||
|
> {
|
||||||
|
println!("OPEN LINK: {:?}", link);
|
||||||
|
err(errors::LinkError::force_detach().description("unimplemented"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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,amqp_transport=trace",
|
||||||
|
);
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
let srv = TestServer::with(|| {
|
||||||
|
server::Server::new(
|
||||||
|
server::Handshake::new(|conn: server::Connect<_>| async move {
|
||||||
|
let conn = conn.open().await.unwrap();
|
||||||
|
Ok::<_, errors::AmqpError>(conn.ack(()))
|
||||||
|
})
|
||||||
|
.sasl(server::sasl::no_sasl()),
|
||||||
|
)
|
||||||
|
.finish(
|
||||||
|
server::App::<()>::new()
|
||||||
|
.service("test", fn_factory_with_config(server))
|
||||||
|
.finish(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let uri = Uri::try_from(format!("amqp://{}:{}", srv.host(), srv.port())).unwrap();
|
||||||
|
let mut sasl_srv = sasl::connect_service(default_connector());
|
||||||
|
let req = sasl::SaslConnect {
|
||||||
|
uri,
|
||||||
|
config: Configuration::default(),
|
||||||
|
time: None,
|
||||||
|
auth: sasl::SaslAuth {
|
||||||
|
authz_id: "".to_string(),
|
||||||
|
authn_id: "user1".to_string(),
|
||||||
|
password: "password1".to_string(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let res = sasl_srv.call(req).await;
|
||||||
|
println!("E: {:?}", res.err());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sasl_auth<Io: AsyncRead + AsyncWrite>(
|
||||||
|
auth: server::Sasl<Io>,
|
||||||
|
) -> Result<server::ConnectAck<Io, ()>, server::errors::ServerError<()>> {
|
||||||
|
let init = auth
|
||||||
|
.mechanism("PLAIN")
|
||||||
|
.mechanism("ANONYMOUS")
|
||||||
|
.mechanism("MSSBCBS")
|
||||||
|
.mechanism("AMQPCBS")
|
||||||
|
.init()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if init.mechanism() == "PLAIN" {
|
||||||
|
if let Some(resp) = init.initial_response() {
|
||||||
|
if resp == b"\0user1\0password1" {
|
||||||
|
let succ = init.outcome(amqp_codec::protocol::SaslCode::Ok).await?;
|
||||||
|
return Ok(succ.open().await?.ack(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let succ = init.outcome(amqp_codec::protocol::SaslCode::Auth).await?;
|
||||||
|
Ok(succ.open().await?.ack(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_sasl() -> std::io::Result<()> {
|
||||||
|
let srv = TestServer::with(|| {
|
||||||
|
server::Server::new(
|
||||||
|
server::Handshake::new(|conn: server::Connect<_>| async move {
|
||||||
|
let conn = conn.open().await.unwrap();
|
||||||
|
Ok::<_, errors::Error>(conn.ack(()))
|
||||||
|
})
|
||||||
|
.sasl(pipeline_factory(sasl_auth).map_err(|e| e.into())),
|
||||||
|
)
|
||||||
|
.finish(
|
||||||
|
server::App::<()>::new()
|
||||||
|
.service("test", fn_factory_with_config(server))
|
||||||
|
.finish(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let uri = Uri::try_from(format!("amqp://{}:{}", srv.host(), srv.port())).unwrap();
|
||||||
|
let mut sasl_srv = sasl::connect_service(TcpConnector::new());
|
||||||
|
|
||||||
|
let req = sasl::SaslConnect {
|
||||||
|
uri,
|
||||||
|
config: Configuration::default(),
|
||||||
|
time: None,
|
||||||
|
auth: sasl::SaslAuth {
|
||||||
|
authz_id: "".to_string(),
|
||||||
|
authn_id: "user1".to_string(),
|
||||||
|
password: "password1".to_string(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let res = sasl_srv.call(req).await;
|
||||||
|
println!("E: {:?}", res.err());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
24
actix-mqtt/CHANGES.md
Executable file
24
actix-mqtt/CHANGES.md
Executable 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
38
actix-mqtt/Cargo.toml
Executable 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
201
actix-mqtt/LICENSE-APACHE
Executable 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
25
actix-mqtt/LICENSE-MIT
Executable 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
1
actix-mqtt/README.md
Executable 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
17
actix-mqtt/codec/CHANGES.md
Executable 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
21
actix-mqtt/codec/Cargo.toml
Executable 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
201
actix-mqtt/codec/LICENSE-APACHE
Executable 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
25
actix-mqtt/codec/LICENSE-MIT
Executable 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
3
actix-mqtt/codec/README.md
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
# MQTT v3.1 Codec
|
||||||
|
|
||||||
|
MQTT v3.1 Codec implementation
|
616
actix-mqtt/codec/src/codec/decode.rs
Executable file
616
actix-mqtt/codec/src/codec/decode.rs
Executable 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);
|
||||||
|
}
|
||||||
|
}
|
446
actix-mqtt/codec/src/codec/encode.rs
Executable file
446
actix-mqtt/codec/src/codec/encode.rs
Executable 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
161
actix-mqtt/codec/src/codec/mod.rs
Executable 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
57
actix-mqtt/codec/src/error.rs
Executable 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
22
actix-mqtt/codec/src/lib.rs
Executable 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
251
actix-mqtt/codec/src/packet.rs
Executable 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
64
actix-mqtt/codec/src/proto.rs
Executable 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
520
actix-mqtt/codec/src/topic.rs
Executable 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
35
actix-mqtt/examples/basic.rs
Executable 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
64
actix-mqtt/src/cell.rs
Executable 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
406
actix-mqtt/src/client.rs
Executable 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
150
actix-mqtt/src/connect.rs
Executable 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
125
actix-mqtt/src/default.rs
Executable 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
286
actix-mqtt/src/dispatcher.rs
Executable 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
34
actix-mqtt/src/error.rs
Executable 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
23
actix-mqtt/src/lib.rs
Executable 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
137
actix-mqtt/src/publish.rs
Executable 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
206
actix-mqtt/src/router.rs
Executable 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
331
actix-mqtt/src/server.rs
Executable 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
107
actix-mqtt/src/sink.rs
Executable 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
191
actix-mqtt/src/subs.rs
Executable 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
52
actix-mqtt/tests/test_server.rs
Executable 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(())
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user