Merge remote-tracking branch 'origin/main' into main

# Conflicts:
#	xtask/src/test_js.rs
This commit is contained in:
nora 2022-02-28 07:33:09 +01:00
commit fc131327b2
38 changed files with 2037 additions and 1242 deletions

View file

@ -1,6 +1,2 @@
[alias] [alias]
xtask = "run --package xtask --" xtask = "run --package xtask --"
[build]
# note: if this doesn't apply, update your global rustflags in "~/.cargo/config.toml"
rustflags = ["--cfg", "tokio_unstable"]

8
Cargo.lock generated
View file

@ -30,7 +30,9 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bytes", "bytes",
"parking_lot", "parking_lot",
"rand",
"smallvec", "smallvec",
"thiserror",
"uuid", "uuid",
] ]
@ -50,6 +52,7 @@ name = "amqp_messaging"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"amqp_core", "amqp_core",
"parking_lot",
"tokio", "tokio",
"tracing", "tracing",
] ]
@ -71,7 +74,6 @@ dependencies = [
"thiserror", "thiserror",
"tokio", "tokio",
"tracing", "tracing",
"uuid",
] ]
[[package]] [[package]]
@ -85,9 +87,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.54" version = "1.0.55"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a99269dff3bc004caa411f38845c20303f1e393ca2bd6581576fa3a7f59577d" checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd"
[[package]] [[package]]
name = "async-trait" name = "async-trait"

View file

@ -8,5 +8,7 @@ edition = "2021"
[dependencies] [dependencies]
bytes = "1.1.0" bytes = "1.1.0"
parking_lot = "0.12.0" parking_lot = "0.12.0"
rand = "0.8.5"
smallvec = { version = "1.8.0", features = ["union"] } smallvec = { version = "1.8.0", features = ["union"] }
thiserror = "1.0.30"
uuid = "0.8.2" uuid = "0.8.2"

105
amqp_core/src/connection.rs Normal file
View file

@ -0,0 +1,105 @@
use crate::{newtype_id, GlobalData, Handle, Queue};
use parking_lot::Mutex;
use std::collections::HashMap;
use std::fmt::{Display, Formatter};
use std::net::SocketAddr;
use std::sync::Arc;
newtype_id!(pub ConnectionId);
newtype_id!(pub ChannelId);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ChannelNum(u16);
impl ChannelNum {
#[must_use]
pub fn new(num: u16) -> Self {
Self(num)
}
#[must_use]
pub fn num(self) -> u16 {
self.0
}
#[must_use]
pub fn is_zero(self) -> bool {
self.0 == 0
}
#[must_use]
pub fn zero() -> Self {
Self(0)
}
}
impl Display for ChannelNum {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
pub type ConnectionHandle = Handle<Connection>;
#[derive(Debug)]
pub struct Connection {
pub id: ConnectionId,
pub peer_addr: SocketAddr,
pub global_data: GlobalData,
pub channels: HashMap<u16, ChannelHandle>,
pub exclusive_queues: Vec<Queue>,
}
impl Connection {
#[must_use]
pub fn new_handle(
id: ConnectionId,
peer_addr: SocketAddr,
global_data: GlobalData,
) -> ConnectionHandle {
Arc::new(Mutex::new(Self {
id,
peer_addr,
global_data,
channels: HashMap::new(),
exclusive_queues: vec![],
}))
}
pub fn close(&self) {
let mut global_data = self.global_data.lock();
global_data.connections.remove(&self.id);
}
}
pub type ChannelHandle = Handle<Channel>;
#[derive(Debug)]
pub struct Channel {
pub id: ChannelId,
pub num: u16,
pub connection: ConnectionHandle,
pub global_data: GlobalData,
}
impl Channel {
#[must_use]
pub fn new_handle(
id: ChannelId,
num: u16,
connection: ConnectionHandle,
global_data: GlobalData,
) -> ChannelHandle {
Arc::new(Mutex::new(Self {
id,
num,
connection,
global_data,
}))
}
pub fn close(&self) {
let mut global_data = self.global_data.lock();
global_data.channels.remove(&self.id);
}
}

35
amqp_core/src/error.rs Normal file
View file

@ -0,0 +1,35 @@
#[derive(Debug, thiserror::Error)]
pub enum ProtocolError {
#[error("fatal error")]
Fatal,
#[error("{0}")]
ConException(#[from] ConException),
#[error("{0}")]
ChannelException(#[from] ChannelException),
#[error("Connection must be closed")]
CloseNow,
#[error("Graceful connection closing requested")]
GracefulClose,
}
#[derive(Debug, thiserror::Error)]
pub enum ConException {
#[error("501 Frame error")]
FrameError,
#[error("503 Command invalid")]
CommandInvalid,
#[error("503 Syntax error | {0:?}")]
/// A method was received but there was a syntax error. The string stores where it occurred.
SyntaxError(Vec<String>),
#[error("504 Channel error")]
ChannelError,
#[error("505 Unexpected Frame")]
UnexpectedFrame,
#[error("540 Not implemented. '{0}'")]
NotImplemented(&'static str),
#[error("xxx Not decided yet")]
Todo,
}
#[derive(Debug, thiserror::Error)]
pub enum ChannelException {}

View file

@ -1,13 +1,18 @@
#![warn(rust_2018_idioms)] #![warn(rust_2018_idioms)]
mod message; pub mod connection;
pub mod error;
mod macros;
pub mod message;
pub mod methods; pub mod methods;
pub mod queue;
use crate::connection::{ChannelHandle, ConnectionHandle};
use crate::queue::{Queue, QueueName};
use connection::{ChannelId, ConnectionId};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::collections::HashMap; use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc; use std::sync::Arc;
use uuid::Uuid;
type Handle<T> = Arc<Mutex<T>>; type Handle<T> = Arc<Mutex<T>>;
@ -22,6 +27,8 @@ impl Default for GlobalData {
inner: Arc::new(Mutex::new(GlobalDataInner { inner: Arc::new(Mutex::new(GlobalDataInner {
connections: HashMap::new(), connections: HashMap::new(),
channels: HashMap::new(), channels: HashMap::new(),
queues: HashMap::new(),
default_exchange: HashMap::new(),
})), })),
} }
} }
@ -35,67 +42,9 @@ impl GlobalData {
#[derive(Debug)] #[derive(Debug)]
pub struct GlobalDataInner { pub struct GlobalDataInner {
pub connections: HashMap<Uuid, ConnectionHandle>, pub connections: HashMap<ConnectionId, ConnectionHandle>,
pub channels: HashMap<Uuid, ChannelHandle>, pub channels: HashMap<ChannelId, ChannelHandle>,
} pub queues: HashMap<QueueName, Queue>,
/// Todo: This is just for testing and will be removed later!
pub type ConnectionHandle = Handle<Connection>; pub default_exchange: HashMap<String, Queue>,
#[derive(Debug)]
pub struct Connection {
pub id: Uuid,
pub peer_addr: SocketAddr,
pub global_data: GlobalData,
pub channels: HashMap<u16, ChannelHandle>,
}
impl Connection {
pub fn new_handle(
id: Uuid,
peer_addr: SocketAddr,
global_data: GlobalData,
) -> ConnectionHandle {
Arc::new(Mutex::new(Self {
id,
peer_addr,
global_data,
channels: HashMap::new(),
}))
}
pub fn close(&self) {
let mut global_data = self.global_data.lock();
global_data.connections.remove(&self.id);
}
}
pub type ChannelHandle = Handle<Channel>;
#[derive(Debug)]
pub struct Channel {
pub id: Uuid,
pub num: u16,
pub connection: ConnectionHandle,
pub global_data: GlobalData,
}
impl Channel {
pub fn new_handle(
id: Uuid,
num: u16,
connection: ConnectionHandle,
global_data: GlobalData,
) -> ChannelHandle {
Arc::new(Mutex::new(Self {
id,
num,
connection,
global_data,
}))
}
pub fn close(&self) {
let mut global_data = self.global_data.lock();
global_data.channels.remove(&self.id);
}
} }

70
amqp_core/src/macros.rs Normal file
View file

@ -0,0 +1,70 @@
#[macro_export]
macro_rules! newtype_id {
($vis:vis $name:ident) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
$vis struct $name(::uuid::Uuid);
impl $name {
#[must_use]
pub fn random() -> Self {
::rand::random()
}
}
impl ::std::fmt::Display for $name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
self.0.fmt(f)
}
}
impl ::rand::prelude::Distribution<$name> for ::rand::distributions::Standard {
fn sample<R: ::rand::Rng + ?Sized>(&self, rng: &mut R) -> $name {
$name(::uuid::Uuid::from_bytes(rng.gen()))
}
}
};
}
#[macro_export]
macro_rules! newtype {
($(#[$meta:meta])* $vis:vis $name:ident: $ty:ty) => {
$(#[$meta])*
$vis struct $name($ty);
impl $name {
pub fn new(inner: $ty) -> Self {
Self(inner)
}
pub fn into_inner(self) -> $ty {
self.0
}
}
impl std::ops::Deref for $name {
type Target = $ty;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> std::convert::From<T> for $name
where
$ty: From<T>,
{
fn from(other: T) -> Self {
Self(other.into())
}
}
};
}
#[macro_export]
macro_rules! amqp_todo {
() => {
return Err(
::amqp_core::error::ConException::NotImplemented(concat!(file!(), ":", line!())).into(),
)
};
}

View file

@ -1,20 +1,24 @@
#![allow(dead_code)] #![allow(dead_code)]
use crate::methods; use crate::methods;
use crate::newtype_id;
use bytes::Bytes; use bytes::Bytes;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::sync::Arc; use std::sync::Arc;
use uuid::Uuid;
pub type Message = Arc<RawMessage>; pub type Message = Arc<RawMessage>;
newtype_id!(pub MessageId);
#[derive(Debug)]
pub struct RawMessage { pub struct RawMessage {
id: Uuid, pub id: MessageId,
properties: methods::Table, pub properties: methods::Table,
routing: RoutingInformation, pub routing: RoutingInformation,
content: SmallVec<[Bytes; 1]>, pub content: SmallVec<[Bytes; 1]>,
} }
#[derive(Debug)]
pub struct RoutingInformation { pub struct RoutingInformation {
pub exchange: String, pub exchange: String,
pub routing_key: String, pub routing_key: String,

View file

@ -126,151 +126,236 @@ pub type Table = super::Table;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Method { pub enum Method {
ConnectionStart(ConnectionStart),
ConnectionStartOk(ConnectionStartOk),
ConnectionSecure(ConnectionSecure),
ConnectionSecureOk(ConnectionSecureOk),
ConnectionTune(ConnectionTune),
ConnectionTuneOk(ConnectionTuneOk),
ConnectionOpen(ConnectionOpen),
ConnectionOpenOk(ConnectionOpenOk),
ConnectionClose(ConnectionClose),
ConnectionCloseOk(ConnectionCloseOk),
ChannelOpen(ChannelOpen),
ChannelOpenOk(ChannelOpenOk),
ChannelFlow(ChannelFlow),
ChannelFlowOk(ChannelFlowOk),
ChannelClose(ChannelClose),
ChannelCloseOk(ChannelCloseOk),
ExchangeDeclare(ExchangeDeclare),
ExchangeDeclareOk(ExchangeDeclareOk),
ExchangeDelete(ExchangeDelete),
ExchangeDeleteOk(ExchangeDeleteOk),
QueueDeclare(QueueDeclare),
QueueDeclareOk(QueueDeclareOk),
QueueBind(QueueBind),
QueueBindOk(QueueBindOk),
QueueUnbind(QueueUnbind),
QueueUnbindOk(QueueUnbindOk),
QueuePurge(QueuePurge),
QueuePurgeOk(QueuePurgeOk),
QueueDelete(QueueDelete),
QueueDeleteOk(QueueDeleteOk),
BasicQos(BasicQos),
BasicQosOk(BasicQosOk),
BasicConsume(BasicConsume),
BasicConsumeOk(BasicConsumeOk),
BasicCancel(BasicCancel),
BasicCancelOk(BasicCancelOk),
BasicPublish(BasicPublish),
BasicReturn(BasicReturn),
BasicDeliver(BasicDeliver),
BasicGet(BasicGet),
BasicGetOk(BasicGetOk),
BasicGetEmpty(BasicGetEmpty),
BasicAck(BasicAck),
BasicReject(BasicReject),
BasicRecoverAsync(BasicRecoverAsync),
BasicRecover(BasicRecover),
BasicRecoverOk(BasicRecoverOk),
TxSelect(TxSelect),
TxSelectOk(TxSelectOk),
TxCommit(TxCommit),
TxCommitOk(TxCommitOk),
TxRollback(TxRollback),
TxRollbackOk(TxRollbackOk),
}
/// The connection class provides methods for a client to establish a network connection to /// The connection class provides methods for a client to establish a network connection to
/// a server, and for both peers to operate the connection thereafter. /// a server, and for both peers to operate the connection thereafter.
/// This method starts the connection negotiation process by telling the client the /// This method starts the connection negotiation process by telling the client the
/// protocol version that the server proposes, along with a list of security mechanisms /// protocol version that the server proposes, along with a list of security mechanisms
/// which the client can use for authentication. /// which the client can use for authentication.
ConnectionStart { #[derive(Debug, Clone, PartialEq)]
pub struct ConnectionStart {
/// The major version number can take any value from 0 to 99 as defined in the /// The major version number can take any value from 0 to 99 as defined in the
/// AMQP specification. /// AMQP specification.
version_major: Octet, pub version_major: Octet,
/// The minor version number can take any value from 0 to 99 as defined in the /// The minor version number can take any value from 0 to 99 as defined in the
/// AMQP specification. /// AMQP specification.
version_minor: Octet, pub version_minor: Octet,
server_properties: PeerProperties, pub server_properties: PeerProperties,
/// must not be null /// must not be null
/// ///
/// A list of the security mechanisms that the server supports, delimited by spaces. /// A list of the security mechanisms that the server supports, delimited by spaces.
mechanisms: Longstr, pub mechanisms: Longstr,
/// must not be null /// must not be null
/// ///
/// A list of the message locales that the server supports, delimited by spaces. The /// A list of the message locales that the server supports, delimited by spaces. The
/// locale defines the language in which the server will send reply texts. /// locale defines the language in which the server will send reply texts.
locales: Longstr, pub locales: Longstr,
}, }
/// The connection class provides methods for a client to establish a network connection to /// The connection class provides methods for a client to establish a network connection to
/// a server, and for both peers to operate the connection thereafter. /// a server, and for both peers to operate the connection thereafter.
/// This method selects a SASL security mechanism. /// This method selects a SASL security mechanism.
ConnectionStartOk { #[derive(Debug, Clone, PartialEq)]
client_properties: PeerProperties, pub struct ConnectionStartOk {
pub client_properties: PeerProperties,
/// must not be null /// must not be null
/// ///
/// A single security mechanisms selected by the client, which must be one of those /// A single security mechanisms selected by the client, which must be one of those
/// specified by the server. /// specified by the server.
mechanism: Shortstr, pub mechanism: Shortstr,
/// must not be null /// must not be null
/// ///
/// A block of opaque data passed to the security mechanism. The contents of this /// A block of opaque data passed to the security mechanism. The contents of this
/// data are defined by the SASL security mechanism. /// data are defined by the SASL security mechanism.
response: Longstr, pub response: Longstr,
/// must not be null /// must not be null
/// ///
/// A single message locale selected by the client, which must be one of those /// A single message locale selected by the client, which must be one of those
/// specified by the server. /// specified by the server.
locale: Shortstr, pub locale: Shortstr,
}, }
/// The connection class provides methods for a client to establish a network connection to /// The connection class provides methods for a client to establish a network connection to
/// a server, and for both peers to operate the connection thereafter. /// a server, and for both peers to operate the connection thereafter.
/// The SASL protocol works by exchanging challenges and responses until both peers have /// The SASL protocol works by exchanging challenges and responses until both peers have
/// received sufficient information to authenticate each other. This method challenges /// received sufficient information to authenticate each other. This method challenges
/// the client to provide more information. /// the client to provide more information.
ConnectionSecure { #[derive(Debug, Clone, PartialEq)]
pub struct ConnectionSecure {
/// Challenge information, a block of opaque binary data passed to the security /// Challenge information, a block of opaque binary data passed to the security
/// mechanism. /// mechanism.
challenge: Longstr, pub challenge: Longstr,
}, }
/// The connection class provides methods for a client to establish a network connection to /// The connection class provides methods for a client to establish a network connection to
/// a server, and for both peers to operate the connection thereafter. /// a server, and for both peers to operate the connection thereafter.
/// This method attempts to authenticate, passing a block of SASL data for the security /// This method attempts to authenticate, passing a block of SASL data for the security
/// mechanism at the server side. /// mechanism at the server side.
ConnectionSecureOk { #[derive(Debug, Clone, PartialEq)]
pub struct ConnectionSecureOk {
/// must not be null /// must not be null
/// ///
/// A block of opaque data passed to the security mechanism. The contents of this /// A block of opaque data passed to the security mechanism. The contents of this
/// data are defined by the SASL security mechanism. /// data are defined by the SASL security mechanism.
response: Longstr, pub response: Longstr,
}, }
/// The connection class provides methods for a client to establish a network connection to /// The connection class provides methods for a client to establish a network connection to
/// a server, and for both peers to operate the connection thereafter. /// a server, and for both peers to operate the connection thereafter.
/// This method proposes a set of connection configuration values to the client. The /// This method proposes a set of connection configuration values to the client. The
/// client can accept and/or adjust these. /// client can accept and/or adjust these.
ConnectionTune { #[derive(Debug, Clone, PartialEq)]
pub struct ConnectionTune {
/// Specifies highest channel number that the server permits. Usable channel numbers /// Specifies highest channel number that the server permits. Usable channel numbers
/// are in the range 1..channel-max. Zero indicates no specified limit. /// are in the range 1..channel-max. Zero indicates no specified limit.
channel_max: Short, pub channel_max: Short,
/// The largest frame size that the server proposes for the connection, including /// The largest frame size that the server proposes for the connection, including
/// frame header and end-byte. The client can negotiate a lower value. Zero means /// frame header and end-byte. The client can negotiate a lower value. Zero means
/// that the server does not impose any specific limit but may reject very large /// that the server does not impose any specific limit but may reject very large
/// frames if it cannot allocate resources for them. /// frames if it cannot allocate resources for them.
frame_max: Long, pub frame_max: Long,
/// The delay, in seconds, of the connection heartbeat that the server wants. /// The delay, in seconds, of the connection heartbeat that the server wants.
/// Zero means the server does not want a heartbeat. /// Zero means the server does not want a heartbeat.
heartbeat: Short, pub heartbeat: Short,
}, }
/// The connection class provides methods for a client to establish a network connection to /// The connection class provides methods for a client to establish a network connection to
/// a server, and for both peers to operate the connection thereafter. /// a server, and for both peers to operate the connection thereafter.
/// This method sends the client's connection tuning parameters to the server. /// This method sends the client's connection tuning parameters to the server.
/// Certain fields are negotiated, others provide capability information. /// Certain fields are negotiated, others provide capability information.
ConnectionTuneOk { #[derive(Debug, Clone, PartialEq)]
pub struct ConnectionTuneOk {
/// must not be null, must be less than the tune field of the method channel-max /// must not be null, must be less than the tune field of the method channel-max
/// ///
/// The maximum total number of channels that the client will use per connection. /// The maximum total number of channels that the client will use per connection.
channel_max: Short, pub channel_max: Short,
/// The largest frame size that the client and server will use for the connection. /// The largest frame size that the client and server will use for the connection.
/// Zero means that the client does not impose any specific limit but may reject /// Zero means that the client does not impose any specific limit but may reject
/// very large frames if it cannot allocate resources for them. Note that the /// very large frames if it cannot allocate resources for them. Note that the
/// frame-max limit applies principally to content frames, where large contents can /// frame-max limit applies principally to content frames, where large contents can
/// be broken into frames of arbitrary size. /// be broken into frames of arbitrary size.
frame_max: Long, pub frame_max: Long,
/// The delay, in seconds, of the connection heartbeat that the client wants. Zero /// The delay, in seconds, of the connection heartbeat that the client wants. Zero
/// means the client does not want a heartbeat. /// means the client does not want a heartbeat.
heartbeat: Short, pub heartbeat: Short,
}, }
/// The connection class provides methods for a client to establish a network connection to /// The connection class provides methods for a client to establish a network connection to
/// a server, and for both peers to operate the connection thereafter. /// a server, and for both peers to operate the connection thereafter.
/// This method opens a connection to a virtual host, which is a collection of /// This method opens a connection to a virtual host, which is a collection of
/// resources, and acts to separate multiple application domains within a server. /// resources, and acts to separate multiple application domains within a server.
/// The server may apply arbitrary limits per virtual host, such as the number /// The server may apply arbitrary limits per virtual host, such as the number
/// of each type of entity that may be used, per connection and/or in total. /// of each type of entity that may be used, per connection and/or in total.
ConnectionOpen { #[derive(Debug, Clone, PartialEq)]
pub struct ConnectionOpen {
/// The name of the virtual host to work with. /// The name of the virtual host to work with.
virtual_host: Path, pub virtual_host: Path,
reserved_1: Shortstr, pub reserved_1: Shortstr,
reserved_2: Bit, pub reserved_2: Bit,
}, }
/// The connection class provides methods for a client to establish a network connection to /// The connection class provides methods for a client to establish a network connection to
/// a server, and for both peers to operate the connection thereafter. /// a server, and for both peers to operate the connection thereafter.
/// This method signals to the client that the connection is ready for use. /// This method signals to the client that the connection is ready for use.
ConnectionOpenOk { reserved_1: Shortstr }, #[derive(Debug, Clone, PartialEq)]
pub struct ConnectionOpenOk {
pub reserved_1: Shortstr,
}
/// The connection class provides methods for a client to establish a network connection to /// The connection class provides methods for a client to establish a network connection to
/// a server, and for both peers to operate the connection thereafter. /// a server, and for both peers to operate the connection thereafter.
/// This method indicates that the sender wants to close the connection. This may be /// This method indicates that the sender wants to close the connection. This may be
/// due to internal conditions (e.g. a forced shut-down) or due to an error handling /// due to internal conditions (e.g. a forced shut-down) or due to an error handling
/// a specific method, i.e. an exception. When a close is due to an exception, the /// a specific method, i.e. an exception. When a close is due to an exception, the
/// sender provides the class and method id of the method which caused the exception. /// sender provides the class and method id of the method which caused the exception.
ConnectionClose { #[derive(Debug, Clone, PartialEq)]
reply_code: ReplyCode, pub struct ConnectionClose {
reply_text: ReplyText, pub reply_code: ReplyCode,
pub reply_text: ReplyText,
/// When the close is provoked by a method exception, this is the class of the /// When the close is provoked by a method exception, this is the class of the
/// method. /// method.
class_id: ClassId, pub class_id: ClassId,
/// When the close is provoked by a method exception, this is the ID of the method. /// When the close is provoked by a method exception, this is the ID of the method.
method_id: MethodId, pub method_id: MethodId,
}, }
/// The connection class provides methods for a client to establish a network connection to /// The connection class provides methods for a client to establish a network connection to
/// a server, and for both peers to operate the connection thereafter. /// a server, and for both peers to operate the connection thereafter.
/// This method confirms a Connection.Close method and tells the recipient that it is /// This method confirms a Connection.Close method and tells the recipient that it is
/// safe to release resources for the connection and close the socket. /// safe to release resources for the connection and close the socket.
ConnectionCloseOk, #[derive(Debug, Clone, PartialEq)]
pub struct ConnectionCloseOk;
/// The channel class provides methods for a client to establish a channel to a /// The channel class provides methods for a client to establish a channel to a
/// server and for both peers to operate the channel thereafter. /// server and for both peers to operate the channel thereafter.
/// This method opens a channel to the server. /// This method opens a channel to the server.
ChannelOpen { reserved_1: Shortstr }, #[derive(Debug, Clone, PartialEq)]
pub struct ChannelOpen {
pub reserved_1: Shortstr,
}
/// The channel class provides methods for a client to establish a channel to a /// The channel class provides methods for a client to establish a channel to a
/// server and for both peers to operate the channel thereafter. /// server and for both peers to operate the channel thereafter.
/// This method signals to the client that the channel is ready for use. /// This method signals to the client that the channel is ready for use.
ChannelOpenOk { reserved_1: Longstr }, #[derive(Debug, Clone, PartialEq)]
pub struct ChannelOpenOk {
pub reserved_1: Longstr,
}
/// The channel class provides methods for a client to establish a channel to a /// The channel class provides methods for a client to establish a channel to a
/// server and for both peers to operate the channel thereafter. /// server and for both peers to operate the channel thereafter.
/// This method asks the peer to pause or restart the flow of content data sent by /// This method asks the peer to pause or restart the flow of content data sent by
@ -278,145 +363,165 @@ pub enum Method {
/// overflowing its queues or otherwise finding itself receiving more messages than /// overflowing its queues or otherwise finding itself receiving more messages than
/// it can process. Note that this method is not intended for window control. It does /// it can process. Note that this method is not intended for window control. It does
/// not affect contents returned by Basic.Get-Ok methods. /// not affect contents returned by Basic.Get-Ok methods.
ChannelFlow { #[derive(Debug, Clone, PartialEq)]
pub struct ChannelFlow {
/// If 1, the peer starts sending content frames. If 0, the peer stops sending /// If 1, the peer starts sending content frames. If 0, the peer stops sending
/// content frames. /// content frames.
active: Bit, pub active: Bit,
}, }
/// The channel class provides methods for a client to establish a channel to a /// The channel class provides methods for a client to establish a channel to a
/// server and for both peers to operate the channel thereafter. /// server and for both peers to operate the channel thereafter.
/// Confirms to the peer that a flow command was received and processed. /// Confirms to the peer that a flow command was received and processed.
ChannelFlowOk { #[derive(Debug, Clone, PartialEq)]
pub struct ChannelFlowOk {
/// Confirms the setting of the processed flow method: 1 means the peer will start /// Confirms the setting of the processed flow method: 1 means the peer will start
/// sending or continue to send content frames; 0 means it will not. /// sending or continue to send content frames; 0 means it will not.
active: Bit, pub active: Bit,
}, }
/// The channel class provides methods for a client to establish a channel to a /// The channel class provides methods for a client to establish a channel to a
/// server and for both peers to operate the channel thereafter. /// server and for both peers to operate the channel thereafter.
/// This method indicates that the sender wants to close the channel. This may be due to /// This method indicates that the sender wants to close the channel. This may be due to
/// internal conditions (e.g. a forced shut-down) or due to an error handling a specific /// internal conditions (e.g. a forced shut-down) or due to an error handling a specific
/// method, i.e. an exception. When a close is due to an exception, the sender provides /// method, i.e. an exception. When a close is due to an exception, the sender provides
/// the class and method id of the method which caused the exception. /// the class and method id of the method which caused the exception.
ChannelClose { #[derive(Debug, Clone, PartialEq)]
reply_code: ReplyCode, pub struct ChannelClose {
reply_text: ReplyText, pub reply_code: ReplyCode,
pub reply_text: ReplyText,
/// When the close is provoked by a method exception, this is the class of the /// When the close is provoked by a method exception, this is the class of the
/// method. /// method.
class_id: ClassId, pub class_id: ClassId,
/// When the close is provoked by a method exception, this is the ID of the method. /// When the close is provoked by a method exception, this is the ID of the method.
method_id: MethodId, pub method_id: MethodId,
}, }
/// The channel class provides methods for a client to establish a channel to a /// The channel class provides methods for a client to establish a channel to a
/// server and for both peers to operate the channel thereafter. /// server and for both peers to operate the channel thereafter.
/// This method confirms a Channel.Close method and tells the recipient that it is safe /// This method confirms a Channel.Close method and tells the recipient that it is safe
/// to release resources for the channel. /// to release resources for the channel.
ChannelCloseOk, #[derive(Debug, Clone, PartialEq)]
pub struct ChannelCloseOk;
/// Exchanges match and distribute messages across queues. Exchanges can be configured in /// Exchanges match and distribute messages across queues. Exchanges can be configured in
/// the server or declared at runtime. /// the server or declared at runtime.
/// This method creates an exchange if it does not already exist, and if the exchange /// This method creates an exchange if it does not already exist, and if the exchange
/// exists, verifies that it is of the correct and expected class. /// exists, verifies that it is of the correct and expected class.
ExchangeDeclare { #[derive(Debug, Clone, PartialEq)]
reserved_1: Short, pub struct ExchangeDeclare {
pub reserved_1: Short,
/// must not be null /// must not be null
exchange: ExchangeName, pub exchange: ExchangeName,
/// Each exchange belongs to one of a set of exchange types implemented by the /// Each exchange belongs to one of a set of exchange types implemented by the
/// server. The exchange types define the functionality of the exchange - i.e. how /// server. The exchange types define the functionality of the exchange - i.e. how
/// messages are routed through it. It is not valid or meaningful to attempt to /// messages are routed through it. It is not valid or meaningful to attempt to
/// change the type of an existing exchange. /// change the type of an existing exchange.
r#type: Shortstr, pub r#type: Shortstr,
/// If set, the server will reply with Declare-Ok if the exchange already /// If set, the server will reply with Declare-Ok if the exchange already
/// exists with the same name, and raise an error if not. The client can /// exists with the same name, and raise an error if not. The client can
/// use this to check whether an exchange exists without modifying the /// use this to check whether an exchange exists without modifying the
/// server state. When set, all other method fields except name and no-wait /// server state. When set, all other method fields except name and no-wait
/// are ignored. A declare with both passive and no-wait has no effect. /// are ignored. A declare with both passive and no-wait has no effect.
/// Arguments are compared for semantic equivalence. /// Arguments are compared for semantic equivalence.
passive: Bit, pub passive: Bit,
/// If set when creating a new exchange, the exchange will be marked as durable. /// If set when creating a new exchange, the exchange will be marked as durable.
/// Durable exchanges remain active when a server restarts. Non-durable exchanges /// Durable exchanges remain active when a server restarts. Non-durable exchanges
/// (transient exchanges) are purged if/when a server restarts. /// (transient exchanges) are purged if/when a server restarts.
durable: Bit, pub durable: Bit,
reserved_2: Bit, pub reserved_2: Bit,
reserved_3: Bit, pub reserved_3: Bit,
no_wait: NoWait, pub no_wait: NoWait,
/// A set of arguments for the declaration. The syntax and semantics of these /// A set of arguments for the declaration. The syntax and semantics of these
/// arguments depends on the server implementation. /// arguments depends on the server implementation.
arguments: Table, pub arguments: Table,
}, }
/// Exchanges match and distribute messages across queues. Exchanges can be configured in /// Exchanges match and distribute messages across queues. Exchanges can be configured in
/// the server or declared at runtime. /// the server or declared at runtime.
/// This method confirms a Declare method and confirms the name of the exchange, /// This method confirms a Declare method and confirms the name of the exchange,
/// essential for automatically-named exchanges. /// essential for automatically-named exchanges.
ExchangeDeclareOk, #[derive(Debug, Clone, PartialEq)]
pub struct ExchangeDeclareOk;
/// Exchanges match and distribute messages across queues. Exchanges can be configured in /// Exchanges match and distribute messages across queues. Exchanges can be configured in
/// the server or declared at runtime. /// the server or declared at runtime.
/// This method deletes an exchange. When an exchange is deleted all queue bindings on /// This method deletes an exchange. When an exchange is deleted all queue bindings on
/// the exchange are cancelled. /// the exchange are cancelled.
ExchangeDelete { #[derive(Debug, Clone, PartialEq)]
reserved_1: Short, pub struct ExchangeDelete {
pub reserved_1: Short,
/// must not be null /// must not be null
exchange: ExchangeName, pub exchange: ExchangeName,
/// If set, the server will only delete the exchange if it has no queue bindings. If /// If set, the server will only delete the exchange if it has no queue bindings. If
/// the exchange has queue bindings the server does not delete it but raises a /// the exchange has queue bindings the server does not delete it but raises a
/// channel exception instead. /// channel exception instead.
if_unused: Bit, pub if_unused: Bit,
no_wait: NoWait, pub no_wait: NoWait,
}, }
/// Exchanges match and distribute messages across queues. Exchanges can be configured in /// Exchanges match and distribute messages across queues. Exchanges can be configured in
/// the server or declared at runtime. /// the server or declared at runtime.
/// This method confirms the deletion of an exchange. /// This method confirms the deletion of an exchange.
ExchangeDeleteOk, #[derive(Debug, Clone, PartialEq)]
pub struct ExchangeDeleteOk;
/// Queues store and forward messages. Queues can be configured in the server or created at /// Queues store and forward messages. Queues can be configured in the server or created at
/// runtime. Queues must be attached to at least one exchange in order to receive messages /// runtime. Queues must be attached to at least one exchange in order to receive messages
/// from publishers. /// from publishers.
/// This method creates or checks a queue. When creating a new queue the client can /// This method creates or checks a queue. When creating a new queue the client can
/// specify various properties that control the durability of the queue and its /// specify various properties that control the durability of the queue and its
/// contents, and the level of sharing for the queue. /// contents, and the level of sharing for the queue.
QueueDeclare { #[derive(Debug, Clone, PartialEq)]
reserved_1: Short, pub struct QueueDeclare {
queue: QueueName, pub reserved_1: Short,
pub queue: QueueName,
/// If set, the server will reply with Declare-Ok if the queue already /// If set, the server will reply with Declare-Ok if the queue already
/// exists with the same name, and raise an error if not. The client can /// exists with the same name, and raise an error if not. The client can
/// use this to check whether a queue exists without modifying the /// use this to check whether a queue exists without modifying the
/// server state. When set, all other method fields except name and no-wait /// server state. When set, all other method fields except name and no-wait
/// are ignored. A declare with both passive and no-wait has no effect. /// are ignored. A declare with both passive and no-wait has no effect.
/// Arguments are compared for semantic equivalence. /// Arguments are compared for semantic equivalence.
passive: Bit, pub passive: Bit,
/// If set when creating a new queue, the queue will be marked as durable. Durable /// If set when creating a new queue, the queue will be marked as durable. Durable
/// queues remain active when a server restarts. Non-durable queues (transient /// queues remain active when a server restarts. Non-durable queues (transient
/// queues) are purged if/when a server restarts. Note that durable queues do not /// queues) are purged if/when a server restarts. Note that durable queues do not
/// necessarily hold persistent messages, although it does not make sense to send /// necessarily hold persistent messages, although it does not make sense to send
/// persistent messages to a transient queue. /// persistent messages to a transient queue.
durable: Bit, pub durable: Bit,
/// Exclusive queues may only be accessed by the current connection, and are /// Exclusive queues may only be accessed by the current connection, and are
/// deleted when that connection closes. Passive declaration of an exclusive /// deleted when that connection closes. Passive declaration of an exclusive
/// queue by other connections are not allowed. /// queue by other connections are not allowed.
exclusive: Bit, pub exclusive: Bit,
/// If set, the queue is deleted when all consumers have finished using it. The last /// If set, the queue is deleted when all consumers have finished using it. The last
/// consumer can be cancelled either explicitly or because its channel is closed. If /// consumer can be cancelled either explicitly or because its channel is closed. If
/// there was no consumer ever on the queue, it won't be deleted. Applications can /// there was no consumer ever on the queue, it won't be deleted. Applications can
/// explicitly delete auto-delete queues using the Delete method as normal. /// explicitly delete auto-delete queues using the Delete method as normal.
auto_delete: Bit, pub auto_delete: Bit,
no_wait: NoWait, pub no_wait: NoWait,
/// A set of arguments for the declaration. The syntax and semantics of these /// A set of arguments for the declaration. The syntax and semantics of these
/// arguments depends on the server implementation. /// arguments depends on the server implementation.
arguments: Table, pub arguments: Table,
}, }
/// Queues store and forward messages. Queues can be configured in the server or created at /// Queues store and forward messages. Queues can be configured in the server or created at
/// runtime. Queues must be attached to at least one exchange in order to receive messages /// runtime. Queues must be attached to at least one exchange in order to receive messages
/// from publishers. /// from publishers.
/// This method confirms a Declare method and confirms the name of the queue, essential /// This method confirms a Declare method and confirms the name of the queue, essential
/// for automatically-named queues. /// for automatically-named queues.
QueueDeclareOk { #[derive(Debug, Clone, PartialEq)]
pub struct QueueDeclareOk {
/// must not be null /// must not be null
/// ///
/// Reports the name of the queue. If the server generated a queue name, this field /// Reports the name of the queue. If the server generated a queue name, this field
/// contains that name. /// contains that name.
queue: QueueName, pub queue: QueueName,
message_count: MessageCount, pub message_count: MessageCount,
/// Reports the number of active consumers for the queue. Note that consumers can /// Reports the number of active consumers for the queue. Note that consumers can
/// suspend activity (Channel.Flow) in which case they do not appear in this count. /// suspend activity (Channel.Flow) in which case they do not appear in this count.
consumer_count: Long, pub consumer_count: Long,
}, }
/// Queues store and forward messages. Queues can be configured in the server or created at /// Queues store and forward messages. Queues can be configured in the server or created at
/// runtime. Queues must be attached to at least one exchange in order to receive messages /// runtime. Queues must be attached to at least one exchange in order to receive messages
/// from publishers. /// from publishers.
@ -424,11 +529,12 @@ pub enum Method {
/// receive any messages. In a classic messaging model, store-and-forward queues /// receive any messages. In a classic messaging model, store-and-forward queues
/// are bound to a direct exchange and subscription queues are bound to a topic /// are bound to a direct exchange and subscription queues are bound to a topic
/// exchange. /// exchange.
QueueBind { #[derive(Debug, Clone, PartialEq)]
reserved_1: Short, pub struct QueueBind {
pub reserved_1: Short,
/// Specifies the name of the queue to bind. /// Specifies the name of the queue to bind.
queue: QueueName, pub queue: QueueName,
exchange: ExchangeName, pub exchange: ExchangeName,
/// Specifies the routing key for the binding. The routing key is used for routing /// Specifies the routing key for the binding. The routing key is used for routing
/// messages depending on the exchange configuration. Not all exchanges use a /// messages depending on the exchange configuration. Not all exchanges use a
/// routing key - refer to the specific exchange documentation. If the queue name /// routing key - refer to the specific exchange documentation. If the queue name
@ -437,89 +543,105 @@ pub enum Method {
/// key as well. If the queue name is provided but the routing key is empty, the /// key as well. If the queue name is provided but the routing key is empty, the
/// server does the binding with that empty routing key. The meaning of empty /// server does the binding with that empty routing key. The meaning of empty
/// routing keys depends on the exchange implementation. /// routing keys depends on the exchange implementation.
routing_key: Shortstr, pub routing_key: Shortstr,
no_wait: NoWait, pub no_wait: NoWait,
/// A set of arguments for the binding. The syntax and semantics of these arguments /// A set of arguments for the binding. The syntax and semantics of these arguments
/// depends on the exchange class. /// depends on the exchange class.
arguments: Table, pub arguments: Table,
}, }
/// Queues store and forward messages. Queues can be configured in the server or created at /// Queues store and forward messages. Queues can be configured in the server or created at
/// runtime. Queues must be attached to at least one exchange in order to receive messages /// runtime. Queues must be attached to at least one exchange in order to receive messages
/// from publishers. /// from publishers.
/// This method confirms that the bind was successful. /// This method confirms that the bind was successful.
QueueBindOk, #[derive(Debug, Clone, PartialEq)]
pub struct QueueBindOk;
/// Queues store and forward messages. Queues can be configured in the server or created at /// Queues store and forward messages. Queues can be configured in the server or created at
/// runtime. Queues must be attached to at least one exchange in order to receive messages /// runtime. Queues must be attached to at least one exchange in order to receive messages
/// from publishers. /// from publishers.
/// This method unbinds a queue from an exchange. /// This method unbinds a queue from an exchange.
QueueUnbind { #[derive(Debug, Clone, PartialEq)]
reserved_1: Short, pub struct QueueUnbind {
pub reserved_1: Short,
/// Specifies the name of the queue to unbind. /// Specifies the name of the queue to unbind.
queue: QueueName, pub queue: QueueName,
/// The name of the exchange to unbind from. /// The name of the exchange to unbind from.
exchange: ExchangeName, pub exchange: ExchangeName,
/// Specifies the routing key of the binding to unbind. /// Specifies the routing key of the binding to unbind.
routing_key: Shortstr, pub routing_key: Shortstr,
/// Specifies the arguments of the binding to unbind. /// Specifies the arguments of the binding to unbind.
arguments: Table, pub arguments: Table,
}, }
/// Queues store and forward messages. Queues can be configured in the server or created at /// Queues store and forward messages. Queues can be configured in the server or created at
/// runtime. Queues must be attached to at least one exchange in order to receive messages /// runtime. Queues must be attached to at least one exchange in order to receive messages
/// from publishers. /// from publishers.
/// This method confirms that the unbind was successful. /// This method confirms that the unbind was successful.
QueueUnbindOk, #[derive(Debug, Clone, PartialEq)]
pub struct QueueUnbindOk;
/// Queues store and forward messages. Queues can be configured in the server or created at /// Queues store and forward messages. Queues can be configured in the server or created at
/// runtime. Queues must be attached to at least one exchange in order to receive messages /// runtime. Queues must be attached to at least one exchange in order to receive messages
/// from publishers. /// from publishers.
/// This method removes all messages from a queue which are not awaiting /// This method removes all messages from a queue which are not awaiting
/// acknowledgment. /// acknowledgment.
QueuePurge { #[derive(Debug, Clone, PartialEq)]
reserved_1: Short, pub struct QueuePurge {
pub reserved_1: Short,
/// Specifies the name of the queue to purge. /// Specifies the name of the queue to purge.
queue: QueueName, pub queue: QueueName,
no_wait: NoWait, pub no_wait: NoWait,
}, }
/// Queues store and forward messages. Queues can be configured in the server or created at /// Queues store and forward messages. Queues can be configured in the server or created at
/// runtime. Queues must be attached to at least one exchange in order to receive messages /// runtime. Queues must be attached to at least one exchange in order to receive messages
/// from publishers. /// from publishers.
/// This method confirms the purge of a queue. /// This method confirms the purge of a queue.
QueuePurgeOk { #[derive(Debug, Clone, PartialEq)]
pub struct QueuePurgeOk {
/// Reports the number of messages purged. /// Reports the number of messages purged.
message_count: MessageCount, pub message_count: MessageCount,
}, }
/// Queues store and forward messages. Queues can be configured in the server or created at /// Queues store and forward messages. Queues can be configured in the server or created at
/// runtime. Queues must be attached to at least one exchange in order to receive messages /// runtime. Queues must be attached to at least one exchange in order to receive messages
/// from publishers. /// from publishers.
/// This method deletes a queue. When a queue is deleted any pending messages are sent /// This method deletes a queue. When a queue is deleted any pending messages are sent
/// to a dead-letter queue if this is defined in the server configuration, and all /// to a dead-letter queue if this is defined in the server configuration, and all
/// consumers on the queue are cancelled. /// consumers on the queue are cancelled.
QueueDelete { #[derive(Debug, Clone, PartialEq)]
reserved_1: Short, pub struct QueueDelete {
pub reserved_1: Short,
/// Specifies the name of the queue to delete. /// Specifies the name of the queue to delete.
queue: QueueName, pub queue: QueueName,
/// If set, the server will only delete the queue if it has no consumers. If the /// If set, the server will only delete the queue if it has no consumers. If the
/// queue has consumers the server does does not delete it but raises a channel /// queue has consumers the server does does not delete it but raises a channel
/// exception instead. /// exception instead.
if_unused: Bit, pub if_unused: Bit,
/// If set, the server will only delete the queue if it has no messages. /// If set, the server will only delete the queue if it has no messages.
if_empty: Bit, pub if_empty: Bit,
no_wait: NoWait, pub no_wait: NoWait,
}, }
/// Queues store and forward messages. Queues can be configured in the server or created at /// Queues store and forward messages. Queues can be configured in the server or created at
/// runtime. Queues must be attached to at least one exchange in order to receive messages /// runtime. Queues must be attached to at least one exchange in order to receive messages
/// from publishers. /// from publishers.
/// This method confirms the deletion of a queue. /// This method confirms the deletion of a queue.
QueueDeleteOk { #[derive(Debug, Clone, PartialEq)]
pub struct QueueDeleteOk {
/// Reports the number of messages deleted. /// Reports the number of messages deleted.
message_count: MessageCount, pub message_count: MessageCount,
}, }
/// The Basic class provides methods that support an industry-standard messaging model. /// The Basic class provides methods that support an industry-standard messaging model.
/// This method requests a specific quality of service. The QoS can be specified for the /// This method requests a specific quality of service. The QoS can be specified for the
/// current channel or for all channels on the connection. The particular properties and /// current channel or for all channels on the connection. The particular properties and
/// semantics of a qos method always depend on the content class semantics. Though the /// semantics of a qos method always depend on the content class semantics. Though the
/// qos method could in principle apply to both peers, it is currently meaningful only /// qos method could in principle apply to both peers, it is currently meaningful only
/// for the server. /// for the server.
BasicQos { #[derive(Debug, Clone, PartialEq)]
pub struct BasicQos {
/// The client can request that messages be sent in advance so that when the client /// The client can request that messages be sent in advance so that when the client
/// finishes processing a message, the following message is already held locally, /// finishes processing a message, the following message is already held locally,
/// rather than needing to be sent down the channel. Prefetching gives a performance /// rather than needing to be sent down the channel. Prefetching gives a performance
@ -528,187 +650,224 @@ pub enum Method {
/// available prefetch size (and also falls into other prefetch limits). May be set /// available prefetch size (and also falls into other prefetch limits). May be set
/// to zero, meaning "no specific limit", although other prefetch limits may still /// to zero, meaning "no specific limit", although other prefetch limits may still
/// apply. The prefetch-size is ignored if the no-ack option is set. /// apply. The prefetch-size is ignored if the no-ack option is set.
prefetch_size: Long, pub prefetch_size: Long,
/// Specifies a prefetch window in terms of whole messages. This field may be used /// Specifies a prefetch window in terms of whole messages. This field may be used
/// in combination with the prefetch-size field; a message will only be sent in /// in combination with the prefetch-size field; a message will only be sent in
/// advance if both prefetch windows (and those at the channel and connection level) /// advance if both prefetch windows (and those at the channel and connection level)
/// allow it. The prefetch-count is ignored if the no-ack option is set. /// allow it. The prefetch-count is ignored if the no-ack option is set.
prefetch_count: Short, pub prefetch_count: Short,
/// By default the QoS settings apply to the current channel only. If this field is /// By default the QoS settings apply to the current channel only. If this field is
/// set, they are applied to the entire connection. /// set, they are applied to the entire connection.
global: Bit, pub global: Bit,
}, }
/// The Basic class provides methods that support an industry-standard messaging model. /// The Basic class provides methods that support an industry-standard messaging model.
/// This method tells the client that the requested QoS levels could be handled by the /// This method tells the client that the requested QoS levels could be handled by the
/// server. The requested QoS applies to all active consumers until a new QoS is /// server. The requested QoS applies to all active consumers until a new QoS is
/// defined. /// defined.
BasicQosOk, #[derive(Debug, Clone, PartialEq)]
pub struct BasicQosOk;
/// The Basic class provides methods that support an industry-standard messaging model. /// The Basic class provides methods that support an industry-standard messaging model.
/// This method asks the server to start a "consumer", which is a transient request for /// This method asks the server to start a "consumer", which is a transient request for
/// messages from a specific queue. Consumers last as long as the channel they were /// messages from a specific queue. Consumers last as long as the channel they were
/// declared on, or until the client cancels them. /// declared on, or until the client cancels them.
BasicConsume { #[derive(Debug, Clone, PartialEq)]
reserved_1: Short, pub struct BasicConsume {
pub reserved_1: Short,
/// Specifies the name of the queue to consume from. /// Specifies the name of the queue to consume from.
queue: QueueName, pub queue: QueueName,
/// Specifies the identifier for the consumer. The consumer tag is local to a /// Specifies the identifier for the consumer. The consumer tag is local to a
/// channel, so two clients can use the same consumer tags. If this field is /// channel, so two clients can use the same consumer tags. If this field is
/// empty the server will generate a unique tag. /// empty the server will generate a unique tag.
consumer_tag: ConsumerTag, pub consumer_tag: ConsumerTag,
no_local: NoLocal, pub no_local: NoLocal,
no_ack: NoAck, pub no_ack: NoAck,
/// Request exclusive consumer access, meaning only this consumer can access the /// Request exclusive consumer access, meaning only this consumer can access the
/// queue. /// queue.
exclusive: Bit, pub exclusive: Bit,
no_wait: NoWait, pub no_wait: NoWait,
/// A set of arguments for the consume. The syntax and semantics of these /// A set of arguments for the consume. The syntax and semantics of these
/// arguments depends on the server implementation. /// arguments depends on the server implementation.
arguments: Table, pub arguments: Table,
}, }
/// The Basic class provides methods that support an industry-standard messaging model. /// The Basic class provides methods that support an industry-standard messaging model.
/// The server provides the client with a consumer tag, which is used by the client /// The server provides the client with a consumer tag, which is used by the client
/// for methods called on the consumer at a later stage. /// for methods called on the consumer at a later stage.
BasicConsumeOk { #[derive(Debug, Clone, PartialEq)]
pub struct BasicConsumeOk {
/// Holds the consumer tag specified by the client or provided by the server. /// Holds the consumer tag specified by the client or provided by the server.
consumer_tag: ConsumerTag, pub consumer_tag: ConsumerTag,
}, }
/// The Basic class provides methods that support an industry-standard messaging model. /// The Basic class provides methods that support an industry-standard messaging model.
/// This method cancels a consumer. This does not affect already delivered /// This method cancels a consumer. This does not affect already delivered
/// messages, but it does mean the server will not send any more messages for /// messages, but it does mean the server will not send any more messages for
/// that consumer. The client may receive an arbitrary number of messages in /// that consumer. The client may receive an arbitrary number of messages in
/// between sending the cancel method and receiving the cancel-ok reply. /// between sending the cancel method and receiving the cancel-ok reply.
BasicCancel { #[derive(Debug, Clone, PartialEq)]
consumer_tag: ConsumerTag, pub struct BasicCancel {
no_wait: NoWait, pub consumer_tag: ConsumerTag,
}, pub no_wait: NoWait,
}
/// The Basic class provides methods that support an industry-standard messaging model. /// The Basic class provides methods that support an industry-standard messaging model.
/// This method confirms that the cancellation was completed. /// This method confirms that the cancellation was completed.
BasicCancelOk { consumer_tag: ConsumerTag }, #[derive(Debug, Clone, PartialEq)]
pub struct BasicCancelOk {
pub consumer_tag: ConsumerTag,
}
/// The Basic class provides methods that support an industry-standard messaging model. /// The Basic class provides methods that support an industry-standard messaging model.
/// This method publishes a message to a specific exchange. The message will be routed /// This method publishes a message to a specific exchange. The message will be routed
/// to queues as defined by the exchange configuration and distributed to any active /// to queues as defined by the exchange configuration and distributed to any active
/// consumers when the transaction, if any, is committed. /// consumers when the transaction, if any, is committed.
BasicPublish { #[derive(Debug, Clone, PartialEq)]
reserved_1: Short, pub struct BasicPublish {
pub reserved_1: Short,
/// Specifies the name of the exchange to publish to. The exchange name can be /// Specifies the name of the exchange to publish to. The exchange name can be
/// empty, meaning the default exchange. If the exchange name is specified, and that /// empty, meaning the default exchange. If the exchange name is specified, and that
/// exchange does not exist, the server will raise a channel exception. /// exchange does not exist, the server will raise a channel exception.
exchange: ExchangeName, pub exchange: ExchangeName,
/// Specifies the routing key for the message. The routing key is used for routing /// Specifies the routing key for the message. The routing key is used for routing
/// messages depending on the exchange configuration. /// messages depending on the exchange configuration.
routing_key: Shortstr, pub routing_key: Shortstr,
/// This flag tells the server how to react if the message cannot be routed to a /// This flag tells the server how to react if the message cannot be routed to a
/// queue. If this flag is set, the server will return an unroutable message with a /// queue. If this flag is set, the server will return an unroutable message with a
/// Return method. If this flag is zero, the server silently drops the message. /// Return method. If this flag is zero, the server silently drops the message.
mandatory: Bit, pub mandatory: Bit,
/// This flag tells the server how to react if the message cannot be routed to a /// This flag tells the server how to react if the message cannot be routed to a
/// queue consumer immediately. If this flag is set, the server will return an /// queue consumer immediately. If this flag is set, the server will return an
/// undeliverable message with a Return method. If this flag is zero, the server /// undeliverable message with a Return method. If this flag is zero, the server
/// will queue the message, but with no guarantee that it will ever be consumed. /// will queue the message, but with no guarantee that it will ever be consumed.
immediate: Bit, pub immediate: Bit,
}, }
/// The Basic class provides methods that support an industry-standard messaging model. /// The Basic class provides methods that support an industry-standard messaging model.
/// This method returns an undeliverable message that was published with the "immediate" /// This method returns an undeliverable message that was published with the "immediate"
/// flag set, or an unroutable message published with the "mandatory" flag set. The /// flag set, or an unroutable message published with the "mandatory" flag set. The
/// reply code and text provide information about the reason that the message was /// reply code and text provide information about the reason that the message was
/// undeliverable. /// undeliverable.
BasicReturn { #[derive(Debug, Clone, PartialEq)]
reply_code: ReplyCode, pub struct BasicReturn {
reply_text: ReplyText, pub reply_code: ReplyCode,
pub reply_text: ReplyText,
/// Specifies the name of the exchange that the message was originally published /// Specifies the name of the exchange that the message was originally published
/// to. May be empty, meaning the default exchange. /// to. May be empty, meaning the default exchange.
exchange: ExchangeName, pub exchange: ExchangeName,
/// Specifies the routing key name specified when the message was published. /// Specifies the routing key name specified when the message was published.
routing_key: Shortstr, pub routing_key: Shortstr,
}, }
/// The Basic class provides methods that support an industry-standard messaging model. /// The Basic class provides methods that support an industry-standard messaging model.
/// This method delivers a message to the client, via a consumer. In the asynchronous /// This method delivers a message to the client, via a consumer. In the asynchronous
/// message delivery model, the client starts a consumer using the Consume method, then /// message delivery model, the client starts a consumer using the Consume method, then
/// the server responds with Deliver methods as and when messages arrive for that /// the server responds with Deliver methods as and when messages arrive for that
/// consumer. /// consumer.
BasicDeliver { #[derive(Debug, Clone, PartialEq)]
consumer_tag: ConsumerTag, pub struct BasicDeliver {
delivery_tag: DeliveryTag, pub consumer_tag: ConsumerTag,
redelivered: Redelivered, pub delivery_tag: DeliveryTag,
pub redelivered: Redelivered,
/// Specifies the name of the exchange that the message was originally published to. /// Specifies the name of the exchange that the message was originally published to.
/// May be empty, indicating the default exchange. /// May be empty, indicating the default exchange.
exchange: ExchangeName, pub exchange: ExchangeName,
/// Specifies the routing key name specified when the message was published. /// Specifies the routing key name specified when the message was published.
routing_key: Shortstr, pub routing_key: Shortstr,
}, }
/// The Basic class provides methods that support an industry-standard messaging model. /// The Basic class provides methods that support an industry-standard messaging model.
/// This method provides a direct access to the messages in a queue using a synchronous /// This method provides a direct access to the messages in a queue using a synchronous
/// dialogue that is designed for specific types of application where synchronous /// dialogue that is designed for specific types of application where synchronous
/// functionality is more important than performance. /// functionality is more important than performance.
BasicGet { #[derive(Debug, Clone, PartialEq)]
reserved_1: Short, pub struct BasicGet {
pub reserved_1: Short,
/// Specifies the name of the queue to get a message from. /// Specifies the name of the queue to get a message from.
queue: QueueName, pub queue: QueueName,
no_ack: NoAck, pub no_ack: NoAck,
}, }
/// The Basic class provides methods that support an industry-standard messaging model. /// The Basic class provides methods that support an industry-standard messaging model.
/// This method delivers a message to the client following a get method. A message /// This method delivers a message to the client following a get method. A message
/// delivered by 'get-ok' must be acknowledged unless the no-ack option was set in the /// delivered by 'get-ok' must be acknowledged unless the no-ack option was set in the
/// get method. /// get method.
BasicGetOk { #[derive(Debug, Clone, PartialEq)]
delivery_tag: DeliveryTag, pub struct BasicGetOk {
redelivered: Redelivered, pub delivery_tag: DeliveryTag,
pub redelivered: Redelivered,
/// Specifies the name of the exchange that the message was originally published to. /// Specifies the name of the exchange that the message was originally published to.
/// If empty, the message was published to the default exchange. /// If empty, the message was published to the default exchange.
exchange: ExchangeName, pub exchange: ExchangeName,
/// Specifies the routing key name specified when the message was published. /// Specifies the routing key name specified when the message was published.
routing_key: Shortstr, pub routing_key: Shortstr,
message_count: MessageCount, pub message_count: MessageCount,
}, }
/// The Basic class provides methods that support an industry-standard messaging model. /// The Basic class provides methods that support an industry-standard messaging model.
/// This method tells the client that the queue has no messages available for the /// This method tells the client that the queue has no messages available for the
/// client. /// client.
BasicGetEmpty { reserved_1: Shortstr }, #[derive(Debug, Clone, PartialEq)]
pub struct BasicGetEmpty {
pub reserved_1: Shortstr,
}
/// The Basic class provides methods that support an industry-standard messaging model. /// The Basic class provides methods that support an industry-standard messaging model.
/// This method acknowledges one or more messages delivered via the Deliver or Get-Ok /// This method acknowledges one or more messages delivered via the Deliver or Get-Ok
/// methods. The client can ask to confirm a single message or a set of messages up to /// methods. The client can ask to confirm a single message or a set of messages up to
/// and including a specific message. /// and including a specific message.
BasicAck { #[derive(Debug, Clone, PartialEq)]
delivery_tag: DeliveryTag, pub struct BasicAck {
pub delivery_tag: DeliveryTag,
/// If set to 1, the delivery tag is treated as "up to and including", so that the /// If set to 1, the delivery tag is treated as "up to and including", so that the
/// client can acknowledge multiple messages with a single method. If set to zero, /// client can acknowledge multiple messages with a single method. If set to zero,
/// the delivery tag refers to a single message. If the multiple field is 1, and the /// the delivery tag refers to a single message. If the multiple field is 1, and the
/// delivery tag is zero, tells the server to acknowledge all outstanding messages. /// delivery tag is zero, tells the server to acknowledge all outstanding messages.
multiple: Bit, pub multiple: Bit,
}, }
/// The Basic class provides methods that support an industry-standard messaging model. /// The Basic class provides methods that support an industry-standard messaging model.
/// This method allows a client to reject a message. It can be used to interrupt and /// This method allows a client to reject a message. It can be used to interrupt and
/// cancel large incoming messages, or return untreatable messages to their original /// cancel large incoming messages, or return untreatable messages to their original
/// queue. /// queue.
BasicReject { #[derive(Debug, Clone, PartialEq)]
delivery_tag: DeliveryTag, pub struct BasicReject {
pub delivery_tag: DeliveryTag,
/// If requeue is true, the server will attempt to requeue the message. If requeue /// If requeue is true, the server will attempt to requeue the message. If requeue
/// is false or the requeue attempt fails the messages are discarded or dead-lettered. /// is false or the requeue attempt fails the messages are discarded or dead-lettered.
requeue: Bit, pub requeue: Bit,
}, }
/// The Basic class provides methods that support an industry-standard messaging model. /// The Basic class provides methods that support an industry-standard messaging model.
/// This method asks the server to redeliver all unacknowledged messages on a /// This method asks the server to redeliver all unacknowledged messages on a
/// specified channel. Zero or more messages may be redelivered. This method /// specified channel. Zero or more messages may be redelivered. This method
/// is deprecated in favour of the synchronous Recover/Recover-Ok. /// is deprecated in favour of the synchronous Recover/Recover-Ok.
BasicRecoverAsync { #[derive(Debug, Clone, PartialEq)]
pub struct BasicRecoverAsync {
/// If this field is zero, the message will be redelivered to the original /// If this field is zero, the message will be redelivered to the original
/// recipient. If this bit is 1, the server will attempt to requeue the message, /// recipient. If this bit is 1, the server will attempt to requeue the message,
/// potentially then delivering it to an alternative subscriber. /// potentially then delivering it to an alternative subscriber.
requeue: Bit, pub requeue: Bit,
}, }
/// The Basic class provides methods that support an industry-standard messaging model. /// The Basic class provides methods that support an industry-standard messaging model.
/// This method asks the server to redeliver all unacknowledged messages on a /// This method asks the server to redeliver all unacknowledged messages on a
/// specified channel. Zero or more messages may be redelivered. This method /// specified channel. Zero or more messages may be redelivered. This method
/// replaces the asynchronous Recover. /// replaces the asynchronous Recover.
BasicRecover { #[derive(Debug, Clone, PartialEq)]
pub struct BasicRecover {
/// If this field is zero, the message will be redelivered to the original /// If this field is zero, the message will be redelivered to the original
/// recipient. If this bit is 1, the server will attempt to requeue the message, /// recipient. If this bit is 1, the server will attempt to requeue the message,
/// potentially then delivering it to an alternative subscriber. /// potentially then delivering it to an alternative subscriber.
requeue: Bit, pub requeue: Bit,
}, }
/// The Basic class provides methods that support an industry-standard messaging model. /// The Basic class provides methods that support an industry-standard messaging model.
/// This method acknowledges a Basic.Recover method. /// This method acknowledges a Basic.Recover method.
BasicRecoverOk, #[derive(Debug, Clone, PartialEq)]
pub struct BasicRecoverOk;
/// The Tx class allows publish and ack operations to be batched into atomic /// The Tx class allows publish and ack operations to be batched into atomic
/// units of work. The intention is that all publish and ack requests issued /// units of work. The intention is that all publish and ack requests issued
/// within a transaction will complete successfully or none of them will. /// within a transaction will complete successfully or none of them will.
@ -720,7 +879,9 @@ pub enum Method {
/// mandatory flags on Basic.Publish methods is not defined. /// mandatory flags on Basic.Publish methods is not defined.
/// This method sets the channel to use standard transactions. The client must use this /// This method sets the channel to use standard transactions. The client must use this
/// method at least once on a channel before using the Commit or Rollback methods. /// method at least once on a channel before using the Commit or Rollback methods.
TxSelect, #[derive(Debug, Clone, PartialEq)]
pub struct TxSelect;
/// The Tx class allows publish and ack operations to be batched into atomic /// The Tx class allows publish and ack operations to be batched into atomic
/// units of work. The intention is that all publish and ack requests issued /// units of work. The intention is that all publish and ack requests issued
/// within a transaction will complete successfully or none of them will. /// within a transaction will complete successfully or none of them will.
@ -732,7 +893,9 @@ pub enum Method {
/// mandatory flags on Basic.Publish methods is not defined. /// mandatory flags on Basic.Publish methods is not defined.
/// This method confirms to the client that the channel was successfully set to use /// This method confirms to the client that the channel was successfully set to use
/// standard transactions. /// standard transactions.
TxSelectOk, #[derive(Debug, Clone, PartialEq)]
pub struct TxSelectOk;
/// The Tx class allows publish and ack operations to be batched into atomic /// The Tx class allows publish and ack operations to be batched into atomic
/// units of work. The intention is that all publish and ack requests issued /// units of work. The intention is that all publish and ack requests issued
/// within a transaction will complete successfully or none of them will. /// within a transaction will complete successfully or none of them will.
@ -744,7 +907,9 @@ pub enum Method {
/// mandatory flags on Basic.Publish methods is not defined. /// mandatory flags on Basic.Publish methods is not defined.
/// This method commits all message publications and acknowledgments performed in /// This method commits all message publications and acknowledgments performed in
/// the current transaction. A new transaction starts immediately after a commit. /// the current transaction. A new transaction starts immediately after a commit.
TxCommit, #[derive(Debug, Clone, PartialEq)]
pub struct TxCommit;
/// The Tx class allows publish and ack operations to be batched into atomic /// The Tx class allows publish and ack operations to be batched into atomic
/// units of work. The intention is that all publish and ack requests issued /// units of work. The intention is that all publish and ack requests issued
/// within a transaction will complete successfully or none of them will. /// within a transaction will complete successfully or none of them will.
@ -756,7 +921,9 @@ pub enum Method {
/// mandatory flags on Basic.Publish methods is not defined. /// mandatory flags on Basic.Publish methods is not defined.
/// This method confirms to the client that the commit succeeded. Note that if a commit /// This method confirms to the client that the commit succeeded. Note that if a commit
/// fails, the server raises a channel exception. /// fails, the server raises a channel exception.
TxCommitOk, #[derive(Debug, Clone, PartialEq)]
pub struct TxCommitOk;
/// The Tx class allows publish and ack operations to be batched into atomic /// The Tx class allows publish and ack operations to be batched into atomic
/// units of work. The intention is that all publish and ack requests issued /// units of work. The intention is that all publish and ack requests issued
/// within a transaction will complete successfully or none of them will. /// within a transaction will complete successfully or none of them will.
@ -770,7 +937,9 @@ pub enum Method {
/// the current transaction. A new transaction starts immediately after a rollback. /// the current transaction. A new transaction starts immediately after a rollback.
/// Note that unacked messages will not be automatically redelivered by rollback; /// Note that unacked messages will not be automatically redelivered by rollback;
/// if that is required an explicit recover call should be issued. /// if that is required an explicit recover call should be issued.
TxRollback, #[derive(Debug, Clone, PartialEq)]
pub struct TxRollback;
/// The Tx class allows publish and ack operations to be batched into atomic /// The Tx class allows publish and ack operations to be batched into atomic
/// units of work. The intention is that all publish and ack requests issued /// units of work. The intention is that all publish and ack requests issued
/// within a transaction will complete successfully or none of them will. /// within a transaction will complete successfully or none of them will.
@ -782,5 +951,5 @@ pub enum Method {
/// mandatory flags on Basic.Publish methods is not defined. /// mandatory flags on Basic.Publish methods is not defined.
/// This method confirms to the client that the rollback succeeded. Note that if an /// This method confirms to the client that the rollback succeeded. Note that if an
/// rollback fails, the server raises a channel exception. /// rollback fails, the server raises a channel exception.
TxRollbackOk, #[derive(Debug, Clone, PartialEq)]
} pub struct TxRollbackOk;

34
amqp_core/src/queue.rs Normal file
View file

@ -0,0 +1,34 @@
use crate::message::Message;
use crate::{newtype, newtype_id, ChannelId};
use parking_lot::Mutex;
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;
pub type Queue = Arc<RawQueue>;
newtype_id!(pub QueueId);
newtype!(
/// The name of a queue. A newtype wrapper around `Arc<str>`, which guarantees cheap clones.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub QueueName: Arc<str>
);
#[derive(Debug)]
pub struct RawQueue {
pub id: QueueId,
pub name: QueueName,
pub messages: Mutex<Vec<Message>>, // use a concurrent linked list???
pub durable: bool,
pub exclusive: Option<ChannelId>,
/// Whether the queue will automatically be deleted when no consumers uses it anymore.
/// The queue can always be manually deleted.
/// If auto-delete is enabled, it keeps track of the consumer count.
pub deletion: QueueDeletion,
}
#[derive(Debug)]
pub enum QueueDeletion {
Auto(AtomicUsize),
Manual,
}

View file

@ -14,6 +14,8 @@
<h1>AMQP Data</h1> <h1>AMQP Data</h1>
<h2>Connections</h2> <h2>Connections</h2>
<div id="connection-wrapper"></div> <div id="connection-wrapper"></div>
<h2>Queues</h2>
<div id="queue-wrapper"></div>
<script src="script.js"></script> <script src="script.js"></script>
</body> </body>

View file

@ -39,10 +39,23 @@ const renderConnections = (connections) => {
wrapper.replaceChildren(table); wrapper.replaceChildren(table);
}; };
const renderQueues = (queues) => {
const wrapper = document.getElementById('queue-wrapper');
const table = renderTable(
['Queue ID', 'Name', 'Durable'],
queues.map((queue) => {
return [queue.id, queue.name, queue.durable ? 'Yes' : 'No'];
})
);
wrapper.replaceChildren(table);
};
const refresh = async () => { const refresh = async () => {
const fetched = await fetch('api/data'); const fetched = await fetch('api/data');
const data = await fetched.json(); const data = await fetched.json();
renderConnections(data.connections); renderConnections(data.connections);
renderQueues(data.queues);
}; };
setInterval(refresh, 1000); setInterval(refresh, 1000);

View file

@ -51,6 +51,7 @@ async fn get_style_css() -> Response {
#[derive(Serialize)] #[derive(Serialize)]
struct Data { struct Data {
connections: Vec<Connection>, connections: Vec<Connection>,
queues: Vec<Queue>,
} }
#[derive(Serialize)] #[derive(Serialize)]
@ -66,6 +67,13 @@ struct Channel {
number: u16, number: u16,
} }
#[derive(Serialize)]
struct Queue {
id: String,
name: String,
durable: bool,
}
async fn get_data(global_data: GlobalData) -> impl IntoResponse { async fn get_data(global_data: GlobalData) -> impl IntoResponse {
let global_data = global_data.lock(); let global_data = global_data.lock();
@ -92,7 +100,20 @@ async fn get_data(global_data: GlobalData) -> impl IntoResponse {
}) })
.collect(); .collect();
let data = Data { connections }; let queues = global_data
.queues
.values()
.map(|queue| Queue {
id: queue.id.to_string(),
name: queue.name.to_string(),
durable: queue.durable,
})
.collect();
let data = Data {
connections,
queues,
};
Json(data) Json(data)
} }

View file

@ -7,5 +7,6 @@ edition = "2021"
[dependencies] [dependencies]
amqp_core = { path = "../amqp_core" } amqp_core = { path = "../amqp_core" }
parking_lot = "0.12.0"
tracing = "0.1.31" tracing = "0.1.31"
tokio = { version = "1.17.0", features = ["full"] } tokio = { version = "1.17.0", features = ["full"] }

View file

@ -1,10 +0,0 @@
use amqp_core::methods::Method;
use amqp_core::ChannelHandle;
use std::time::Duration;
use tokio::time;
use tracing::debug;
pub async fn handle_method(_channel_handle: ChannelHandle, _method: Method) {
debug!("handling method or something in that cool new future");
time::sleep(Duration::from_secs(10)).await;
}

View file

@ -0,0 +1,13 @@
use amqp_core::amqp_todo;
use amqp_core::connection::ChannelHandle;
use amqp_core::error::ProtocolError;
use amqp_core::methods::{BasicConsume, Method};
pub async fn consume(
channel_handle: ChannelHandle,
_basic_consume: BasicConsume,
) -> Result<Method, ProtocolError> {
let _channel = channel_handle.lock();
amqp_todo!()
}

View file

@ -0,0 +1,70 @@
mod consume;
mod queue;
use amqp_core::amqp_todo;
use amqp_core::connection::ChannelHandle;
use amqp_core::error::ProtocolError;
use amqp_core::message::Message;
use amqp_core::methods::Method;
use tracing::info;
pub async fn handle_basic_publish(_channel_handle: ChannelHandle, message: Message) {
info!(
?message,
"Someone has summoned the almighty Basic.Publish handler"
);
}
pub async fn handle_method(
channel_handle: ChannelHandle,
method: Method,
) -> Result<Method, ProtocolError> {
info!(?method, "Handling method");
let response = match method {
Method::ExchangeDeclare(_) => amqp_todo!(),
Method::ExchangeDeclareOk(_) => amqp_todo!(),
Method::ExchangeDelete(_) => amqp_todo!(),
Method::ExchangeDeleteOk(_) => amqp_todo!(),
Method::QueueDeclare(queue_declare) => {
queue::declare(channel_handle, queue_declare).await?
}
Method::QueueDeclareOk { .. } => amqp_todo!(),
Method::QueueBind(queue_bind) => queue::bind(channel_handle, queue_bind).await?,
Method::QueueBindOk(_) => amqp_todo!(),
Method::QueueUnbind { .. } => amqp_todo!(),
Method::QueueUnbindOk(_) => amqp_todo!(),
Method::QueuePurge { .. } => amqp_todo!(),
Method::QueuePurgeOk { .. } => amqp_todo!(),
Method::QueueDelete { .. } => amqp_todo!(),
Method::QueueDeleteOk { .. } => amqp_todo!(),
Method::BasicQos { .. } => amqp_todo!(),
Method::BasicQosOk(_) => amqp_todo!(),
Method::BasicConsume(consume) => consume::consume(channel_handle, consume).await?,
Method::BasicConsumeOk { .. } => amqp_todo!(),
Method::BasicCancel { .. } => amqp_todo!(),
Method::BasicCancelOk { .. } => amqp_todo!(),
Method::BasicReturn { .. } => amqp_todo!(),
Method::BasicDeliver { .. } => amqp_todo!(),
Method::BasicGet { .. } => amqp_todo!(),
Method::BasicGetOk { .. } => amqp_todo!(),
Method::BasicGetEmpty { .. } => amqp_todo!(),
Method::BasicAck { .. } => amqp_todo!(),
Method::BasicReject { .. } => amqp_todo!(),
Method::BasicRecoverAsync { .. } => amqp_todo!(),
Method::BasicRecover { .. } => amqp_todo!(),
Method::BasicRecoverOk(_) => amqp_todo!(),
Method::TxSelect(_)
| Method::TxSelectOk(_)
| Method::TxCommit(_)
| Method::TxCommitOk(_)
| Method::TxRollback(_)
| Method::TxRollbackOk(_) => amqp_todo!(),
Method::BasicPublish { .. } => {
unreachable!("Basic.Publish is handled somewhere else because it has a body")
}
_ => unreachable!("Method handled by transport layer"),
};
Ok(response)
}

View file

@ -0,0 +1,95 @@
use amqp_core::connection::ChannelHandle;
use amqp_core::error::{ConException, ProtocolError};
use amqp_core::methods::{Method, QueueBind, QueueDeclare, QueueDeclareOk};
use amqp_core::queue::{QueueDeletion, QueueId, QueueName, RawQueue};
use amqp_core::{amqp_todo, GlobalData};
use parking_lot::Mutex;
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;
pub async fn declare(
channel_handle: ChannelHandle,
queue_declare: QueueDeclare,
) -> Result<Method, ProtocolError> {
let QueueDeclare {
queue: queue_name,
passive,
durable,
exclusive,
auto_delete,
no_wait,
arguments,
..
} = queue_declare;
let queue_name = QueueName::new(queue_name.into());
if !arguments.is_empty() {
amqp_todo!();
}
if passive || no_wait || durable {
amqp_todo!();
}
let global_data = {
let channel = channel_handle.lock();
let global_data = channel.global_data.clone();
let id = QueueId::random();
let queue = Arc::new(RawQueue {
id,
name: queue_name.clone(),
messages: Mutex::default(),
durable,
exclusive: exclusive.then(|| channel.id),
deletion: if auto_delete {
QueueDeletion::Auto(AtomicUsize::default())
} else {
QueueDeletion::Manual
},
});
{
let mut global_data_lock = global_data.lock();
global_data_lock.queues.insert(queue_name.clone(), queue);
}
global_data
};
bind_queue(global_data, (), queue_name.clone().into_inner()).await?;
Ok(Method::QueueDeclareOk(QueueDeclareOk {
queue: queue_name.to_string(),
message_count: 0,
consumer_count: 0,
}))
}
pub async fn bind(
_channel_handle: ChannelHandle,
_queue_bind: QueueBind,
) -> Result<Method, ProtocolError> {
amqp_todo!();
}
async fn bind_queue(
global_data: GlobalData,
_exchange: (),
routing_key: Arc<str>,
) -> Result<(), ProtocolError> {
let mut global_data = global_data.lock();
// todo: don't
let queue = global_data
.queues
.get(&QueueName::new(routing_key.clone()))
.unwrap()
.clone();
global_data
.default_exchange
.insert(routing_key.to_string(), queue);
Ok(())
}

View file

@ -18,7 +18,6 @@ smallvec = { version = "1.8.0", features = ["union"] }
thiserror = "1.0.30" thiserror = "1.0.30"
tokio = { version = "1.16.1", features = ["full"] } tokio = { version = "1.16.1", features = ["full"] }
tracing = "0.1.30" tracing = "0.1.30"
uuid = "0.8.2"
[features] [features]

View file

@ -1,30 +1,33 @@
use crate::error::{ConException, ProtocolError, Result};
use crate::frame::{ContentHeader, Frame, FrameType};
use crate::{frame, methods, sasl};
use amqp_core::connection::{ChannelHandle, ChannelNum, ConnectionHandle, ConnectionId};
use amqp_core::message::{MessageId, RawMessage, RoutingInformation};
use amqp_core::methods::{
BasicPublish, ChannelClose, ChannelCloseOk, ChannelOpenOk, ConnectionClose, ConnectionCloseOk,
ConnectionOpen, ConnectionOpenOk, ConnectionStart, ConnectionStartOk, ConnectionTune,
ConnectionTuneOk, FieldValue, Method, Table,
};
use amqp_core::GlobalData;
use anyhow::Context;
use bytes::Bytes;
use smallvec::SmallVec;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::collections::HashMap; use std::collections::HashMap;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use anyhow::Context;
use bytes::Bytes;
use smallvec::SmallVec;
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream; use tokio::net::TcpStream;
use tokio::time; use tokio::time;
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, warn};
use uuid::Uuid;
use amqp_core::methods::{FieldValue, Method, Table};
use amqp_core::GlobalData;
use crate::error::{ConException, ProtocolError, Result};
use crate::frame::{ContentHeader, Frame, FrameType};
use crate::{frame, methods, sasl};
fn ensure_conn(condition: bool) -> Result<()> { fn ensure_conn(condition: bool) -> Result<()> {
if condition { if condition {
Ok(()) Ok(())
} else { } else {
Err(ConException::Todo.into_trans()) Err(ConException::Todo.into())
} }
} }
@ -33,37 +36,48 @@ const CHANNEL_MAX: u16 = 0;
const FRAME_SIZE_MAX: u32 = 0; const FRAME_SIZE_MAX: u32 = 0;
const HEARTBEAT_DELAY: u16 = 0; const HEARTBEAT_DELAY: u16 = 0;
#[allow(dead_code)] const BASIC_CLASS_ID: u16 = 60;
pub struct Channel { pub struct Channel {
num: u16, /// A handle to the global channel representation. Used to remove the channel when it's dropped
channel_handle: amqp_core::ChannelHandle, handle: ChannelHandle,
/// The current status of the channel, whether it has sent a method that expects a body
status: ChannelStatus,
} }
pub struct Connection { pub struct Connection {
id: Uuid, id: ConnectionId,
stream: TcpStream, stream: TcpStream,
max_frame_size: usize, max_frame_size: usize,
heartbeat_delay: u16, heartbeat_delay: u16,
channel_max: u16, channel_max: u16,
/// When the next heartbeat expires
next_timeout: Pin<Box<time::Sleep>>, next_timeout: Pin<Box<time::Sleep>>,
channels: HashMap<u16, Channel>, channels: HashMap<ChannelNum, Channel>,
connection_handle: amqp_core::ConnectionHandle, handle: ConnectionHandle,
global_data: GlobalData, global_data: GlobalData,
} }
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30); const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
enum WaitForBodyStatus { enum ChannelStatus {
Method(Method), Default,
Header(Method, ContentHeader, SmallVec<[Bytes; 1]>), /// ClassId // todo: newtype it
None, NeedHeader(u16, Box<Method>),
NeedsBody(Box<Method>, Box<ContentHeader>, SmallVec<[Bytes; 1]>),
}
impl ChannelStatus {
fn take(&mut self) -> Self {
std::mem::replace(self, Self::Default)
}
} }
impl Connection { impl Connection {
pub fn new( pub fn new(
id: Uuid, id: ConnectionId,
stream: TcpStream, stream: TcpStream,
connection_handle: amqp_core::ConnectionHandle, connection_handle: ConnectionHandle,
global_data: GlobalData, global_data: GlobalData,
) -> Self { ) -> Self {
Self { Self {
@ -73,8 +87,8 @@ impl Connection {
heartbeat_delay: HEARTBEAT_DELAY, heartbeat_delay: HEARTBEAT_DELAY,
channel_max: CHANNEL_MAX, channel_max: CHANNEL_MAX,
next_timeout: Box::pin(time::sleep(DEFAULT_TIMEOUT)), next_timeout: Box::pin(time::sleep(DEFAULT_TIMEOUT)),
connection_handle, handle: connection_handle,
channels: HashMap::new(), channels: HashMap::with_capacity(4),
global_data, global_data,
} }
} }
@ -85,7 +99,7 @@ impl Connection {
Err(err) => error!(%err, "Error during processing of connection"), Err(err) => error!(%err, "Error during processing of connection"),
} }
let connection_handle = self.connection_handle.lock(); let connection_handle = self.handle.lock();
connection_handle.close(); connection_handle.close();
} }
@ -100,7 +114,7 @@ impl Connection {
self.main_loop().await self.main_loop().await
} }
async fn send_method(&mut self, channel: u16, method: Method) -> Result<()> { async fn send_method(&mut self, channel: ChannelNum, method: Method) -> Result<()> {
let mut payload = Vec::with_capacity(64); let mut payload = Vec::with_capacity(64);
methods::write::write_method(method, &mut payload)?; methods::write::write_method(method, &mut payload)?;
frame::write_frame( frame::write_frame(
@ -124,7 +138,7 @@ impl Connection {
} }
async fn start(&mut self) -> Result<()> { async fn start(&mut self) -> Result<()> {
let start_method = Method::ConnectionStart { let start_method = Method::ConnectionStart(ConnectionStart {
version_major: 0, version_major: 0,
version_minor: 9, version_minor: 9,
server_properties: server_properties( server_properties: server_properties(
@ -134,50 +148,50 @@ impl Connection {
), ),
mechanisms: "PLAIN".into(), mechanisms: "PLAIN".into(),
locales: "en_US".into(), locales: "en_US".into(),
}; });
debug!(?start_method, "Sending Start method"); debug!(?start_method, "Sending Start method");
self.send_method(0, start_method).await?; self.send_method(ChannelNum::zero(), start_method).await?;
let start_ok = self.recv_method().await?; let start_ok = self.recv_method().await?;
debug!(?start_ok, "Received Start-Ok"); debug!(?start_ok, "Received Start-Ok");
if let Method::ConnectionStartOk { if let Method::ConnectionStartOk(ConnectionStartOk {
mechanism, mechanism,
locale, locale,
response, response,
.. ..
} = start_ok }) = start_ok
{ {
ensure_conn(mechanism == "PLAIN")?; ensure_conn(mechanism == "PLAIN")?;
ensure_conn(locale == "en_US")?; ensure_conn(locale == "en_US")?;
let plain_user = sasl::parse_sasl_plain_response(&response)?; let plain_user = sasl::parse_sasl_plain_response(&response)?;
info!(username = %plain_user.authentication_identity, "SASL Authentication successful") info!(username = %plain_user.authentication_identity, "SASL Authentication successful");
} else { } else {
return Err(ConException::Todo.into_trans()); return Err(ConException::Todo.into());
} }
Ok(()) Ok(())
} }
async fn tune(&mut self) -> Result<()> { async fn tune(&mut self) -> Result<()> {
let tune_method = Method::ConnectionTune { let tune_method = Method::ConnectionTune(ConnectionTune {
channel_max: CHANNEL_MAX, channel_max: CHANNEL_MAX,
frame_max: FRAME_SIZE_MAX, frame_max: FRAME_SIZE_MAX,
heartbeat: HEARTBEAT_DELAY, heartbeat: HEARTBEAT_DELAY,
}; });
debug!("Sending Tune method"); debug!("Sending Tune method");
self.send_method(0, tune_method).await?; self.send_method(ChannelNum::zero(), tune_method).await?;
let tune_ok = self.recv_method().await?; let tune_ok = self.recv_method().await?;
debug!(?tune_ok, "Received Tune-Ok method"); debug!(?tune_ok, "Received Tune-Ok method");
if let Method::ConnectionTuneOk { if let Method::ConnectionTuneOk(ConnectionTuneOk {
channel_max, channel_max,
frame_max, frame_max,
heartbeat, heartbeat,
} = tune_ok }) = tune_ok
{ {
self.channel_max = channel_max; self.channel_max = channel_max;
self.max_frame_size = usize::try_from(frame_max).unwrap(); self.max_frame_size = usize::try_from(frame_max).unwrap();
@ -192,15 +206,15 @@ impl Connection {
let open = self.recv_method().await?; let open = self.recv_method().await?;
debug!(?open, "Received Open method"); debug!(?open, "Received Open method");
if let Method::ConnectionOpen { virtual_host, .. } = open { if let Method::ConnectionOpen(ConnectionOpen { virtual_host, .. }) = open {
ensure_conn(virtual_host == "/")?; ensure_conn(virtual_host == "/")?;
} }
self.send_method( self.send_method(
0, ChannelNum::zero(),
Method::ConnectionOpenOk { Method::ConnectionOpenOk(ConnectionOpenOk {
reserved_1: "".to_string(), reserved_1: "".to_string(),
}, }),
) )
.await?; .await?;
@ -208,33 +222,109 @@ impl Connection {
} }
async fn main_loop(&mut self) -> Result<()> { async fn main_loop(&mut self) -> Result<()> {
// todo: find out how header/body frames can interleave between channels
let mut wait_for_body = WaitForBodyStatus::None;
loop { loop {
debug!("Waiting for next frame");
let frame = frame::read_frame(&mut self.stream, self.max_frame_size).await?; let frame = frame::read_frame(&mut self.stream, self.max_frame_size).await?;
self.reset_timeout(); self.reset_timeout();
match frame.kind { match frame.kind {
FrameType::Method => wait_for_body = self.dispatch_method(frame).await?, FrameType::Method => self.dispatch_method(frame).await?,
FrameType::Heartbeat => {} FrameType::Heartbeat => { /* Nothing here, just the `reset_timeout` above */ }
FrameType::Header => match wait_for_body { FrameType::Header => self.dispatch_header(frame)?,
WaitForBodyStatus::None => warn!(channel = %frame.channel, "unexpected header"), FrameType::Body => self.dispatch_body(frame)?,
WaitForBodyStatus::Method(method) => {
wait_for_body =
WaitForBodyStatus::Header(method, ContentHeader::new(), SmallVec::new())
} }
WaitForBodyStatus::Header(_, _, _) => {
warn!(channel = %frame.channel, "already got header")
} }
}
async fn dispatch_method(&mut self, frame: Frame) -> Result<()> {
let method = methods::parse_method(&frame.payload)?;
debug!(?method, "Received method");
// Sending a method implicitly cancels the content frames that might be ongoing
self.channels
.get_mut(&frame.channel)
.map(|channel| channel.status.take());
match method {
Method::ConnectionClose(ConnectionClose {
reply_code,
reply_text,
class_id,
method_id,
}) => {
info!(%reply_code, %reply_text, %class_id, %method_id, "Closing connection");
self.send_method(
ChannelNum::zero(),
Method::ConnectionCloseOk(ConnectionCloseOk),
)
.await?;
return Err(ProtocolError::GracefulClose.into());
}
Method::ChannelOpen { .. } => self.channel_open(frame.channel).await?,
Method::ChannelClose { .. } => self.channel_close(frame.channel, method).await?,
Method::BasicPublish { .. } => match self.channels.get_mut(&frame.channel) {
Some(channel) => {
channel.status = ChannelStatus::NeedHeader(BASIC_CLASS_ID, Box::new(method));
}
None => return Err(ConException::Todo.into()),
}, },
FrameType::Body => match &mut wait_for_body { _ => {
WaitForBodyStatus::None => warn!(channel = %frame.channel, "unexpected body"), let channel_handle = self
WaitForBodyStatus::Method(_) => { .channels
warn!(channel = %frame.channel, "unexpected body") .get(&frame.channel)
.ok_or(ConException::Todo)?
.handle
.clone();
// call into amqp_messaging to handle the method
// it returns the response method that we are supposed to send
// maybe this might become an `Option` in the future
let return_method =
amqp_messaging::methods::handle_method(channel_handle, method).await?;
self.send_method(frame.channel, return_method).await?;
} }
WaitForBodyStatus::Header(_, header, vec) => { }
Ok(())
}
fn dispatch_header(&mut self, frame: Frame) -> Result<()> {
self.channels
.get_mut(&frame.channel)
.ok_or_else(|| ConException::Todo.into())
.and_then(|channel| match channel.status.take() {
ChannelStatus::Default => {
warn!(channel = %frame.channel, "unexpected header");
Err(ConException::UnexpectedFrame.into())
}
ChannelStatus::NeedHeader(class_id, method) => {
let header = ContentHeader::parse(&frame.payload)?;
ensure_conn(header.class_id == class_id)?;
channel.status = ChannelStatus::NeedsBody(method, header, SmallVec::new());
Ok(())
}
ChannelStatus::NeedsBody(_, _, _) => {
warn!(channel = %frame.channel, "already got header");
Err(ConException::UnexpectedFrame.into())
}
})
}
fn dispatch_body(&mut self, frame: Frame) -> Result<()> {
let channel = self
.channels
.get_mut(&frame.channel)
.ok_or(ConException::Todo)?;
match channel.status.take() {
ChannelStatus::Default => {
warn!(channel = %frame.channel, "unexpected body");
Err(ConException::UnexpectedFrame.into())
}
ChannelStatus::NeedHeader(_, _) => {
warn!(channel = %frame.channel, "unexpected body");
Err(ConException::UnexpectedFrame.into())
}
ChannelStatus::NeedsBody(method, header, mut vec) => {
vec.push(frame.payload); vec.push(frame.payload);
match vec match vec
.iter() .iter()
@ -242,71 +332,79 @@ impl Connection {
.sum::<usize>() .sum::<usize>()
.cmp(&usize::try_from(header.body_size).unwrap()) .cmp(&usize::try_from(header.body_size).unwrap())
{ {
Ordering::Equal => todo!("process body"), Ordering::Equal => {
Ordering::Greater => todo!("too much data!"), self.process_method_with_body(*method, *header, vec, frame.channel)
Ordering::Less => {} // wait for next body }
Ordering::Greater => Err(ConException::Todo.into()),
Ordering::Less => Ok(()), // wait for next body
} }
} }
}
}
fn process_method_with_body(
&mut self,
method: Method,
header: ContentHeader,
payloads: SmallVec<[Bytes; 1]>,
channel: ChannelNum,
) -> Result<()> {
// The only method with content that is sent to the server is Basic.Publish.
ensure_conn(header.class_id == BASIC_CLASS_ID)?;
if let Method::BasicPublish(BasicPublish {
exchange,
routing_key,
mandatory,
immediate,
..
}) = method
{
let message = RawMessage {
id: MessageId::random(),
properties: header.property_fields,
routing: RoutingInformation {
exchange,
routing_key,
mandatory,
immediate,
}, },
} content: payloads,
} };
} let message = Arc::new(message);
async fn dispatch_method(&mut self, frame: Frame) -> Result<WaitForBodyStatus> { let channel = self.channels.get(&channel).ok_or(ConException::Todo)?;
let method = methods::parse_method(&frame.payload)?;
debug!(?method, "Received method");
match method { // Spawn the handler for the publish. The connection task goes back to handling
Method::ConnectionClose { // just the connection.
reply_code, tokio::spawn(amqp_messaging::methods::handle_basic_publish(
reply_text, channel.handle.clone(),
class_id, message,
method_id,
} => {
info!(%reply_code, %reply_text, %class_id, %method_id, "Closing connection");
self.send_method(0, Method::ConnectionCloseOk {}).await?;
return Err(ProtocolError::GracefulClose.into());
}
Method::ChannelOpen { .. } => self.channel_open(frame.channel).await?,
Method::ChannelClose { .. } => self.channel_close(frame.channel, method).await?,
Method::BasicPublish { .. } => return Ok(WaitForBodyStatus::Method(method)),
_ => {
let channel_handle = self
.channels
.get(&frame.channel)
.ok_or_else(|| ConException::Todo.into_trans())?
.channel_handle
.clone();
tokio::spawn(amqp_messaging::methods::handle_method(
channel_handle,
method,
)); ));
// we don't handle this here, forward it to *somewhere* Ok(())
} else {
Err(ConException::Todo.into())
} }
} }
Ok(WaitForBodyStatus::None) async fn channel_open(&mut self, channel_num: ChannelNum) -> Result<()> {
} let id = rand::random();
let channel_handle = amqp_core::connection::Channel::new_handle(
async fn channel_open(&mut self, num: u16) -> Result<()> {
let id = Uuid::from_bytes(rand::random());
let channel_handle = amqp_core::Channel::new_handle(
id, id,
num, channel_num.num(),
self.connection_handle.clone(), self.handle.clone(),
self.global_data.clone(), self.global_data.clone(),
); );
let channel = Channel { let channel = Channel {
num, handle: channel_handle.clone(),
channel_handle: channel_handle.clone(), status: ChannelStatus::Default,
}; };
let prev = self.channels.insert(num, channel); let prev = self.channels.insert(channel_num, channel);
if let Some(prev) = prev { if let Some(prev) = prev {
self.channels.insert(num, prev); // restore previous state self.channels.insert(channel_num, prev); // restore previous state
return Err(ConException::ChannelError.into_trans()); return Err(ConException::ChannelError.into());
} }
{ {
@ -318,36 +416,37 @@ impl Connection {
.unwrap() .unwrap()
.lock() .lock()
.channels .channels
.insert(num, channel_handle); .insert(channel_num.num(), channel_handle);
} }
info!(%num, "Opened new channel"); info!(%channel_num, "Opened new channel");
self.send_method( self.send_method(
num, channel_num,
Method::ChannelOpenOk { Method::ChannelOpenOk(ChannelOpenOk {
reserved_1: Vec::new(), reserved_1: Vec::new(),
}, }),
) )
.await?; .await?;
Ok(()) Ok(())
} }
async fn channel_close(&mut self, num: u16, method: Method) -> Result<()> { async fn channel_close(&mut self, channel_id: ChannelNum, method: Method) -> Result<()> {
if let Method::ChannelClose { if let Method::ChannelClose(ChannelClose {
reply_code: code, reply_code: code,
reply_text: reason, reply_text: reason,
.. ..
} = method }) = method
{ {
info!(%code, %reason, "Closing channel"); info!(%code, %reason, "Closing channel");
if let Some(channel) = self.channels.remove(&num) { if let Some(channel) = self.channels.remove(&channel_id) {
drop(channel); drop(channel);
self.send_method(num, Method::ChannelCloseOk).await?; self.send_method(channel_id, Method::ChannelCloseOk(ChannelCloseOk))
.await?;
} else { } else {
return Err(ConException::Todo.into_trans()); return Err(ConException::Todo.into());
} }
} else { } else {
unreachable!() unreachable!()
@ -357,7 +456,7 @@ impl Connection {
fn reset_timeout(&mut self) { fn reset_timeout(&mut self) {
if self.heartbeat_delay != 0 { if self.heartbeat_delay != 0 {
let next = Duration::from_secs(u64::from(self.heartbeat_delay)); let next = Duration::from_secs(u64::from(self.heartbeat_delay / 2));
self.next_timeout = Box::pin(time::sleep(next)); self.next_timeout = Box::pin(time::sleep(next));
} }
} }
@ -396,13 +495,13 @@ impl Connection {
impl Drop for Connection { impl Drop for Connection {
fn drop(&mut self) { fn drop(&mut self) {
self.connection_handle.lock().close(); self.handle.lock().close();
} }
} }
impl Drop for Channel { impl Drop for Channel {
fn drop(&mut self) { fn drop(&mut self) {
self.channel_handle.lock().close(); self.handle.lock().close();
} }
} }

View file

@ -1,15 +1,15 @@
#![allow(dead_code)]
use std::io::Error; use std::io::Error;
pub type StdResult<T, E> = std::result::Result<T, E>; pub use amqp_core::error::{ConException, ProtocolError};
type StdResult<T, E> = std::result::Result<T, E>;
pub type Result<T> = StdResult<T, TransError>; pub type Result<T> = StdResult<T, TransError>;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum TransError { pub enum TransError {
#[error("{0}")] #[error("{0}")]
Invalid(#[from] ProtocolError), Protocol(#[from] ProtocolError),
#[error("connection error: `{0}`")] #[error("connection error: `{0}`")]
Other(#[from] anyhow::Error), Other(#[from] anyhow::Error),
} }
@ -20,40 +20,8 @@ impl From<std::io::Error> for TransError {
} }
} }
#[derive(Debug, thiserror::Error)] impl From<amqp_core::error::ConException> for TransError {
pub enum ProtocolError { fn from(err: ConException) -> Self {
#[error("fatal error")] Self::Protocol(ProtocolError::ConException(err))
Fatal,
#[error("{0}")]
ConException(#[from] ConException),
#[error("{0}")]
ChannelException(#[from] ChannelException),
#[error("Connection must be closed")]
CloseNow,
#[error("Graceful connection closing requested")]
GracefulClose,
}
#[derive(Debug, thiserror::Error)]
pub enum ConException {
#[error("501 Frame error")]
FrameError,
#[error("503 Command invalid")]
CommandInvalid,
#[error("503 Syntax error | {0:?}")]
/// A method was received but there was a syntax error. The string stores where it occurred.
SyntaxError(Vec<String>),
#[error("504 Channel error")]
ChannelError,
#[error("xxx Not decided yet")]
Todo,
}
impl ConException {
pub fn into_trans(self) -> TransError {
TransError::Invalid(ProtocolError::ConException(self))
} }
} }
#[derive(Debug, thiserror::Error)]
pub enum ChannelException {}

View file

@ -1,8 +1,8 @@
use crate::error::{ConException, ProtocolError, Result}; use crate::error::{ConException, ProtocolError, Result};
use amqp_core::methods::FieldValue; use amqp_core::connection::ChannelNum;
use amqp_core::methods;
use anyhow::Context; use anyhow::Context;
use bytes::Bytes; use bytes::Bytes;
use smallvec::SmallVec;
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tracing::trace; use tracing::trace;
@ -19,7 +19,7 @@ mod frame_type {
pub struct Frame { pub struct Frame {
/// The type of the frame including its parsed metadata. /// The type of the frame including its parsed metadata.
pub kind: FrameType, pub kind: FrameType,
pub channel: u16, pub channel: ChannelNum,
/// Includes the whole payload, also including the metadata from each type. /// Includes the whole payload, also including the metadata from each type.
pub payload: Bytes, pub payload: Bytes,
} }
@ -38,24 +38,112 @@ pub struct ContentHeader {
pub class_id: u16, pub class_id: u16,
pub weight: u16, pub weight: u16,
pub body_size: u64, pub body_size: u64,
pub property_flags: SmallVec<[u16; 1]>, pub property_fields: methods::Table,
pub property_fields: Vec<FieldValue>, }
mod content_header_parse {
use crate::error::TransError;
use crate::frame::ContentHeader;
use crate::methods::parse_helper::{octet, shortstr, table, timestamp};
use amqp_core::methods;
use amqp_core::methods::FieldValue::{FieldTable, ShortShortUInt, ShortString, Timestamp};
use nom::number::complete::{u16, u64};
use nom::number::Endianness::Big;
type IResult<'a, T> = nom::IResult<&'a [u8], T, TransError>;
pub fn basic_properties(flags: u16, input: &[u8]) -> IResult<'_, methods::Table> {
macro_rules! parse_property {
(if $flags:ident >> $n:literal, $parser:ident($input:ident)?, $map:ident.insert($name:expr, $ctor:path)) => {
if (($flags >> $n) & 1) == 1 {
let (input, value) = $parser($input)?;
$map.insert(String::from($name), $ctor(value));
input
} else {
$input
}
};
}
let mut map = methods::Table::new();
let input = parse_property!(if flags >> 15, shortstr(input)?, map.insert("content-type", ShortString));
let input = parse_property!(if flags >> 14, shortstr(input)?, map.insert("content-encoding", ShortString));
let input =
parse_property!(if flags >> 13, table(input)?, map.insert("headers", FieldTable));
let input = parse_property!(if flags >> 12, octet(input)?, map.insert("delivery-mode", ShortShortUInt));
let input =
parse_property!(if flags >> 11, octet(input)?, map.insert("priority", ShortShortUInt));
let input = parse_property!(if flags >> 10, shortstr(input)?, map.insert("correlation-id", ShortString));
let input =
parse_property!(if flags >> 9, shortstr(input)?, map.insert("reply-to", ShortString));
let input =
parse_property!(if flags >> 8, shortstr(input)?, map.insert("expiration", ShortString));
let input =
parse_property!(if flags >> 7, shortstr(input)?, map.insert("message-id", ShortString));
let input =
parse_property!(if flags >> 6, timestamp(input)?, map.insert("timestamp", Timestamp));
let input =
parse_property!(if flags >> 5, shortstr(input)?, map.insert("type", ShortString));
let input =
parse_property!(if flags >> 4, shortstr(input)?, map.insert("user-id", ShortString));
let input =
parse_property!(if flags >> 3, shortstr(input)?, map.insert("app-id", ShortString));
let input =
parse_property!(if flags >> 2, shortstr(input)?, map.insert("reserved", ShortString));
Ok((input, map))
}
pub fn header(input: &[u8]) -> IResult<'_, Box<ContentHeader>> {
let (input, class_id) = u16(Big)(input)?;
let (input, weight) = u16(Big)(input)?;
let (input, body_size) = u64(Big)(input)?;
// I do not quite understand this here. Apparently, there can be more than 15 flags?
// But the Basic class only specifies 15, so idk. Don't care about this for now
// Todo: But probably later.
let (input, property_flags) = u16(Big)(input)?;
let (input, property_fields) = basic_properties(property_flags, input)?;
Ok((
input,
Box::new(ContentHeader {
class_id,
weight,
body_size,
property_fields,
}),
))
}
} }
impl ContentHeader { impl ContentHeader {
pub fn new() -> Self { pub fn parse(input: &[u8]) -> Result<Box<Self>> {
todo!() match content_header_parse::header(input) {
Ok(([], header)) => Ok(header),
Ok((_, _)) => {
Err(
ConException::SyntaxError(vec!["could not consume all input".to_string()])
.into(),
)
}
Err(nom::Err::Incomplete(_)) => {
Err(ConException::SyntaxError(vec!["there was not enough data".to_string()]).into())
}
Err(nom::Err::Failure(err) | nom::Err::Error(err)) => Err(err),
}
} }
} }
pub async fn write_frame<W>(frame: &Frame, mut w: W) -> Result<()> pub async fn write_frame<W>(frame: &Frame, mut w: W) -> Result<()>
where where
W: AsyncWriteExt + Unpin, W: AsyncWriteExt + Unpin + Send,
{ {
trace!(?frame, "Sending frame"); trace!(?frame, "Sending frame");
w.write_u8(frame.kind as u8).await?; w.write_u8(frame.kind as u8).await?;
w.write_u16(frame.channel).await?; w.write_u16(frame.channel.num()).await?;
w.write_u32(u32::try_from(frame.payload.len()).context("frame size too big")?) w.write_u32(u32::try_from(frame.payload.len()).context("frame size too big")?)
.await?; .await?;
w.write_all(&frame.payload).await?; w.write_all(&frame.payload).await?;
@ -66,10 +154,11 @@ where
pub async fn read_frame<R>(r: &mut R, max_frame_size: usize) -> Result<Frame> pub async fn read_frame<R>(r: &mut R, max_frame_size: usize) -> Result<Frame>
where where
R: AsyncReadExt + Unpin, R: AsyncReadExt + Unpin + Send,
{ {
let kind = r.read_u8().await.context("read type")?; let kind = r.read_u8().await.context("read type")?;
let channel = r.read_u16().await.context("read channel")?; let channel = r.read_u16().await.context("read channel")?;
let channel = ChannelNum::new(channel);
let size = r.read_u32().await.context("read size")?; let size = r.read_u32().await.context("read size")?;
let mut payload = vec![0; size.try_into().unwrap()]; let mut payload = vec![0; size.try_into().unwrap()];
@ -82,7 +171,7 @@ where
} }
if max_frame_size != 0 && payload.len() > max_frame_size { if max_frame_size != 0 && payload.len() > max_frame_size {
return Err(ConException::FrameError.into_trans()); return Err(ConException::FrameError.into());
} }
let kind = parse_frame_type(kind, channel)?; let kind = parse_frame_type(kind, channel)?;
@ -98,25 +187,25 @@ where
Ok(frame) Ok(frame)
} }
fn parse_frame_type(kind: u8, channel: u16) -> Result<FrameType> { fn parse_frame_type(kind: u8, channel: ChannelNum) -> Result<FrameType> {
match kind { match kind {
frame_type::METHOD => Ok(FrameType::Method), frame_type::METHOD => Ok(FrameType::Method),
frame_type::HEADER => Ok(FrameType::Header), frame_type::HEADER => Ok(FrameType::Header),
frame_type::BODY => Ok(FrameType::Body), frame_type::BODY => Ok(FrameType::Body),
frame_type::HEARTBEAT => { frame_type::HEARTBEAT => {
if channel != 0 { if channel.is_zero() {
Err(ProtocolError::ConException(ConException::FrameError).into())
} else {
Ok(FrameType::Heartbeat) Ok(FrameType::Heartbeat)
} else {
Err(ProtocolError::ConException(ConException::FrameError).into())
} }
} }
_ => Err(ConException::FrameError.into_trans()), _ => Err(ConException::FrameError.into()),
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::frame::{Frame, FrameType}; use crate::frame::{ChannelNum, Frame, FrameType};
use bytes::Bytes; use bytes::Bytes;
#[tokio::test] #[tokio::test]
@ -145,7 +234,7 @@ mod tests {
frame, frame,
Frame { Frame {
kind: FrameType::Method, kind: FrameType::Method,
channel: 0, channel: ChannelNum::new(0),
payload: Bytes::from_static(&[1, 2, 3]), payload: Bytes::from_static(&[1, 2, 3]),
} }
); );

View file

@ -8,12 +8,13 @@ mod sasl;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
// TODO: handle big types
use crate::connection::Connection; use crate::connection::Connection;
use amqp_core::GlobalData; use amqp_core::GlobalData;
use anyhow::Result; use anyhow::Result;
use tokio::net; use tokio::net;
use tracing::{info, info_span, Instrument}; use tracing::{info, info_span, Instrument};
use uuid::Uuid;
pub async fn do_thing_i_guess(global_data: GlobalData) -> Result<()> { pub async fn do_thing_i_guess(global_data: GlobalData) -> Result<()> {
info!("Binding TCP listener..."); info!("Binding TCP listener...");
@ -23,13 +24,13 @@ pub async fn do_thing_i_guess(global_data: GlobalData) -> Result<()> {
loop { loop {
let (stream, peer_addr) = listener.accept().await?; let (stream, peer_addr) = listener.accept().await?;
let id = Uuid::from_bytes(rand::random()); let id = rand::random();
info!(local_addr = ?stream.local_addr(), %id, "Accepted new connection"); info!(local_addr = ?stream.local_addr(), %id, "Accepted new connection");
let span = info_span!("client-connection", %id); let span = info_span!("client-connection", %id);
let connection_handle = let connection_handle =
amqp_core::Connection::new_handle(id, peer_addr, global_data.clone()); amqp_core::connection::Connection::new_handle(id, peer_addr, global_data.clone());
let mut global_data_guard = global_data.lock(); let mut global_data_guard = global_data.lock();
global_data_guard global_data_guard

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,10 @@
use crate::error::{ConException, TransError}; use crate::error::TransError;
use amqp_core::error::ConException;
use amqp_core::methods::{FieldValue, Method, Table}; use amqp_core::methods::{FieldValue, Method, Table};
use rand::Rng; use rand::Rng;
use std::collections::HashMap;
mod generated; mod generated;
mod parse_helper; pub mod parse_helper;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
mod write_helper; mod write_helper;
@ -18,16 +18,10 @@ pub fn parse_method(payload: &[u8]) -> Result<Method, TransError> {
match nom_result { match nom_result {
Ok(([], method)) => Ok(method), Ok(([], method)) => Ok(method),
Ok((_, _)) => { Ok((_, _)) => {
Err( Err(ConException::SyntaxError(vec!["could not consume all input".to_string()]).into())
ConException::SyntaxError(vec!["could not consume all input".to_string()])
.into_trans(),
)
} }
Err(nom::Err::Incomplete(_)) => { Err(nom::Err::Incomplete(_)) => {
Err( Err(ConException::SyntaxError(vec!["there was not enough data".to_string()]).into())
ConException::SyntaxError(vec!["there was not enough data".to_string()])
.into_trans(),
)
} }
Err(nom::Err::Failure(err) | nom::Err::Error(err)) => Err(err), Err(nom::Err::Failure(err) | nom::Err::Error(err)) => Err(err),
} }
@ -70,7 +64,9 @@ rand_random_method!(bool, u8, i8, u16, i16, u32, i32, u64, i64, f32, f64);
impl<R: Rng> RandomMethod<R> for Table { impl<R: Rng> RandomMethod<R> for Table {
fn random(rng: &mut R) -> Self { fn random(rng: &mut R) -> Self {
let len = rng.gen_range(0..3); let len = rng.gen_range(0..3);
HashMap::from_iter((0..len).map(|_| (String::random(rng), FieldValue::random(rng)))) (0..len)
.map(|_| (String::random(rng), FieldValue::random(rng)))
.collect()
} }
} }

View file

@ -1,5 +1,6 @@
use crate::error::{ConException, ProtocolError, TransError}; use crate::error::TransError;
use crate::methods::generated::parse::IResult; use crate::methods::generated::parse::IResult;
use amqp_core::error::{ConException, ProtocolError};
use amqp_core::methods::{ use amqp_core::methods::{
Bit, FieldValue, Long, Longlong, Longstr, Octet, Short, Shortstr, Table, TableFieldName, Bit, FieldValue, Long, Longlong, Longstr, Octet, Short, Shortstr, Table, TableFieldName,
Timestamp, Timestamp,
@ -11,11 +12,10 @@ use nom::multi::{count, many0};
use nom::number::complete::{f32, f64, i16, i32, i64, i8, u16, u32, u64, u8}; use nom::number::complete::{f32, f64, i16, i32, i64, i8, u16, u32, u64, u8};
use nom::number::Endianness::Big; use nom::number::Endianness::Big;
use nom::Err; use nom::Err;
use std::collections::HashMap;
impl<T> nom::error::ParseError<T> for TransError { impl<T> nom::error::ParseError<T> for TransError {
fn from_error_kind(_input: T, _kind: ErrorKind) -> Self { fn from_error_kind(_input: T, _kind: ErrorKind) -> Self {
ConException::SyntaxError(vec![]).into_trans() ConException::SyntaxError(vec![]).into()
} }
fn append(_input: T, _kind: ErrorKind, other: Self) -> Self { fn append(_input: T, _kind: ErrorKind, other: Self) -> Self {
@ -28,7 +28,7 @@ pub fn fail_err<S: Into<String>>(msg: S) -> impl FnOnce(Err<TransError>) -> Err<
let msg = msg.into(); let msg = msg.into();
let stack = match err { let stack = match err {
Err::Error(e) | Err::Failure(e) => match e { Err::Error(e) | Err::Failure(e) => match e {
TransError::Invalid(ProtocolError::ConException(ConException::SyntaxError( TransError::Protocol(ProtocolError::ConException(ConException::SyntaxError(
mut stack, mut stack,
))) => { ))) => {
stack.push(msg); stack.push(msg);
@ -36,22 +36,22 @@ pub fn fail_err<S: Into<String>>(msg: S) -> impl FnOnce(Err<TransError>) -> Err<
} }
_ => vec![msg], _ => vec![msg],
}, },
_ => vec![msg], Err::Incomplete(_) => vec![msg],
}; };
Err::Failure(ConException::SyntaxError(stack).into_trans()) Err::Failure(ConException::SyntaxError(stack).into())
} }
} }
pub fn err_other<E, S: Into<String>>(msg: S) -> impl FnOnce(E) -> Err<TransError> { pub fn other_fail<E, S: Into<String>>(msg: S) -> impl FnOnce(E) -> Err<TransError> {
move |_| Err::Error(ConException::SyntaxError(vec![msg.into()]).into_trans()) move |_| Err::Failure(ConException::SyntaxError(vec![msg.into()]).into())
} }
#[macro_export] #[macro_export]
macro_rules! fail { macro_rules! fail {
($cause:expr) => { ($cause:expr) => {
return Err(nom::Err::Failure( return Err(nom::Err::Failure(
crate::error::ProtocolError::ConException(crate::error::ConException::SyntaxError( ::amqp_core::error::ProtocolError::ConException(
vec![String::from($cause)], ::amqp_core::error::ConException::SyntaxError(vec![String::from($cause)]),
)) )
.into(), .into(),
)) ))
}; };
@ -104,7 +104,7 @@ pub fn bit(input: &[u8], amount: usize) -> IResult<'_, Vec<Bit>> {
pub fn shortstr(input: &[u8]) -> IResult<'_, Shortstr> { pub fn shortstr(input: &[u8]) -> IResult<'_, Shortstr> {
let (input, len) = u8(input)?; let (input, len) = u8(input)?;
let (input, str_data) = take(usize::from(len))(input)?; let (input, str_data) = take(usize::from(len))(input)?;
let data = String::from_utf8(str_data.into()).map_err(err_other("shortstr"))?; let data = String::from_utf8(str_data.into()).map_err(other_fail("shortstr"))?;
Ok((input, data)) Ok((input, data))
} }
@ -132,7 +132,7 @@ pub fn table(input: &[u8]) -> IResult<'_, Table> {
)); ));
} }
let table = HashMap::from_iter(values.into_iter()); let table = values.into_iter().collect();
Ok((rest_input, table)) Ok((rest_input, table))
} }

View file

@ -2,7 +2,8 @@
//! //!
//! Currently only supports PLAIN (see [RFC 4616](https://datatracker.ietf.org/doc/html/rfc4616)) //! Currently only supports PLAIN (see [RFC 4616](https://datatracker.ietf.org/doc/html/rfc4616))
use crate::error::{ConException, Result}; use crate::error::Result;
use amqp_core::error::ConException;
pub struct PlainUser { pub struct PlainUser {
pub authorization_identity: String, pub authorization_identity: String,
@ -13,17 +14,11 @@ pub struct PlainUser {
pub fn parse_sasl_plain_response(response: &[u8]) -> Result<PlainUser> { pub fn parse_sasl_plain_response(response: &[u8]) -> Result<PlainUser> {
let mut parts = response let mut parts = response
.split(|&n| n == 0) .split(|&n| n == 0)
.map(|bytes| String::from_utf8(bytes.into()).map_err(|_| ConException::Todo.into_trans())); .map(|bytes| String::from_utf8(bytes.into()).map_err(|_| ConException::Todo));
let authorization_identity = parts let authorization_identity = parts.next().ok_or(ConException::Todo)??;
.next() let authentication_identity = parts.next().ok_or(ConException::Todo)??;
.ok_or_else(|| ConException::Todo.into_trans())??; let password = parts.next().ok_or(ConException::Todo)??;
let authentication_identity = parts
.next()
.ok_or_else(|| ConException::Todo.into_trans())??;
let password = parts
.next()
.ok_or_else(|| ConException::Todo.into_trans())??;
Ok(PlainUser { Ok(PlainUser {
authorization_identity, authorization_identity,

View file

@ -1,12 +1,13 @@
use crate::frame::FrameType; use crate::frame::FrameType;
use crate::{frame, methods}; use crate::{frame, methods};
use amqp_core::methods::{FieldValue, Method}; use amqp_core::connection::ChannelNum;
use amqp_core::methods::{ConnectionStart, ConnectionStartOk, FieldValue, Method};
use std::collections::HashMap; use std::collections::HashMap;
#[tokio::test] #[tokio::test]
async fn write_start_ok_frame() { async fn write_start_ok_frame() {
let mut payload = Vec::new(); let mut payload = Vec::new();
let method = Method::ConnectionStart { let method = Method::ConnectionStart(ConnectionStart {
version_major: 0, version_major: 0,
version_minor: 9, version_minor: 9,
server_properties: HashMap::from([( server_properties: HashMap::from([(
@ -15,13 +16,13 @@ async fn write_start_ok_frame() {
)]), )]),
mechanisms: "PLAIN".into(), mechanisms: "PLAIN".into(),
locales: "en_US".into(), locales: "en_US".into(),
}; });
methods::write::write_method(method, &mut payload).unwrap(); methods::write::write_method(method, &mut payload).unwrap();
let frame = frame::Frame { let frame = frame::Frame {
kind: FrameType::Method, kind: FrameType::Method,
channel: 0, channel: ChannelNum::zero(),
payload: payload.into(), payload: payload.into(),
}; };
@ -140,7 +141,7 @@ fn read_start_ok_payload() {
assert_eq!( assert_eq!(
method, method,
Method::ConnectionStartOk { Method::ConnectionStartOk(ConnectionStartOk {
client_properties: HashMap::from([ client_properties: HashMap::from([
( (
"product".to_string(), "product".to_string(),
@ -178,6 +179,6 @@ fn read_start_ok_payload() {
mechanism: "PLAIN".to_string(), mechanism: "PLAIN".to_string(),
response: "\x00admin\x00".into(), response: "\x00admin\x00".into(),
locale: "en_US".to_string() locale: "en_US".to_string()
} })
); );
} }

View file

@ -29,8 +29,8 @@ async fn main() -> Result<()> {
} }
fn setup_tracing() { fn setup_tracing() {
let rust_log = std::env::var("RUST_LOG");
const DEFAULT_LOG: &str = "hyper=info,debug"; const DEFAULT_LOG: &str = "hyper=info,debug";
let rust_log = std::env::var("RUST_LOG");
tracing_subscriber::fmt() tracing_subscriber::fmt()
.with_level(true) .with_level(true)

View file

@ -0,0 +1,18 @@
import { assert, connectAmqp } from './utils/utils.js';
const queueName = 'test-queue-124';
const connection = await connectAmqp();
const channel = await connection.createChannel();
const reply = await channel.assertQueue(queueName, { durable: false });
assert(reply.messageCount === 0, 'Message found in queue');
assert(reply.consumerCount === 0, 'Consumer listening on queue');
assert(reply.queue === queueName, 'Wrong queue name returned');
console.log(`created queue '${queueName}'`);
await channel.close();
await connection.close();

View file

@ -1,4 +1,4 @@
import { connectAmqp, sleep } from './utils/utils.js'; import { connectAmqp } from './utils/utils.js';
const connection = await connectAmqp(); const connection = await connectAmqp();

View file

@ -16,3 +16,9 @@ export const connectAmqp = async () => {
{} {}
); );
}; };
export const assert = (cond, msg) => {
if (!cond) {
throw new Error(`Assertion failed: ${msg}`);
}
};

View file

@ -211,11 +211,31 @@ impl Codegen {
for class in &amqp.classes { for class in &amqp.classes {
let enum_name = class.name.to_upper_camel_case(); let enum_name = class.name.to_upper_camel_case();
for method in &class.methods {
let method_name = method.name.to_upper_camel_case();
write!(
self.output,
" {enum_name}{method_name}({enum_name}{method_name}),"
)
.ok();
}
}
writeln!(self.output, "}}\n").ok();
// now codegen the individual structs
for class in &amqp.classes {
let class_name = class.name.to_upper_camel_case();
for method in &class.methods { for method in &class.methods {
let method_name = method.name.to_upper_camel_case(); let method_name = method.name.to_upper_camel_case();
self.doc_comment(&class.doc, 4); self.doc_comment(&class.doc, 4);
self.doc_comment(&method.doc, 4); self.doc_comment(&method.doc, 4);
write!(self.output, " {enum_name}{method_name}").ok(); writeln!(
self.output,
"#[derive(Debug, Clone, PartialEq)]
pub struct {class_name}{method_name}"
)
.ok();
if !method.fields.is_empty() { if !method.fields.is_empty() {
writeln!(self.output, " {{").ok(); writeln!(self.output, " {{").ok();
for field in &method.fields { for field in &method.fields {
@ -228,21 +248,19 @@ impl Codegen {
writeln!(self.output, " /// {field_docs}").ok(); writeln!(self.output, " /// {field_docs}").ok();
if !field.doc.is_empty() { if !field.doc.is_empty() {
writeln!(self.output, " ///").ok(); writeln!(self.output, " ///").ok();
self.doc_comment(&field.doc, 8); self.doc_comment(&field.doc, 4);
} }
} else { } else {
self.doc_comment(&field.doc, 8); self.doc_comment(&field.doc, 4);
} }
writeln!(self.output, " {field_name}: {field_type},").ok(); writeln!(self.output, " pub {field_name}: {field_type},").ok();
} }
writeln!(self.output, " }},").ok();
} else {
writeln!(self.output, ",").ok();
}
}
}
writeln!(self.output, " }}\n").ok(); writeln!(self.output, " }}\n").ok();
} else {
writeln!(self.output, ";\n").ok();
}
}
}
} }
fn amqp_type_to_rust_type(&self, amqp_type: &str) -> &'static str { fn amqp_type_to_rust_type(&self, amqp_type: &str) -> &'static str {

View file

@ -21,8 +21,8 @@ impl Codegen {
self.output, self.output,
"pub mod parse {{ "pub mod parse {{
use amqp_core::methods::*; use amqp_core::methods::*;
use crate::methods::parse_helper::*;
use crate::error::TransError; use crate::error::TransError;
use crate::methods::parse_helper::*;
use nom::{{branch::alt, bytes::complete::tag}}; use nom::{{branch::alt, bytes::complete::tag}};
use regex::Regex; use regex::Regex;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
@ -154,14 +154,14 @@ pub type IResult<'a, T> = nom::IResult<&'a [u8], T, TransError>;
let method_name = method.name.to_upper_camel_case(); let method_name = method.name.to_upper_camel_case();
writeln!( writeln!(
self.output, self.output,
" Ok((input, Method::{class_name}{method_name} {{" " Ok((input, Method::{class_name}{method_name}({class_name}{method_name} {{"
) )
.ok(); .ok();
for field in &method.fields { for field in &method.fields {
let field_name = self.snake_case(&field.name); let field_name = self.snake_case(&field.name);
writeln!(self.output, " {field_name},").ok(); writeln!(self.output, " {field_name},").ok();
} }
writeln!(self.output, " }}))").ok(); writeln!(self.output, " }})))").ok();
writeln!(self.output, "}}").ok(); writeln!(self.output, "}}").ok();
} }

View file

@ -43,7 +43,7 @@ use crate::methods::RandomMethod;
let method_name = method.name.to_upper_camel_case(); let method_name = method.name.to_upper_camel_case();
writeln!( writeln!(
self.output, self.output,
" {i} => Method::{class_name}{method_name} {{" " {i} => Method::{class_name}{method_name}( {class_name}{method_name}{{"
) )
.ok(); .ok();
for field in &method.fields { for field in &method.fields {
@ -54,7 +54,7 @@ use crate::methods::RandomMethod;
) )
.ok(); .ok();
} }
writeln!(self.output, " }},").ok(); writeln!(self.output, " }}),").ok();
} }
writeln!( writeln!(
self.output, self.output,

View file

@ -7,8 +7,8 @@ impl Codegen {
self.output, self.output,
"pub mod write {{ "pub mod write {{
use amqp_core::methods::*; use amqp_core::methods::*;
use crate::methods::write_helper::*;
use crate::error::TransError; use crate::error::TransError;
use crate::methods::write_helper::*;
use std::io::Write; use std::io::Write;
pub fn write_method<W: Write>(method: Method, mut writer: W) -> Result<(), TransError> {{ pub fn write_method<W: Write>(method: Method, mut writer: W) -> Result<(), TransError> {{
@ -22,12 +22,16 @@ pub fn write_method<W: Write>(method: Method, mut writer: W) -> Result<(), Trans
for method in &class.methods { for method in &class.methods {
let method_name = method.name.to_upper_camel_case(); let method_name = method.name.to_upper_camel_case();
let method_index = method.index; let method_index = method.index;
writeln!(self.output, " Method::{class_name}{method_name} {{").ok(); writeln!(
self.output,
" Method::{class_name}{method_name}({class_name}{method_name} {{"
)
.ok();
for field in &method.fields { for field in &method.fields {
let field_name = self.snake_case(&field.name); let field_name = self.snake_case(&field.name);
writeln!(self.output, " {field_name},").ok(); writeln!(self.output, " {field_name},").ok();
} }
writeln!(self.output, " }} => {{").ok(); writeln!(self.output, " }}) => {{").ok();
let [ci0, ci1] = class_index.to_be_bytes(); let [ci0, ci1] = class_index.to_be_bytes();
let [mi0, mi1] = method_index.to_be_bytes(); let [mi0, mi1] = method_index.to_be_bytes();
writeln!( writeln!(

View file

@ -1,10 +1,26 @@
use crate::project_root; use crate::project_root;
use anyhow::{ensure, Context, Result}; use anyhow::{ensure, Context, Result};
use std::path::Path;
use std::process::Command; use std::process::Command;
pub fn main() -> Result<()> { pub fn main() -> Result<()> {
let test_js_root = project_root().join("test-js"); let project_root = project_root();
println!("$ yarn"); let test_js_root = project_root.join("test-js");
let mut amqp_server = Command::new("cargo")
.arg("run")
.env("RUST_LOG", "trace")
.spawn()
.context("`cargo run` amqp")?;
let test_result = run_js(&test_js_root);
amqp_server.kill()?;
test_result
}
fn run_js(test_js_root: &Path) -> Result<()> {
let status = Command::new("yarn") let status = Command::new("yarn")
.current_dir(&test_js_root) .current_dir(&test_js_root)
.status() .status()

View file

@ -1,4 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1