diff --git a/.gitignore b/.gitignore
index 5c528f964aa7b08cb10b3b0da3d3248894079b25..a9dc4234365eff13880f4cf72e17a8e2634d3d8f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,7 +6,7 @@ __pycache__/
 
 # C extensions
 *.so
-
+.pycharm_helpers/
 # Distribution / packaging
 .Python
 env/
@@ -25,6 +25,7 @@ var/
 *.egg-info/
 .installed.cfg
 *.egg
+*.pdf
 
 # PyInstaller
 #  Usually these files are written by a python script from a template
diff --git a/LICENSE b/LICENSE
index 9d8f6d330a52f91c22a43fb71b6ce3963c69613c..5dfffdb0cdce280eecdca17f31d08c974799c208 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2016 DMS Serwis s.c.
+Copyright (c) 2016-2017 DMS Serwis s.c.
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
@@ -21,4 +21,4 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.
 
 
-resources/amqp-0-9-1.xml: Copyright (c) 2016 OASIS. All rights reserved.
\ No newline at end of file
+resources/*: consult each file for respective copyright holders
\ No newline at end of file
diff --git a/README.md b/README.md
index e59f2a79ccb7f8890f28da739eb82cc27b07fc28..feb513202082febc2a2c79cbdb181f6c2d3c3d82 100644
--- a/README.md
+++ b/README.md
@@ -8,33 +8,22 @@ CoolAMQP
 [![PyPI](https://img.shields.io/pypi/pyversions/CoolAMQP.svg)]()
 [![PyPI](https://img.shields.io/pypi/implementation/CoolAMQP.svg)]()
 
-**API WILL CHANGE MASSIVELY IN v1.0!!!! DO NOT USE FOR NOW!!!!**
+A **magical** AMQP client, that uses **heavy sorcery** to achieve speeds that other AMQP clients cannot even hope to match.
 
-When you're tired of fucking with AMQP reconnects.
+tl;dr - [this](coolamqp/framing/definitions.py) is **machine-generated** compile-time.
+[this](coolamqp/framing/compilation/content_property.py) **generates classes run-time**.
 
-When a connection made by CoolAMQP to your broker fails, it will pick another
-node, redeclare exchanges, queues, consumers, QoS and all the other shit, and tell
-your application that a disconnect happened.
-
-You only need to remember that:
-
-1. Reconnects and redefinitions take a while. Things will happen during that time. It is your responsibility to ensure that your distributed system is built to handle this
-2. CoolAMQP will tell you when it senses losing broker connection. It will also tell you when it regains the connection (that means that everything is redefined and ready to go)
-3. Delivering messages multiple times may happen. Ensure you know when it happens. Keywords: message acknowledgement, amqp specification
-4. CoolAMQP won't touch your messages. You send bags of bytes and properties, you get bags of bytes and their properties. This is by design - the postman shouldn't mess with your mail.
 
 The project is actively maintained and used in a commercial project. Tests can run
 either on Vagrant (Vagrantfile attached) or Travis CI, and run against RabbitMQ.
 
 Enjoy!
 
-# Changelog
-## v0.12
-* ACCESS_REFUSED/RESOURCE_LOCKED on reconnect is properly handled
-* reason for consumer cancel is provided
-* can read error code and reply text from failed orders
-* test suite refactored and improved
-## v0.11
-* added *no_ack* to *consume*
-* can pass other non-text types to Message
-* can set global bit in *qos*
+
+## Notes
+Assertions are sprinkled throughout the code. You may wish to run with optimizations enabled
+if you need every CPU cycle you can get.
+
+**v0.8** series has unstable API.
+
+**v0.9** series will have a stable API.
\ No newline at end of file
diff --git a/coolamqp/__init__.py b/coolamqp/__init__.py
index 64f889284517fbb688deda2350210370ee5285ff..a8863d8254d08542d55ff98ca2ceddbf69b76511 100644
--- a/coolamqp/__init__.py
+++ b/coolamqp/__init__.py
@@ -1,6 +1,2 @@
 # coding=UTF-8
-from coolamqp.cluster import ClusterNode, Cluster
-from coolamqp.events import ConnectionDown, ConnectionUp, MessageReceived, ConsumerCancelled
-from coolamqp.messages import Message, Exchange, Queue
-from coolamqp.backends.base import Cancelled, Discarded
 
diff --git a/coolamqp/attaches/__init__.py b/coolamqp/attaches/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..5075d303b83e0c24bb739eb6716c7a8c914b0304
--- /dev/null
+++ b/coolamqp/attaches/__init__.py
@@ -0,0 +1,17 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+"""
+Attaches are components that attach to an coolamqp.uplink.Connection and perform some duties
+These duties almost always require allocating a channel. A base class - Channeler - is provided to faciliate that.
+The attache becomes then responsible for closing this channel.
+
+Attache should also register at least one on_fail watch, so it can handle things if they go south.
+
+Multiple attaches can be "abstracted" as single one via AttacheGroup (which is also an Attache)
+
+EVERYTHING HERE IS CALLED BY LISTENER THREAD UNLESS STATED OTHERWISE.
+"""
+
+from coolamqp.attaches.consumer import Consumer
+from coolamqp.attaches.publisher import Publisher
+from coolamqp.attaches.agroup import AttacheGroup
diff --git a/coolamqp/attaches/agroup.py b/coolamqp/attaches/agroup.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e3f3440b7939ef14788b8fdbfcaad24fe889f2f
--- /dev/null
+++ b/coolamqp/attaches/agroup.py
@@ -0,0 +1,74 @@
+# coding=UTF-8
+"""
+This is an attache that attaches multiple attaches.
+
+It evicts cancelled attaches.
+"""
+from __future__ import print_function, absolute_import, division
+import six
+import logging
+import weakref
+
+logger = logging.getLogger(__name__)
+
+
+from coolamqp.attaches.channeler import Attache, ST_OFFLINE
+from coolamqp.attaches.consumer import Consumer
+
+
+class AttacheGroup(Attache):
+    """
+    A bunch of attaches
+    """
+
+    def __init__(self):
+        super(AttacheGroup, self).__init__()
+        self.attaches = []
+
+    def add(self, attache):
+        """
+        Add an attache to this group.
+
+        If this is attached, and connection is ST_ONLINE, .attach() will be called
+        on this attache at once.
+
+        :param attache: Attache instance
+        """
+        assert attache not in self.attaches
+        self.attaches.append(attache)
+
+        # If we have any connection, and it's not dead, attach
+        if self.connection is not None and self.connection.state != ST_OFFLINE:
+            attache.attach(self.connection)
+
+        if isinstance(attache, Consumer):
+            attache.attache_group = self
+
+    def on_cancel_customer(self, customer):
+        """
+        Called by a customer, when it's cancelled.
+
+        Consumer must have .attache_group set to this. This is done by .add()
+
+        :param customer: a Customer instance
+        """
+        self.attaches.remove(customer)
+
+    def attach(self, connection):
+        """
+        Attach to a connection
+
+        :param connection: Connection instance of any state
+        """
+        # since this attache does not watch for failures, it can't use typical method.
+        self.connection = connection
+
+        for attache in self.attaches:
+            if not attache.cancelled:
+                print('Attaching', attache)
+                attache.attach(connection)
+            else:
+                print('lol wut')
+                raise Exception
+
+
diff --git a/coolamqp/attaches/channeler.py b/coolamqp/attaches/channeler.py
new file mode 100644
index 0000000000000000000000000000000000000000..a892b751dac87184c7eeaada419bf44238cd6732
--- /dev/null
+++ b/coolamqp/attaches/channeler.py
@@ -0,0 +1,207 @@
+# coding=UTF-8
+"""
+Base class for consumer or publisher with the capabiility to
+set up and tear down channels
+"""
+from __future__ import print_function, absolute_import, division
+import six
+from coolamqp.framing.frames import AMQPMethodFrame, AMQPBodyFrame, AMQPHeaderFrame
+from coolamqp.framing.definitions import ChannelOpen, ChannelOpenOk, BasicConsume, \
+    BasicConsumeOk, QueueDeclare, QueueDeclareOk, ExchangeDeclare, ExchangeDeclareOk, \
+    QueueBind, QueueBindOk, ChannelClose, ChannelCloseOk, BasicCancel, BasicDeliver, \
+    BasicAck, BasicReject, ACCESS_REFUSED, RESOURCE_LOCKED, BasicCancelOk
+from coolamqp.uplink import HeaderOrBodyWatch, MethodWatch
+import logging
+
+ST_OFFLINE = 0  # Consumer is *not* consuming, no setup attempts are being made
+ST_SYNCING = 1  # A process targeted at consuming has been started
+ST_ONLINE = 2   # Consumer is declared all right
+
+
+logger = logging.getLogger(__name__)
+
+
+class Attache(object):
+    """
+    Something that can be attached to connection.
+    """
+    def __init__(self):
+        self.cancelled = False      #: public, if this is True, it won't be attached to next connection
+        self.state = ST_OFFLINE
+        self.connection = None
+
+    def attach(self, connection):
+        """
+        Attach to a connection.
+
+        :param connection: Connection instance of any state
+        """
+        assert self.connection is None
+        assert connection.state != ST_OFFLINE
+        self.connection = connection
+
+
+class Channeler(Attache):
+    """
+    A base class for Consumer/Publisher implementing link set up and tear down.
+
+    A channeler can be essentially in 4 states:
+    - ST_OFFLINE (.channel is None): channel is closed, object is unusable. Requires an attach() a connection
+                                     that is being established, or open, or whatever. Connection will notify
+                                     this channeler that it's open.
+    - ST_SYNCING: channeler is opening a channel/doing some other things related to it's setup.
+                  it's going to be ST_ONLINE soon, or go back to ST_OFFLINE.
+                  It has, for sure, acquired a channel number.
+    - ST_ONLINE:  channeler is operational. It has a channel number and has declared everything
+                  it needs to.
+
+                  on_operational(True) will be called when a transition is made TO this state.
+                  on_operational(False) will be called when a transition is made FROM this state.
+
+    - ST_OFFLINE (.channel is not None): channeler is undergoing a close. It has not yet torn down the channel,
+                                         but ordering it to do anything is pointless, because it will not get done
+                                         until attach() with new connection is called.
+    """
+
+    def __init__(self):
+        """
+        [EXTEND ME!]
+        """
+        super(Channeler, self).__init__()
+        self.channel_id = None      # channel obtained from Connection
+
+    def attach(self, connection):
+        """
+        Attach this object to a live Connection.
+
+        :param connection: Connection instance to use
+        """
+        super(Channeler, self).attach(connection)
+        assert self.connection is not None
+        connection.call_on_connected(self.on_uplink_established)
+
+    # ------- event handlers
+
+    def on_operational(self, operational):
+        """
+        [EXTEND ME] Called by internal methods (on_*) when channel has achieved (or lost) operational status.
+
+        If this is called with operational=True, then for sure it will be called with operational=False.
+
+        This will, therefore, get called an even number of times.
+
+        :param operational: True if channel has just become operational, False if it has just become useless.
+        """
+
+    def on_close(self, payload=None):
+        """
+        [EXTEND ME] Handler for channeler destruction.
+
+        Called on:
+        - channel exception
+        - connection failing
+
+        This handles following situations:
+        - payload is None: this means that connection has gone down hard, so our Connection object is
+                           probably very dead. Transition to ST_OFFLINE (.channel is None)
+        - payload is a ChannelClose: this means that a channel exception has occurred. Dispatch a ChannelCloseOk,
+                                     attempt to log an exception, transition to ST_OFFLINE (.channel is None)
+        - payload is a ChannelCloseOk: this means that it was us who attempted to close the channel. Return the channel
+                                       to free pool, transition to ST_OFFLINE (.channel is None)
+
+        If you need to handle something else, extend this. Take care that this DOES NOT HANDLE errors that happen
+        while state is ST_SYNCING. You can expect this to handle a full channel close, therefore releasing all
+        resources, so it mostly will do *the right thing*.
+
+        If you need to do something else than just close a channel, please extend or modify as necessary.
+
+        WARNING: THIS WILL GET CALLED TWICE.
+            Once on ChannelClose - if so,
+            Second with None - because socket dies.
+
+            Be prepared!
+
+        """
+        if self.connection is None:
+            # teardown already done
+            return
+
+        if self.state == ST_ONLINE:
+            # The channel has just lost operationality!
+            self.on_operational(False)
+        self.state = ST_OFFLINE
+
+        if isinstance(payload, (ChannelClose, ChannelCloseOk)):
+            assert self.channel_id is not None
+            self.connection.free_channels.append(self.channel_id)
+            # it's just dead don't bother with returning port
+
+        self.connection = None
+        self.channel_id = None
+        print(self, 'pwned with', payload)
+
+        if isinstance(payload, ChannelClose):
+            logger.debug('Channel closed: %s %s', payload.reply_code, payload.reply_text)
+
+    def methods(self, payloads):
+        """
+        Syntactic sugar for
+
+            for payload in paylods:
+                self.method(payload)
+
+        But moar performant.
+        """
+        assert self.channel_id is not None
+        frames = [AMQPMethodFrame(self.channel_id, payload) for payload in payloads]
+        self.connection.send(frames)
+
+    def method(self, payload):
+        """
+        Syntactic sugar for:
+
+            self.connection.send([AMQPMethodFrame(self.channel_id, payload)])
+        """
+        self.methods([payload])
+
+    def method_and_watch(self, method_payload, method_classes_to_watch, callable):
+        """
+        Syntactic sugar for
+
+            self.connection.method_and_watch(self.channel_id,
+                                             method_payload,
+                                             method_classes_to_watch,
+                                             callable)
+        """
+        assert self.channel_id is not None
+        self.connection.method_and_watch(self.channel_id, method_payload, method_classes_to_watch, callable)
+
+    def on_setup(self, payload):
+        """
+        [OVERRIDE ME!] Called with a method frame that signifies a part of setup.
+
+        You must be prepared to handle at least a payload of ChannelOpenOk
+
+        :param payload: AMQP method frame payload
+        """
+        raise Exception('Abstract method - override me!')
+
+
+    def on_uplink_established(self):
+        """Called by connection. Connection reports being ready to do things."""
+        assert self.connection is not None
+        assert self.connection.state == ST_ONLINE, repr(self)
+        self.state = ST_SYNCING
+        self.channel_id = self.connection.free_channels.pop()
+
+        self.connection.watch_for_method(self.channel_id, (ChannelClose, ChannelCloseOk, BasicCancel),
+                                         self.on_close,
+                                         on_fail=self.on_close)
+
+        self.connection.method_and_watch(
+            self.channel_id,
+            ChannelOpen(),
+            ChannelOpenOk,
+            self.on_setup
+        )
+
diff --git a/coolamqp/attaches/consumer.py b/coolamqp/attaches/consumer.py
new file mode 100644
index 0000000000000000000000000000000000000000..bee5f12ed7151017bcb632110380f3b682d60e66
--- /dev/null
+++ b/coolamqp/attaches/consumer.py
@@ -0,0 +1,405 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+import six
+import logging
+from coolamqp.framing.frames import AMQPBodyFrame, AMQPHeaderFrame
+from coolamqp.framing.definitions import ChannelOpenOk, BasicConsume, \
+    BasicConsumeOk, QueueDeclare, QueueDeclareOk, ExchangeDeclare, ExchangeDeclareOk, \
+    QueueBind, QueueBindOk, ChannelClose, BasicCancel, BasicDeliver, \
+    BasicAck, BasicReject, RESOURCE_LOCKED, BasicCancelOk, BasicQos, HARD_ERROR
+from coolamqp.uplink import HeaderOrBodyWatch, MethodWatch
+
+from coolamqp.attaches.channeler import Channeler, ST_ONLINE, ST_OFFLINE
+from coolamqp.exceptions import ResourceLocked, AMQPError
+
+
+logger = logging.getLogger(__name__)
+
+
+class Consumer(Channeler):
+    """
+    This object represents a consumer in the system.
+
+    Consumer may reside on any AMQP broker, this is to be decided by CoolAMQP.
+    Consumer, when created, has the state of ST_SYNCING. CoolAMQP will
+    try to declare the consumer where it makes most sense for it to be.
+
+    If it succeeds, the consumer will enter state ST_ONLINE, and callables
+    on_start will be called. This means that broker has confirmed that this
+    consumer is operational and receiving messages.
+
+    Note that does not attempt to cancel consumers, or any of such nonsense. Having
+    a channel per consumer gives you the unique possibility of simply closing the channel.
+    Since this implies cancelling the consumer, here you go.
+    """
+
+    def __init__(self, queue, on_message, no_ack=True, qos=None, cancel_on_failure=False,
+                 future_to_notify=None,
+                 fail_on_first_time_resource_locked=False
+                 ):
+        """
+        :param queue: Queue object, being consumed from right now.
+            Note that name of anonymous queue might change at any time!
+        :param on_message: callable that will process incoming messages
+        :type on_message: callable(ReceivedMessage instance)
+        :param no_ack: Will this consumer require acknowledges from messages?
+        :param qos: a tuple of (prefetch size, prefetch window) for this consumer
+        :type qos: tuple(int, int) or tuple(None, int)
+        :param cancel_on_failure: Consumer will cancel itself when link goes down
+        :type cancel_on_failure: bool
+        :param future_to_notify: Future to succeed when this consumer goes online for the first time.
+                                 This future can also raise with:
+                                        AMQPError - a HARD_ERROR (see AMQP spec) was encountered
+                                        ResourceLocked - this was the first declaration, and
+                                            fail_on_first_time_resource_locked was set
+        :param fail_on_first_time_resource_locked: When consumer is declared for the first time,
+                                                   and RESOURCE_LOCKED is encountered, it will fail the
+                                                   future with ResourceLocked, and consumer will cancel itself.
+                                                   By default it will retry until success is made.
+                                                   If the consumer doesn't get the chance to be declared - because
+                                                   of a connection fail - next reconnect will consider this to be
+                                                   SECOND declaration, ie. it will retry ad infinitum
+        :type fail_on_first_time_resource_locked: bool
+        """
+        super(Consumer, self).__init__()
+
+        self.queue = queue
+        self.no_ack = no_ack
+
+        self.on_message = on_message
+
+        # private
+        self.cancelled = False  # did the client want to STOP using this consumer?
+        self.receiver = None  # MessageReceiver instance
+
+        self.attache_group = None   # attache group this belongs to.
+                                    # if this is not None, then it has an attribute
+                                    # on_cancel_customer(Consumer instance)
+        if qos is not None:
+            if qos[0] is None:
+                qos = 0, qos[1] # prefetch_size=0=undefined
+        self.qos = qos
+        self.qos_update_sent = False    # QoS was not sent to server
+
+        self.future_to_notify = future_to_notify
+        self.fail_on_first_time_resource_locked = fail_on_first_time_resource_locked
+        self.cancel_on_failure = cancel_on_failure
+
+    def set_qos(self, prefetch_size, prefetch_count):
+        """
+        Set new QoS for this consumer.
+
+        :param prefetch_size: prefetch in octets
+        :param prefetch_count: prefetch in whole messages
+        """
+        if self.state == ST_ONLINE:
+            self.method(BasicQos(prefetch_size or 0, prefetch_count, False))
+        self.qos = prefetch_size or 0, prefetch_count
+
+    def cancel(self):
+        """
+        Cancel the customer.
+
+        Note that this is a departure form AMQP specification. We don't attempt to cancel the customer,
+        we simply trash the channel. Idk if it's a good idea...
+
+        .ack() or .nack() for messages from this customer will have no effect.
+        """
+        self.cancelled = True
+        self.method(ChannelClose(0, b'consumer cancelled', 0, 0))
+        if self.attache_group is not None:
+            self.attache_group.on_cancel_customer(self)
+
+
+    def on_operational(self, operational):
+        super(Consumer, self).on_operational(operational)
+
+        if operational:
+            assert self.receiver is None
+            self.receiver = MessageReceiver(self)
+
+            # notify the future
+            if self.future_to_notify is not None:
+                self.future_to_notify.set_result()
+                self.future_to_notify = None
+
+        else:
+            self.receiver.on_gone()
+            self.receiver = None
+
+    def on_close(self, payload=None):
+        """
+        Handle closing the channel. It sounds like an exception...
+
+        This is done in two steps:
+        1. self.state <- ST_OFFLINE, on_event(EV_OFFLINE)   upon detecting that no more messages will
+           be there
+        2. self.channel_id <- None, channel is returned to Connection - channel has been physically torn down
+
+        Note, this can be called multiple times, and eventually with None.
+
+        """
+
+        if self.cancel_on_failure:
+            logger.debug('Consumer is cancel_on_failure and failure seen, cancelling')
+            self.cancel()
+
+        if self.state == ST_ONLINE:
+            # The channel has just lost operationality!
+            self.on_operational(False)
+        self.state = ST_OFFLINE
+
+        should_retry = False
+
+        if isinstance(payload, BasicCancel):
+            # Consumer Cancel Notification - by RabbitMQ
+            self.methods([BasicCancelOk(), ChannelClose(0, b'Received basic.cancel', 0, 0)])
+            return
+
+        if isinstance(payload, BasicCancelOk):
+            # OK, our cancelling went just fine - proceed with teardown
+            self.method(ChannelClose(0, b'Received basic.cancel-ok', 0, 0))
+            return
+
+        if isinstance(payload, ChannelClose):
+            rc = payload.reply_code
+            if rc == RESOURCE_LOCKED:
+                # special handling
+                # This is because we might be reconnecting, and the broker doesn't know yet that we are dead.
+                # it won't release our exclusive channels, and that's why we'll get RESOURCE_LOCKED.
+
+                if self.fail_on_first_time_resource_locked:
+                    # still, a RESOURCE_LOCKED on a first declaration ever suggests something is very wrong
+                    if self.future_to_notify:
+                        self.future_to_notify.set_exception(ResourceLocked(payload))
+                        self.future_to_notify = None
+                        self.cancel()
+
+                should_retry = True
+            elif rc in HARD_ERROR:
+                logger.warn('Channel closed due to hard error, %s: %s', payload.reply_code, payload.reply_text)
+                if self.future_to_notify:
+                    self.future_to_notify.set_exception(AMQPError(payload))
+                    self.future_to_notify = None
+
+
+        # We might not want to throw the connection away.
+        should_retry = should_retry and (not self.cancelled)
+
+
+        old_con = self.connection
+
+        super(Consumer, self).on_close(payload)     # this None's self.connection
+        self.fail_on_first_time_resource_locked = False
+
+        if should_retry:
+            if old_con.state == ST_ONLINE:
+                logger.info('Retrying with %s', self.queue.name)
+                self.attach(old_con)
+
+    def on_delivery(self, sth):
+        """
+        Callback for delivery-related shit
+        :param sth: AMQPMethodFrame WITH basic-deliver, AMQPHeaderFrame or AMQPBodyFrame
+        """
+        if isinstance(sth, BasicDeliver):
+            self.receiver.on_basic_deliver(sth)
+        elif isinstance(sth, AMQPBodyFrame):
+            self.receiver.on_body(sth.data)
+        elif isinstance(sth, AMQPHeaderFrame):
+            self.receiver.on_head(sth)
+
+        # No point in listening for more stuff, that's all the watches even listen for
+
+    def on_setup(self, payload):
+        """Called with different kinds of frames - during setup"""
+
+        if isinstance(payload, ChannelOpenOk):
+            # Do we need to declare the exchange?
+
+            if self.queue.exchange is not None:
+                self.connection.method_and_watch(
+                    self.channel_id,
+                    ExchangeDeclare(self.queue.exchange.name.encode('utf8'),
+                                    self.queue.exchange.type.encode('utf8'),
+                                    False,
+                                    self.queue.exchange.durable,
+                                    self.queue.exchange.auto_delete,
+                                    False,
+                                    False,
+                                    []),
+                    ExchangeDeclareOk,
+                    self.on_setup
+                )
+            else:
+                self.on_setup(ExchangeDeclareOk())
+
+        elif isinstance(payload, ExchangeDeclareOk):
+            # Declare the queue
+
+            name = b'' if self.queue.anonymous else self.queue.name.encode('utf8')
+
+            self.connection.method_and_watch(
+                self.channel_id,
+                QueueDeclare(
+                    name,
+                    False,
+                    self.queue.durable,
+                    self.queue.exclusive,
+                    self.queue.auto_delete,
+                    False,
+                    []
+                ),
+                QueueDeclareOk,
+                self.on_setup
+            )
+
+        elif isinstance(payload, QueueDeclareOk):
+            # did we need an anonymous name?
+            if self.queue.anonymous:
+                self.queue.name = payload.queue_name.decode('utf8')
+
+            # We need any form of binding.
+            if self.queue.exchange is not None:
+                self.method_and_watch(
+                    QueueBind(
+                        self.queue.name.encode('utf8'), self.queue.exchange.name.encode('utf8'),
+                        b'', False, []),
+                    QueueBindOk,
+                    self.on_setup
+                )
+            else:
+                # default exchange, pretend it was bind ok
+                self.on_setup(QueueBindOk())
+        elif isinstance(payload, QueueBindOk):
+            # itadakimasu
+            if self.qos is not None:
+                self.method(BasicQos(self.qos[0], self.qos[1], False))
+            self.method_and_watch(
+                BasicConsume(self.queue.name.encode('utf8'), self.queue.name.encode('utf8'),
+                    False, self.no_ack, self.queue.exclusive, False, []),
+                BasicConsumeOk,
+                self.on_setup
+            )
+
+        elif isinstance(payload, BasicConsumeOk):
+            # AWWW RIGHT~!!! We're good.
+
+            # Register watches for receiving shit
+            self.connection.watch(HeaderOrBodyWatch(self.channel_id, self.on_delivery))
+            mw = MethodWatch(self.channel_id, BasicDeliver, self.on_delivery)
+            mw.oneshot = False
+            self.connection.watch(mw)
+
+            self.state = ST_ONLINE
+            self.on_operational(True)
+
+            # resend QoS, in case of sth
+            if self.qos is not None:
+                self.set_qos(self.qos[0], self.qos[1])
+
+
+class MessageReceiver(object):
+    """This is an object that is used to received messages.
+
+    It maintains all the state, and is used to ack/nack messages as well.
+
+    This object is TORN DOWN when a consumer goes offline,
+    and is recreated when it goes online.
+
+    This is called by consumer upon receiving different parts of the message,
+    and may opt to kill the connection on bad framing with
+    self.consumer.connection.send(None)
+    """
+    def __init__(self, consumer):
+        self.consumer = consumer
+        self.state = 0  # 0 - waiting for Basic-Deliver
+                        # 1 - waiting for Header
+                        # 2 - waiting for Body [all]
+                        # 3 - gone!
+
+        self.bdeliver = None    # payload of Basic-Deliver
+        self.header = None      # AMQPHeaderFrame
+        self.body = []          # list of payloads
+        self.data_to_go = None  # set on receiving header, how much bytes we need yet
+
+        self.acks_pending = set()   # list of things to ack/reject
+
+    def on_gone(self):
+        """Called by Consumer to inform upon discarding this receiver"""
+        self.state = 3
+
+    def confirm(self, delivery_tag, success):
+        """
+        This crafts a constructor for confirming messages.
+
+        This should return a callable/0, whose calling will ACK or REJECT the message.
+        Calling it multiple times should have no ill effect.
+
+        If this receiver is long gone,
+
+        :param delivery_tag: delivery_tag to ack
+        :param success: True if ACK, False if REJECT
+        :return: callable/0
+        """
+
+        def callable():
+            if self.state == 3:
+                return  # Gone!
+
+            if self.consumer.cancelled:
+                return # cancelled!
+
+            if delivery_tag not in self.acks_pending:
+                return  # already confirmed/rejected
+
+            if success:
+                self.consumer.method(BasicAck(delivery_tag, False))
+            else:
+                self.consumer.method(BasicReject(delivery_tag, True))
+
+        return callable
+
+
+    def on_head(self, frame):
+        assert self.state == 1
+        self.header = frame
+        self.data_to_go = frame.body_size
+        self.state = 2
+
+    def on_basic_deliver(self, payload):
+        assert self.state == 0
+        self.bdeliver = payload
+        self.state = 1
+
+    def on_body(self, payload):
+        """:type payload: buffer"""
+        assert self.state == 2
+        self.body.append(payload)
+        self.data_to_go -= len(payload)
+        assert self.data_to_go >= 0
+        if self.data_to_go == 0:
+            ack_expected = not self.consumer.no_ack
+
+            # Message A-OK!
+
+            if ack_expected:
+                self.acks_pending.add(self.bdeliver.delivery_tag)
+
+            from coolamqp.objects import ReceivedMessage
+            rm = ReceivedMessage(
+                b''.join(map(six.binary_type, self.body)), #todo inefficient as FUUUCK
+                self.bdeliver.exchange,
+                self.bdeliver.routing_key,
+                self.header.properties,
+                self.bdeliver.delivery_tag,
+                None if self.consumer.no_ack else self.confirm(self.bdeliver.delivery_tag, True),
+                None if self.consumer.no_ack else self.confirm(self.bdeliver.delivery_tag, False),
+            )
+
+            self.consumer.on_message(rm)
+
+            self.state = 0
+
+        # at this point it's safe to clear the body
+        self.body = []
diff --git a/coolamqp/attaches/publisher.py b/coolamqp/attaches/publisher.py
new file mode 100644
index 0000000000000000000000000000000000000000..147df0fc5705a95d3700f42fafbc1da8879bb7d1
--- /dev/null
+++ b/coolamqp/attaches/publisher.py
@@ -0,0 +1,245 @@
+# coding=utf-8
+"""
+Module used to publish messages.
+
+Expect wild NameErrors if you build this without RabbitMQ extensions (enabled by default),
+and try to use MODE_CNPUB.
+
+If you use a broker that doesn't support these, just don't use MODE_CNPUB. CoolAMQP is smart enough
+to check with the broker beforehand.
+"""
+from __future__ import absolute_import, division, print_function
+
+import collections
+import logging
+import struct
+import warnings
+
+from coolamqp.framing.definitions import ChannelOpenOk, BasicPublish, Basic, BasicAck
+from coolamqp.framing.frames import AMQPMethodFrame, AMQPBodyFrame, AMQPHeaderFrame
+
+try:
+    # these extensions will be available
+    from coolamqp.framing.definitions import ConfirmSelect, ConfirmSelectOk, BasicNack
+except ImportError:
+    pass
+
+from coolamqp.attaches.channeler import Channeler, ST_ONLINE, ST_OFFLINE
+from coolamqp.uplink import PUBLISHER_CONFIRMS, MethodWatch, FailWatch
+from coolamqp.attaches.utils import AtomicTagger, FutureConfirmableRejectable, Synchronized
+
+from coolamqp.objects import Future
+
+logger = logging.getLogger(__name__)
+
+
+# for holding messages when MODE_CNPUB and link is down
+CnpubMessageSendOrder = collections.namedtuple('CnpubMessageSendOrder', ('message', 'exchange_name',
+                                                                         'routing_key', 'future'))
+
+
+class Publisher(Channeler, Synchronized):
+    """
+    An object that is capable of sucking into a Connection and sending messages.
+    Depending on it's characteristic, it may process messages in:
+
+        - non-ack mode (default) - messages will be dropped on the floor if there is no active uplink
+        - Consumer Publish mode - requires broker support, each message will be ACK/NACKed by the broker
+                                  messages will survive broker reconnections.
+
+                                  If you support this, it is your job to ensure that broker supports
+                                  publisher_confirms. If it doesn't, this publisher will enter ST_OFFLINE
+                                  and emit a warning.
+
+        Other modes may be added in the future.
+
+    Since this may be called by other threads than ListenerThread, this has locking.
+
+    _pub and on_fail are synchronized so that _pub doesn't see a partially destroyed class.
+    """
+    MODE_NOACK = 0      # no-ack publishing
+    MODE_CNPUB = 1      # RabbitMQ publisher confirms extension
+    #todo add fallback using plain AMQP transactions - this will remove UnusablePublisher and stuff
+
+
+    class UnusablePublisher(Exception):
+        """This publisher will never work (eg. MODE_CNPUB on a broker not supporting publisher confirms)"""
+
+    def __init__(self, mode):
+        """
+        Create a new publisher
+        :param mode: Publishing mode to use. One of:
+                         MODE_NOACK - use non-ack mode
+                         MODE_CNPUB - use consumer publishing mode. A switch to MODE_TXPUB will be made
+                                      if broker does not support these.
+        :raise ValueError: mode invalid
+        """
+        Channeler.__init__(self)
+        Synchronized.__init__(self)
+
+        if mode not in (Publisher.MODE_NOACK, Publisher.MODE_CNPUB):
+            raise ValueError(u'Invalid publisher mode')
+
+        self.mode = mode
+
+        self.messages = collections.deque() # Messages to publish. From newest to last.
+                                            # tuple of (Message object, exchange name::str, routing_key::str,
+                                            #           Future to confirm or None, flags as tuple|empty tuple
+
+        self.tagger = None  # None, or AtomicTagger instance id MODE_CNPUB
+
+        self.critically_failed = False
+
+    @Synchronized.synchronized
+    def attach(self, connection):
+        Channeler.attach(self, connection)
+        connection.watch(FailWatch(self.on_fail))
+
+    @Synchronized.synchronized
+    def on_fail(self):
+        self.state = ST_OFFLINE
+        print('Publisher is FAILED')
+
+    def _pub(self, message, exchange_name, routing_key):
+        """
+        Just send the message. Sends BasicDeliver + header + body.
+
+        BECAUSE OF publish THIS CAN GET CALLED BY FOREIGN THREAD.
+
+        :param message: Message instance
+        :param exchange_name: exchange to use
+        :param routing_key: routing key to use
+        :type exchange_name: bytes
+        :param routing_key: bytes
+        """
+        # Break down large bodies
+        bodies = []
+
+        body = buffer(message.body)
+        max_body_size = self.connection.frame_max - AMQPBodyFrame.FRAME_SIZE_WITHOUT_PAYLOAD
+        while len(body) > 0:
+            bodies.append(buffer(body, 0, max_body_size))
+            body = buffer(body, max_body_size)
+
+        self.connection.send([
+            AMQPMethodFrame(self.channel_id, BasicPublish(exchange_name, routing_key, False, False)),
+            AMQPHeaderFrame(self.channel_id, Basic.INDEX, 0, len(message.body), message.properties)
+        ])
+
+        # todo optimize it - if there's only one frame it can with previous send
+        for body in bodies:
+            self.connection.send([AMQPBodyFrame(self.channel_id, body)])
+
+    def _mode_cnpub_process_deliveries(self):
+        """
+        Dispatch all frames that are waiting to be sent
+
+        To be used when  mode is MODE_CNPUB and we just got ST_ONLINE
+        """
+        assert self.state == ST_ONLINE
+        assert self.mode == Publisher.MODE_CNPUB
+
+        while len(self.messages) > 0:
+            msg, xchg, rk, fut = self.messages.popleft()
+
+            if fut.cancelled:
+                # Ok, don't do this.
+                fut.set_cancel()
+                continue
+
+            self.tagger.deposit(self.tagger.get_key(), FutureConfirmableRejectable(fut))
+            self._pub(msg, xchg, rk)
+
+    def _on_cnpub_delivery(self, payload):
+        """
+        This gets called on BasicAck and BasicNack, if mode is MODE_CNPUB
+        """
+        assert self.mode == Publisher.MODE_CNPUB
+
+        print('Got %s with dt=%s' % (payload, payload.delivery_tag))
+
+        if isinstance(payload, BasicAck):
+            self.tagger.ack(payload.delivery_tag, payload.multiple)
+        elif isinstance(payload, BasicNack):
+            self.tagger.nack(payload.delivery_tag, payload.multiple)
+
+    @Synchronized.synchronized
+    def publish(self, message, exchange_name=b'', routing_key=b''):
+        """
+        Schedule to have a message published.
+
+        If mode is MODE_CNPUB:
+            this function will return a Future. Future can end either with success (result will be None),
+            or exception (a plain Exception instance). Exception will happen when broker NACKs the message:
+            that, according to RabbitMQ, means an internal error in Erlang process.
+
+            Returned Future can be cancelled - this will prevent from sending the message, if it hasn't commenced yet.
+
+        If mode is MODE_NOACK:
+            this function returns None. Messages are dropped on the floor if there's no connection.
+
+        :param message: Message object to send
+        :param exchange_name: exchange name to use. Default direct exchange by default
+        :param routing_key: routing key to use
+        :return: a Future instance, or None
+        :raise Publisher.UnusablePublisher: this publisher will never work (eg. MODE_CNPUB on Non-RabbitMQ)
+        """
+        # Formulate the request
+        if self.mode == Publisher.MODE_NOACK:
+            # If we are not connected right now, drop the message on the floor and log it with DEBUG
+            if self.state != ST_ONLINE:
+                logger.debug(u'Publish request, but not connected - dropping the message')
+            else:
+                self._pub(message, exchange_name, routing_key)
+
+        elif self.mode == Publisher.MODE_CNPUB:
+            fut = Future()
+
+            #todo can optimize this not to create an object if ST_ONLINE already
+            cnpo = CnpubMessageSendOrder(message, exchange_name, routing_key, fut)
+            self.messages.append(cnpo)
+
+            if self.state == ST_ONLINE:
+                self._mode_cnpub_process_deliveries()
+
+            return fut
+        else:
+            raise Exception(u'Invalid mode')
+
+    def on_setup(self, payload):
+
+        # Assert that mode is OK
+        if self.mode == Publisher.MODE_CNPUB:
+            if PUBLISHER_CONFIRMS not in self.connection.extensions:
+                warnings.warn(u'Broker does not support publisher_confirms, refusing to start publisher',
+                              RuntimeWarning)
+                self.state = ST_OFFLINE
+                self.critically_failed = True
+                return
+
+        if isinstance(payload, ChannelOpenOk):
+            # Ok, if this has a mode different from MODE_NOACK, we need to additionally set up
+            # the functionality.
+
+            if self.mode == Publisher.MODE_CNPUB:
+                self.method_and_watch(ConfirmSelect(False), ConfirmSelectOk, self.on_setup)
+            elif self.mode == Publisher.MODE_NOACK:
+                # A-OK! Boot it.
+                self.state = ST_ONLINE
+                self.on_operational(True)
+
+        elif self.mode == Publisher.MODE_CNPUB:
+            # Because only in this case it makes sense to check for MODE_CNPUB
+            if isinstance(payload, ConfirmSelectOk):
+                # A-OK! Boot it.
+                self.state = ST_ONLINE
+                self.on_operational(True)
+
+                self.tagger = AtomicTagger()
+
+                # now we need to listen for BasicAck and BasicNack
+
+                mw = MethodWatch(self.channel_id, (BasicAck, BasicNack), self._on_cnpub_delivery)
+                mw.oneshot = False
+                self.connection.watch(mw)
+                self._mode_cnpub_process_deliveries()
diff --git a/coolamqp/attaches/utils.py b/coolamqp/attaches/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..9989ca77f47edc846386c44312849dbd9348372c
--- /dev/null
+++ b/coolamqp/attaches/utils.py
@@ -0,0 +1,233 @@
+# coding=UTF-8
+from __future__ import print_function, absolute_import, division
+import six
+import logging
+import threading
+import functools
+
+logger = logging.getLogger(__name__)
+
+
+class ConfirmableRejectable(object):
+    """
+    Protocol for objects put into AtomicTagger. You need not subclass it,
+    just support this protocol.
+    """
+
+    def confirm(self):
+        """
+        This has been ACK'd
+        :return: don't care
+        """
+
+    def reject(self):
+        """
+        This has been REJECT'd/NACK'd
+        :return: don't care
+        """
+
+class FutureConfirmableRejectable(ConfirmableRejectable):
+    """
+    A ConfirmableRejectable that can result a future (with None),
+    or Exception it with a message
+    """
+    def __init__(self, future):
+        self.future = future
+
+    def confirm(self):
+        self.future.set_result()
+
+    def reject(self):
+        self.future.set_exception(Exception())
+
+
+class AtomicTagger(object):
+    """
+    This implements a thread-safe dictionary of (integer=>ConfirmableRejectable | None),
+    used for processing delivery tags / (negative) acknowledgements.
+        - you can requisition a key. This key belongs only to you, and the whole world
+          doesn't know you have it.
+
+            delivery_tag_to_use = tagger.get_key()
+
+        - you can deposit a ConfirmableRejectable into the  tagger.
+
+            tagger.deposit(delivery_tag, message)
+
+         After you do so, this tag is subject to be acked/nacked. Read on.
+
+        - you can (multiple)(ack/nack) messages. This coresponds to multiple bit
+          used in basic.ack/basic.nack.
+
+          If this is done, your message objects (that MUST implement the
+          ConfirmableRejectable protocol) will have respective methods called.
+          These methods MUST NOT depend on particular state of locking by this
+          object.
+
+    Thread safety is implemented using reentrant locking. The lock object is a
+    threading.RLock, and you can access it at atomicTagger.lock.
+
+    Please note that delivery tags are increasing non-negative integer.
+    Therefore, X>Y implies that sending/receiving X happened after Y.
+
+    Note that key/delivery_tag of 0 has special meaning of "everything so far".
+
+    This has to be fast for most common cases. Corner cases will be resolved correctly,
+    but maybe not fast.
+    """
+
+    def __init__(self):
+        self.lock = threading.RLock()
+
+        # Protected by lock
+        self.next_tag = 1       # 0 is AMQP-reserved to mean "everything so far"
+        self.tags = []  # a list of (tag, ConfirmableRejectable)
+                        # they remain to be acked/nacked
+                        # invariant: FOR EACH i, j: (i>j) => (tags[i][0] > tags[j][0])
+
+    def deposit(self, tag, obj):
+        """
+        Put a tag into the tag list.
+
+        Putting the same tag more than one time will result in undefined behaviour.
+
+        :param tag: non-negative integer
+        :param obj: ConfirmableRejectable
+                    if you put something that isn't a ConfirmableRejectable, you won't get bitten
+                    until you call .ack() or .nack().
+        """
+        assert tag >= 0
+        opt = (tag, obj)
+
+        with self.lock:
+            if len(self.tags) == 0:
+                self.tags.append(opt)
+            elif self.tags[-1][0] < tag:
+                self.tags.append(opt)
+            else:
+                # Insert a value at place where it makes sense. Iterate from the end, because
+                # values will usually land there...
+                i = len(self.tags) - 1 # start index
+
+                while i>0:  # this will terminate at i=0
+                    if self.tags[i][0] > tag: # this means we should insert it here...
+                        break
+                    i -= 1  # previousl index
+
+                self.tags.insert(i, opt)
+
+    def __acknack(self, tag, multiple, ack):
+        """
+        :param tag: Note that 0 means "everything"
+        :param ack: True to ack, False to nack
+        """
+        # Compute limits - they go from 0 to somewhere
+        with self.lock:
+            start = 0
+            # start and stop will signify the PYTHON SLICE parameters
+
+            if tag > 0:
+
+                if multiple:
+                    # Compute the ranges
+                    for stop, opt in enumerate(self.tags):
+                        if opt[0] == tag:
+                            stop += 1 # this is exactly this tag. Adjust stop to end one further (Python slicing) and stop
+                            break
+                        if opt[0] > tag:
+                            break # We went too far, but it's OK, we don't need to bother with adjusting stop
+                    else:
+                        # List finished without breaking? That would mean the entire range!
+                        stop = len(self.tags)
+                else:
+                    # Just find that piece
+                    for index, opt in enumerate(self.tags):
+                        if opt[0] == tag:
+                            stop = index + 1
+                            break
+                    else:
+                        return  # not found!
+
+
+                if not multiple:
+                    start = stop-1
+            else:
+                # Oh, I know the range!
+                stop = len(self.tags)
+
+            print('Range computed of %s:%s' % (start, stop))
+
+            items = self.tags[start:stop]
+            del self.tags[start:stop]
+
+        for tag, cr in items:
+            if ack:
+                cr.confirm()
+            else:
+                cr.reject()
+
+    def ack(self, tag, multiple):
+        """
+        Acknowledge given objects.
+
+        If multiple, objects UP TO AND INCLUDING tag will have .confirm() called.
+        If it's false, only this precise objects will have done so.
+        It this object does not exist, nothing will happen. Acking same tag more than one time
+        is a no-op.
+
+        Things acked/nacked will be evicted from .data
+        :param tag: delivery tag to use. Note that 0 means "everything so far"
+        """
+        self.__acknack(tag, multiple, True)
+
+    def nack(self, tag, multiple):
+        """
+        Acknowledge given objects.
+
+        If multiple, objects UP TO AND INCLUDING tag will have .confirm() called.
+        If it's false, only this precise objects will have done so.
+        It this object does not exist, nothing will happen. Acking same tag more than one time
+        is a no-op.
+
+        Things acked/nacked will be evicted from .data
+        :param tag: delivery tag to use. Note that 0 means "everything so far"
+        """
+        self.__acknack(tag, multiple, False)
+
+    def get_key(self):
+        """
+        Return a key. It won't be seen here until you deposit it.
+
+        It's just yours, and you can do whatever you want with it, even drop on the floor.
+        :return: a positive integer
+        """
+        with self.lock:
+            self.next_tag += 1
+            return self.next_tag - 1
+
+
+class Synchronized(object):
+    """
+    I have a lock and can sync on it. Use like:
+
+    class Synced(Synchronized):
+
+        @synchronized
+        def mandatorily_a_instance_method(self, ...):
+            ...
+
+    """
+
+    def __init__(self):
+        self._monitor_lock = threading.Lock()
+
+    @staticmethod
+    def synchronized(fun):
+        @functools.wraps(fun)
+        def monitored(*args, **kwargs):
+            with args[0]._monitor_lock:
+                return fun(*args, **kwargs)
+
+        return monitored
+
+
diff --git a/coolamqp/backends/__init__.py b/coolamqp/backends/__init__.py
deleted file mode 100644
index 77106aaea6de30fafc10248afa1fc5d5ec8e89d0..0000000000000000000000000000000000000000
--- a/coolamqp/backends/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# coding=UTF-8
-from coolamqp.backends.pyamqp import PyAMQPBackend
-from coolamqp.backends.base import AMQPError, ConnectionFailedError, RemoteAMQPError, Cancelled
diff --git a/coolamqp/backends/base.py b/coolamqp/backends/base.py
deleted file mode 100644
index ad1b34ff77fff1029b704697ae0012218c7c2b2e..0000000000000000000000000000000000000000
--- a/coolamqp/backends/base.py
+++ /dev/null
@@ -1,145 +0,0 @@
-# coding=UTF-8
-class AMQPError(Exception):
-    """Connection errors and bawking of AMQP server"""
-    code = None
-    reply_text = 'AMQP error'
-
-    def __repr__(self):
-        return u'AMQPError()'
-
-
-class ConnectionFailedError(AMQPError):
-    """Connection to broker failed"""
-    reply_text = 'failed connecting to broker'
-
-    def __repr__(self):
-        return u'ConnectionFailedError("%s")' % map(repr, (self.reply_text, ))
-
-
-class Discarded(Exception):
-    """send() for this message had discard_on_retry"""
-
-
-class Cancelled(Exception):
-    """Cancel ordered by user"""
-
-
-class RemoteAMQPError(AMQPError):
-    """
-    Remote AMQP broker responded with an error code
-    """
-    def __init__(self, code, text=None):
-        """
-        :param code: AMQP error code
-        :param text: AMQP error text (optional)
-        """
-        AMQPError.__init__(self, text)
-        self.code = code
-        self.text = text or 'server sent back an error'
-
-    def __repr__(self):
-        return u'RemoteAMQPError(%s, %s)' % map(repr, (self.code, self.text))
-
-class AMQPBackend(object):
-    """
-    Dummy AMQP backend.
-
-    Every method may raise either ConnectionFailedError (if connection failed)
-    or RemoteAMQPError (if broker returned an error response)
-    """
-
-    def __init__(self, cluster_node, cluster_handler_thread):
-        """
-        Connects to an AMQP backend.
-        """
-        self.cluster_handler_thread = cluster_handler_thread
-
-    def process(self, max_time=10):
-        """
-        Do bookkeeping, process messages, etc.
-        :param max_time: maximum time in seconds this call can take
-        :raises ConnectionFailedError: if connection failed in the meantime
-        """
-
-    def exchange_declare(self, exchange):
-        """
-        Declare an exchange
-        :param exchange: Exchange object
-        """
-
-    def exchange_delete(self, exchange):
-        """
-        Delete an exchange
-        :param exchange: Exchange object
-        """
-
-    def queue_bind(self, queue, exchange, routing_key=''):
-        """
-        Bind a queue to an exchange
-        :param queue: Queue object
-        :param exchange: Exchange object
-        :param routing_key: routing key to use
-        """
-
-    def queue_delete(self, queue):
-        """
-        Delete a queue.
-
-        :param queue: Queue
-        """
-
-
-    def queue_declare(self, queue):
-        """
-        Declare a queue.
-
-        This will change queue's name if anonymous
-        :param queue: Queue
-        """
-
-    def basic_cancel(self, consumer_tag):
-        """
-        Cancel consuming, identified by a consumer_tag
-        :param consumer_tag: consumer_tag to cancel
-        """
-
-    def basic_consume(self, queue, no_ack=False):
-        """
-        Start consuming from a queue
-        :param queue: Queue object
-        :param no_ack: Messages will not need to be ack()ed for this queue
-        """
-
-    def basic_ack(self, delivery_tag):
-        """
-        ACK a message.
-        :param delivery_tag: delivery tag to ack
-        """
-
-    def basic_qos(self, prefetch_size, prefetch_count, global_):
-        """
-        Issue a basic.qos(prefetch_size, prefetch_count, True) against broker
-        :param prefetch_size: prefetch window size in octets
-        :param prefetch_count: prefetch window in terms of whole messages
-        """
-
-    def basic_reject(self, delivery_tag):
-        """
-        Reject a message
-        :param delivery_tag: delivery tag to reject
-        """
-
-    def basic_publish(self, message, exchange, routing_key):
-        """
-        Send a message
-        :param message: Message object to send
-        :param exchange: Exchange object to publish to
-        :param routing_key: routing key to use
-        """
-
-    def shutdown(self):
-        """
-        Close this connection.
-        This is not allowed to return anything or raise
-        """
-        self.cluster_handler_thread = None  # break GC cycles
diff --git a/coolamqp/backends/pyamqp.py b/coolamqp/backends/pyamqp.py
deleted file mode 100644
index b452bb8345f2d349c09feefeb13fd7c4e60c0889..0000000000000000000000000000000000000000
--- a/coolamqp/backends/pyamqp.py
+++ /dev/null
@@ -1,173 +0,0 @@
-# coding=UTF-8
-"""Backend using pyamqp"""
-from __future__ import division
-import amqp
-import socket
-import six
-import functools
-import logging
-from coolamqp.backends.base import AMQPBackend, RemoteAMQPError, ConnectionFailedError
-import monotonic
-
-
-logger = logging.getLogger(__name__)
-
-
-def translate_exceptions(fun):
-    """
-    Translates pyamqp's exceptions to CoolAMQP's
-
-    py-amqp's exceptions are less than intuitive, so expect many special cases
-    """
-    @functools.wraps(fun)
-    def q(*args, **kwargs):
-        try:
-            return fun(*args, **kwargs)
-        except (amqp.exceptions.ConsumerCancelled):
-            # I did not expect those here. Channel must be really bad.
-            logger.critical('Found consumer cancelled where it should not be')
-            raise ConnectionFailedError('WTF: '+(e.message if six.PY2 else e.args[0]))
-        except (amqp.RecoverableChannelError,
-                amqp.exceptions.NotFound,
-                amqp.exceptions.NoConsumers,
-                amqp.exceptions.ResourceLocked,
-                amqp.exceptions.ResourceError,
-                amqp.exceptions.ResourceLocked,
-                amqp.exceptions.AccessRefused) as e:
-            logger.warn('py-amqp: backend reports %s', repr(e))
-            raise RemoteAMQPError(e.reply_code, e.reply_text)
-        except (IOError,
-                amqp.ConnectionForced,
-                amqp.exceptions.InvalidPath,
-                amqp.IrrecoverableChannelError,
-                amqp.exceptions.UnexpectedFrame) as e:
-            logger.warn('py-amqp: backend reports %s', repr(e))
-            raise ConnectionFailedError(e.message if six.PY2 else e.args[0])
-    return q
-
-
-class PyAMQPBackend(AMQPBackend):
-    @translate_exceptions
-    def __init__(self, node, cluster_handler_thread):
-        AMQPBackend.__init__(self, node, cluster_handler_thread)
-
-        self.connection = amqp.Connection(host=node.host,
-                                          userid=node.user,
-                                          password=node.password,
-                                          virtual_host=node.virtual_host,
-                                          heartbeat=node.heartbeat or 0)
-        try:
-            self.connection.connect()     #todo what does this raise?
-        except AttributeError:
-            pass    # this does not always have to exist
-        self.channel = self.connection.channel()
-        self.channel.auto_decode = False
-        self.heartbeat = node.heartbeat or 0
-        self.last_heartbeat_at = monotonic.monotonic()
-
-    def shutdown(self):
-        AMQPBackend.shutdown(self)
-        print 'BACKEND SHUTDOWN START'
-        try:
-            self.channel.close()
-        except:
-            pass
-        try:
-            self.connection.close()
-        except:
-            pass
-        print 'BACKEND SHUTDOWN COMPLETE'
-
-    @translate_exceptions
-    def process(self, max_time=1):
-        try:
-            if self.heartbeat > 0:
-                if monotonic.monotonic() - self.last_heartbeat_at > (self.heartbeat / 2):
-                    self.connection.heartbeat_tick(rate=self.heartbeat)
-                    self.last_heartbeat_at = monotonic.monotonic()
-            self.connection.drain_events(max_time)
-        except socket.timeout as e:
-            pass
-
-    @translate_exceptions
-    def basic_cancel(self, consumer_tag):
-        self.channel.basic_cancel(consumer_tag)
-
-    @translate_exceptions
-    def basic_publish(self, message, exchange, routing_key):
-        # convert this to pyamqp's Message
-        a = amqp.Message(six.binary_type(message.body),
-                         **message.properties)
-
-        self.channel.basic_publish(a, exchange=exchange.name, routing_key=routing_key)
-
-    @translate_exceptions
-    def exchange_declare(self, exchange):
-        self.channel.exchange_declare(exchange.name, exchange.type, durable=exchange.durable,
-                                      auto_delete=exchange.auto_delete)
-
-    @translate_exceptions
-    def queue_bind(self, queue, exchange, routing_key=''):
-        self.channel.queue_bind(queue.name, exchange.name, routing_key)
-
-    @translate_exceptions
-    def basic_ack(self, delivery_tag):
-        self.channel.basic_ack(delivery_tag, multiple=False)
-
-    @translate_exceptions
-    def exchange_delete(self, exchange):
-        self.channel.exchange_delete(exchange.name)
-
-    @translate_exceptions
-    def basic_qos(self, prefetch_size, prefetch_count, global_):
-        self.channel.basic_qos(prefetch_size, prefetch_count, global_)
-
-    @translate_exceptions
-    def queue_delete(self, queue):
-        self.channel.queue_delete(queue.name)
-
-    @translate_exceptions
-    def basic_reject(self, delivery_tag):
-        self.channel.basic_reject(delivery_tag, True)
-
-    @translate_exceptions
-    def queue_declare(self, queue):
-        """
-        Declare a queue.
-
-        This will change queue's name if anonymous
-        :param queue: Queue
-        """
-        if queue.anonymous:
-            queue.name = ''
-
-        qname, mc, cc = self.channel.queue_declare(queue.name,
-                                                   durable=queue.durable,
-                                                   exclusive=queue.exclusive,
-                                                   auto_delete=queue.auto_delete)
-        if queue.anonymous:
-            queue.name = qname
-
-    @translate_exceptions
-    def basic_consume(self, queue, no_ack=False):
-        """
-        Start consuming from a queue
-        :param queue: Queue object
-        """
-        self.channel.basic_consume(queue.name,
-                                   consumer_tag=queue.consumer_tag,
-                                   exclusive=queue.exclusive,
-                                   no_ack=no_ack,
-                                   callback=self.__on_message,
-                                   on_cancel=self.__on_consumercancelled)
-
-    def __on_consumercancelled(self, consumer_tag):
-        self.cluster_handler_thread._on_consumercancelled(consumer_tag)
-
-    def __on_message(self, message):
-        assert isinstance(message.body, six.binary_type)
-        self.cluster_handler_thread._on_recvmessage(message.body,
-                                                    message.delivery_info['exchange'],
-                                                    message.delivery_info['routing_key'],
-                                                    message.delivery_info['delivery_tag'],
-                                                    message.properties)
diff --git a/coolamqp/cluster.py b/coolamqp/cluster.py
deleted file mode 100644
index 21bbb37e85f831ee7bfebb916245c30bec883ab8..0000000000000000000000000000000000000000
--- a/coolamqp/cluster.py
+++ /dev/null
@@ -1,240 +0,0 @@
-# coding=UTF-8
-import itertools
-from six.moves import queue as Queue
-from coolamqp.backends import PyAMQPBackend
-from coolamqp.backends.base import Discarded
-from coolamqp.orders import SendMessage, ConsumeQueue, DeclareExchange, CancelQueue, DeleteQueue, \
-                    DeleteExchange, SetQoS, DeclareQueue, Order
-from coolamqp.messages import Exchange
-
-
-class ClusterNode(object):
-    """
-    Definition of a reachable AMQP node.
-
-    This object is hashable.
-    """
-
-    def __init__(self, *args, **kwargs):
-        """
-        Create a cluster node definition.
-
-            a = ClusterNode(host='192.168.0.1', user='admin', password='password',
-                            virtual_host='vhost')
-
-        or
-
-            a = ClusterNode('192.168.0.1', 'admin', 'password')
-
-        Additional keyword parameters that can be specified:
-            heartbeat - heartbeat interval in seconds
-        """
-
-        self.heartbeat = kwargs.pop('heartbeat', None)
-
-        if len(kwargs) > 0:
-            # Prepare arguments for amqp.connection.Connection
-            self.host = kwargs['host']
-            self.user = kwargs['user']
-            self.password = kwargs['password']
-            self.virtual_host = kwargs.get('virtual_host', '/')
-        elif len(args) == 3:
-            self.host, self.user, self.password = args
-            self.virtual_host = '/'
-        elif len(args) == 4:
-            self.host, self.user, self.password, self.virtual_host = args
-        else:
-            raise NotImplementedError #todo implement this
-
-    def __str__(self):
-        return '%s@%s/%s' % (self.host,
-                             self.user,
-                             self.virtual_host)
-
-
-class Cluster(object):
-    """
-    Represents connection to an AMQP cluster. This internally connects only to one node, but
-    will select another one upon connection failing.
-
-    You can pass callbacks to most commands. They will also return an Order instance,
-    that you can wait for to know an operation has completed.
-
-    Callbacks are executed before Order is marked as complete (it's .result() returns), so if you do:
-
-        cluster.send(.., on_completed=hello).result()
-        bye()
-
-    hello will be called before bye is called.
-    """
-
-    def __init__(self, nodes, backend=PyAMQPBackend):
-        """
-        Construct the cluster definition
-        :param nodes: iterable of nodes to try connecting, in this order.
-            if list if exhaused, it will be started from beginning
-        :param backend: backend to use
-        """
-
-        self.backend = backend
-        self.node_to_connect_to = itertools.cycle(nodes)
-
-        self.connected = False      #: public, is connected to broker?
-
-        from .handler import ClusterHandlerThread
-        self.thread = ClusterHandlerThread(self)
-
-    def send(self, message, exchange=None, routing_key='', discard_on_fail=False, on_completed=None, on_failed=None):
-        """
-        Schedule a message to be sent.
-        :param message: Message object to send.
-        :param exchange: Exchange to use. Leave None to use the default exchange
-        :param routing_key: routing key to use
-        :param discard_on_fail: if True, then message is valid for sending ONLY with current connection.
-            Will be discarded upon fail.
-        :param on_completed: callable/0 to call when this succeeds
-        :param on_failed: callable/1 to call when this fails with AMQPError instance
-            or Cancelled instance if user cancelled this order
-            or Discarded instance if message discarded due to 'discard_on_fail'
-        :return: a Future with this order's status
-        """
-        a = SendMessage(message, exchange or Exchange.direct, routing_key,
-                        discard_on_fail=discard_on_fail,
-                        on_completed=on_completed, on_failed=on_failed)
-
-        if discard_on_fail and self.thread.backend is None:
-            o = Order()
-            o.discarded = True
-            on_failed(Discarded())
-            return o
-            # discard at once if no point in sending
-
-        self.thread.order_queue.append(a)
-        return a
-
-    def declare_exchange(self, exchange, on_completed=None, on_failed=None):
-        """
-        Declare an exchange. It will be re-declared upon reconnection.
-
-        :param exchange: Exchange to declare
-        :param on_completed: callable/0 to call when this succeeds
-        :param on_failed: callable/1 to call when this fails with AMQPError instance
-        :return: a Future with this order's status
-        """
-        a = DeclareExchange(exchange, on_completed=on_completed, on_failed=on_failed)
-        self.thread.order_queue.append(a)
-        return a
-
-    def declare_queue(self, queue, on_completed=None, on_failed=None):
-        """
-        Declares a queue.
-
-        !!!! If you declare a queue and NOT consume from it, it will not be re-declared
-        upon reconnection !!!!
-
-        :param queue: Queue to declare
-        :param on_completed: callable/0 to call when this succeeds
-        :param on_failed: callable/1 to call when this fails with AMQPError instance
-        :return: a Future with this order's status
-        """
-        a = DeclareQueue(queue, on_completed=on_completed, on_failed=on_failed)
-        self.thread.order_queue.append(a)
-        return a
-
-    def delete_exchange(self, exchange, on_completed=None, on_failed=None):
-        """
-        Delete an exchange
-        :param exchange: Exchange to delete
-        :param on_completed: callable/0 to call when this succeeds
-        :param on_failed: callable/1 to call when this fails with AMQPError instance
-        :return: a Future with this order's status
-        """
-        a = DeleteExchange(exchange, on_completed=on_completed, on_failed=on_failed)
-        self.thread.order_queue.append(a)
-        return a
-
-    def delete_queue(self, queue, on_completed=None, on_failed=None):
-        """
-        Delete a queue
-        :param queue: Queue to delete
-        :param on_completed: callable/0 to call when this succeeds
-        :param on_failed: callable/1 to call when this fails with AMQPError instance
-        :return: a Future with this order's status
-        """
-        a = DeleteQueue(queue, on_completed=on_completed, on_failed=on_failed)
-        self.thread.order_queue.append(a)
-        return a
-
-    def cancel(self, queue, on_completed=None, on_failed=None):
-        """
-        Cancel consuming from a queue
-
-        :param queue: Queue to consume from
-        :param on_completed: callable/0 to call when this succeeds
-        :param on_failed: callable/1 to call when this fails with AMQPError instance
-        :return: a Future with this order's status
-        """
-        a = CancelQueue(queue, on_completed=on_completed, on_failed=on_failed)
-        self.thread.order_queue.append(a)
-        return a
-
-    def qos(self, prefetch_window, prefetch_count, global_=True):
-        a = SetQoS(prefetch_window, prefetch_count, global_)
-        self.thread.order_queue.append(a)
-        return a
-
-    def consume(self, queue, no_ack=False, on_completed=None, on_failed=None):
-        """
-        Start consuming from a queue
-
-        This queue will be declared to the broker. If this queue has any binds
-        (.exchange field is not empty), queue will be binded to exchanges.
-
-        :param queue: Queue to consume from
-        :param on_completed: callable/0 to call when this succeeds
-        :param no_ack: if True, you will not need to call .ack() for this queue
-        :param on_failed: callable/1 to call when this fails with AMQPError instance
-        :return: a Future with this order's status
-        """
-        a = ConsumeQueue(queue, no_ack=no_ack, on_completed=on_completed, on_failed=on_failed)
-        self.thread.order_queue.append(a)
-        return a
-
-    def drain(self, wait=0):
-        """
-        Return a ClusterEvent on what happened, or None if nothing could be obtained
-        within given time
-        :param wait: Interval to wait for events.
-            Finite number to wait this much seconds before returning None
-            None to wait for infinity
-            0 to return immediately
-        :return: a ClusterEvent instance or None
-        """
-        try:
-            if wait == 0:
-                return self.thread.event_queue.get(False)
-            else:
-                return self.thread.event_queue.get(True, wait)
-        except Queue.Empty:
-            return None
-
-    def start(self):
-        """
-        Connect to the cluster.
-        :return: self
-        """
-        self.thread.start()
-        return self
-
-    def shutdown(self, complete_remaining_tasks=False):
-        """
-        Cleans everything and returns.
-
-        :param complete_remaining_tasks_tasks: if set to True, pending operations will be completed.
-            If False, thread will exit without completing them.
-            This can mean that if the cluster doesn't come up online, shutdown MAY BLOCK FOREVER.
-        """
-        self.thread.complete_remaining_upon_termination = complete_remaining_tasks
-        self.thread.terminate()
-        self.thread.join()
-        # thread closes the AMQP uplink for us
diff --git a/coolamqp/clustering/__init__.py b/coolamqp/clustering/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..337b30fa20106d995704f7210238884b0fe5c5a8
--- /dev/null
+++ b/coolamqp/clustering/__init__.py
@@ -0,0 +1,13 @@
+# coding=UTF-8
+"""
+This is the layer that you talk to. It abstracts away one (in future - more) connections
+to broker with an uniform interface.
+"""
+from __future__ import print_function, absolute_import, division
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+from coolamqp.clustering.cluster import Cluster
diff --git a/coolamqp/clustering/cluster.py b/coolamqp/clustering/cluster.py
new file mode 100644
index 0000000000000000000000000000000000000000..9d170b17679ab9a694c4078b3b73d60422a55e93
--- /dev/null
+++ b/coolamqp/clustering/cluster.py
@@ -0,0 +1,131 @@
+# coding=UTF-8
+"""
+THE object you interface with
+"""
+from __future__ import print_function, absolute_import, division
+import six
+import logging
+import warnings
+import time
+from coolamqp.uplink import ListenerThread
+from coolamqp.clustering.single import SingleNodeReconnector
+from coolamqp.attaches import Publisher, AttacheGroup, Consumer
+from coolamqp.objects import Future, Exchange
+from six.moves.queue import Queue
+
+
+logger = logging.getLogger(__name__)
+
+
+class Cluster(object):
+    """
+    Frontend for your AMQP needs.
+
+    This has ListenerThread.
+
+    Call .start() to connect to AMQP.
+    """
+
+    # Events you can be informed about
+    ST_LINK_LOST = 0            # Link has been lost
+    ST_LINK_REGAINED = 1        # Link has been regained
+
+
+    def __init__(self, nodes):
+        """
+        :param nodes: list of nodes, or a single node. For now, only one is supported.
+        :type nodes: NodeDefinition instance or a list of NodeDefinition instances
+        """
+        from coolamqp.objects import NodeDefinition
+        if isinstance(nodes, NodeDefinition):
+            nodes = [nodes]
+
+        if len(nodes) > 1:
+            raise NotImplementedError(u'Multiple nodes not supported yet')
+
+        self.listener = ListenerThread()
+        self.node, = nodes
+
+        self.attache_group = AttacheGroup()
+
+        self.snr = SingleNodeReconnector(self.node, self.attache_group, self.listener)
+
+        # Spawn a transactional publisher and a noack publisher
+        self.pub_tr = Publisher(Publisher.MODE_CNPUB)
+        self.pub_na = Publisher(Publisher.MODE_NOACK)
+
+        self.attache_group.add(self.pub_tr)
+        self.attache_group.add(self.pub_na)
+
+        self.events = Queue()   # for
+
+    def consume(self, queue, on_message=None, *args, **kwargs):
+        """
+        Start consuming from a queue.
+
+        args and kwargs will be passed to Consumer constructor (coolamqp.attaches.consumer.Consumer).
+        Don't use future_to_notify - it's done here!
+
+        Take care not to lose the Consumer object - it's the only way to cancel a consumer!
+
+        :param queue: Queue object, being consumed from right now.
+            Note that name of anonymous queue might change at any time!
+        :param on_message: callable that will process incoming messages
+                           if you leave it at None, messages will be .put into self.events
+        :type on_message: callable(ReceivedMessage instance) or None
+        :return: a tuple (Consumer instance, and a Future), that tells, when consumer is ready
+        """
+        fut = Future()
+        on_message = on_message or self.events.put_nowait
+        con = Consumer(queue, on_message, future_to_notify=fut, *args, **kwargs)
+        self.attache_group.add(con)
+        return con, fut
+
+    def publish(self, message, exchange=None, routing_key=u'', tx=False):
+        """
+        Publish a message.
+
+        :param message: Message to publish
+        :param exchange: exchange to use. Default is the "direct" empty-name exchange.
+        :type exchange: unicode/bytes (exchange name) or Exchange object.
+        :param routing_key: routing key to use
+        :param tx: Whether to publish it transactionally.
+                   If you choose so, you will receive a Future that can be used
+                   to check it broker took responsibility for this message.
+        :return: Future or None
+        """
+
+        publisher = (self.pub_tr if tx else self.pub_na)
+
+        if isinstance(exchange, Exchange):
+            exchange = exchange.name
+
+        try:
+            return publisher.publish(message, exchange.encode('utf8'), routing_key.encode('utf8'))
+        except Publisher.UnusablePublisher:
+            raise NotImplementedError(u'Sorry, this functionality if not yet implemented!')
+
+
+    def start(self, wait=True):
+        """
+        Connect to broker.
+        :param wait: block until connection is ready
+        """
+        self.listener.start()
+        self.snr.connect()
+
+        #todo not really elegant
+        if wait:
+            while not self.snr.is_connected():
+                time.sleep(0.1)
+
+    def shutdown(self, wait=True):
+        """
+        Terminate all connections, release resources - finish the job.
+        :param wait: block until this is done
+        """
+
+        self.snr.shutdown()
+        self.listener.terminate()
+        if wait:
+            self.listener.join()
diff --git a/coolamqp/clustering/single.py b/coolamqp/clustering/single.py
new file mode 100644
index 0000000000000000000000000000000000000000..9fbb9e2683e8dd4c71ce7e977edee500c0cbd47a
--- /dev/null
+++ b/coolamqp/clustering/single.py
@@ -0,0 +1,40 @@
+# coding=UTF-8
+from __future__ import print_function, absolute_import, division
+import six
+import logging
+
+from coolamqp.uplink import Connection
+
+logger = logging.getLogger(__name__)
+
+
+class SingleNodeReconnector(object):
+    """
+    Connection to one node. It will do it's best to remain alive.
+    """
+
+    def __init__(self, node_def, attache_group, listener_thread):
+        self.listener_thread = listener_thread
+        self.node_def = node_def
+        self.attache_group = attache_group
+        self.connection = None
+
+    def is_connected(self):
+        return self.connection is not None
+
+    def connect(self):
+        assert self.connection is None
+
+        # Initiate connecting - this order is very important!
+        self.connection = Connection(self.node_def, self.listener_thread)
+        self.attache_group.attach(self.connection)
+        self.connection.start()
+        self.connection.add_finalizer(self.on_fail)
+
+    def on_fail(self):
+        self.connection = None
+        self.connect()
+
+    def shutdown(self):
+        """Close this connection"""
+        self.connection.send(None)
diff --git a/coolamqp/events.py b/coolamqp/events.py
deleted file mode 100644
index 0dbe85087adab5dc48779423a53bafe8842bb5e6..0000000000000000000000000000000000000000
--- a/coolamqp/events.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# coding=UTF-8
-"""
-Events emitted by Cluster
-"""
-
-
-class ClusterEvent(object):
-    """Base class for events emitted by cluster"""
-
-
-class ConnectionDown(ClusterEvent):
-    """Connection to broker has been broken"""
-
-
-class ConnectionUp(ClusterEvent):
-    """Connection to broker has been (re)established"""
-
-    def __init__(self, initial=False):
-        self.initial = initial  #: public, is this first connection up in this cluster ever?
-
-
-class MessageReceived(ClusterEvent):
-    """A message has been received from the broker"""
-    def __init__(self, message):
-        """
-        :param message: ReceivedMessage instance
-        """
-        self.message = message
-
-
-class ConsumerCancelled(ClusterEvent):
-    """
-    Broker cancelled a consumer of ours.
-    This is also generated in response to cancelling consumption from a queue
-    """
-
-    BROKER_CANCEL = 0
-    REFUSED_ON_RECONNECT = 1
-    USER_CANCEL = 2
-
-    def __init__(self, queue, reason):
-        """
-        :param queue: Queue whose consumer was cancelled
-        :param reason: Reason why the consumer was cancelled
-            ConsumerCancelled.BROKER_CANCEL - broker informed us about cancelling
-            ConsumerCancelled.REFUSED_ON_RECONNECT - during a reconnect, I tried to consume an exclusive
-                                                     queue and got ACCESS_REFUSED.
-                                                     These messages will arrive between ConnectionDown and
-                                                     ConnectionUp.
-            ConsumedCancelled.USER_CANCEL - user called cluster.cancel()
-        """
-        self.queue = queue
-        self.reason = reason
diff --git a/coolamqp/exceptions.py b/coolamqp/exceptions.py
new file mode 100644
index 0000000000000000000000000000000000000000..f4ba6aa34585e02e437d65e82aa130bbdbf58908
--- /dev/null
+++ b/coolamqp/exceptions.py
@@ -0,0 +1,51 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+
+
+class CoolAMQPError(Exception):
+    """Base class for CoolAMQP errors"""
+
+
+
+class ConsumerError(CoolAMQPError):
+    """
+    Exceptions passed to consumer callables.
+    """
+
+
+class UplinkLost(ConsumerError):
+    """
+    Uplink to the network has been lost, I am working on regaining connectivity
+    right now.
+    """
+
+class ConsumerCancelled(CoolAMQPError):
+    """
+    The consumer has been cancelled
+    """
+
+
+class AMQPError(CoolAMQPError):
+    """
+    Base class for errors received from AMQP server
+    """
+    def __init__(self, *args):
+        """
+
+        :param args: can be either reply_code, reply_text, class_id, method_id
+                     or a ConnectionClose/ChannelClose.
+        """
+        from coolamqp.framing.definitions import ConnectionClose, ChannelClose
+
+        if isinstance(args[0], (ConnectionClose, ChannelClose)):
+            self.reply_code = args[0].reply_code
+            self.reply_text = args[0].reply_text
+            self.class_id = args[0].class_id
+            self.method_id = args[0].method_id
+        else:
+            assert len(args) == 4
+            self.reply_code, self.reply_text, self.class_id, self.method_id = args
+
+
+class ResourceLocked(AMQPError):
+    """Shorthand to catch that stuff easier"""
diff --git a/coolamqp/framing/__init__.py b/coolamqp/framing/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..7e413d8fc8f284c7d85e772ebf519058ee1af9b8
--- /dev/null
+++ b/coolamqp/framing/__init__.py
@@ -0,0 +1,10 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+
+"""
+Definitions of framing.
+Mechanisms for serialization/deserialization of AMQP framing and other types.
+
+
+definitions.py is machine-generated from AMQP specification.
+"""
\ No newline at end of file
diff --git a/coolamqp/framing/base.py b/coolamqp/framing/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..3ee0f7a13b4f537482d26cd7a3fdca3ad69ac6cc
--- /dev/null
+++ b/coolamqp/framing/base.py
@@ -0,0 +1,181 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+
+import logging
+import struct
+
+logger = logging.getLogger(__name__)
+
+
+AMQP_HELLO_HEADER = b'AMQP\x00\x00\x09\x01'
+
+
+# name => (length|None, struct ID|None, reserved-field-value : for struct if structable, bytes else, length of default)
+BASIC_TYPES = {u'bit': (None, None, "0", None),          # special case
+               u'octet': (1, 'B', "b'\\x00'", 1),
+               u'short': (2, 'H', "b'\\x00\\x00'", 2),
+               u'long': (4, 'I', "b'\\x00\\x00\\x00\\x00'", 4),
+               u'longlong': (8, 'Q', "b'\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'", 8),
+               u'timestamp': (8, 'Q', "b'\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'", 8),
+               u'table': (None, None, "b'\\x00\\x00\\x00\\x00'", 4),          # special case
+               u'longstr': (None, None, "b'\\x00\\x00\\x00\\x00'", 4),        # special case
+               u'shortstr': (None, None, "b'\\x00'", 1),                        # special case
+               }
+
+DYNAMIC_BASIC_TYPES = (u'table', u'longstr', u'shortstr')
+
+
+class AMQPFrame(object):        # base class for framing
+    FRAME_TYPE = None   # override me!
+
+    def __init__(self, channel):
+        self.channel = channel
+
+    def write_to(self, buf):
+        """
+        Write a complete frame to buffer
+
+        This writes type and channel ID.
+        """
+        # DO NOT UNCOMMENT buf.write(struct.pack('!BH', self.FRAME_TYPE, self.channel))
+        raise NotImplementedError('Please write the frame type and channel in child classes, its faster that way ')
+
+    @staticmethod
+    def unserialize(channel, payload_as_buffer):
+        """
+        Unserialize from a buffer.
+        Buffer starts at frame's own payload - type, channel and size was already obtained.
+        Payload does not contain FRAME_EMD.
+        AMQPHeartbeatFrame does not have to implement this.
+        """
+        raise NotImplementedError('Override me')
+
+    def get_size(self):
+        """
+        Return size of this frame, in bytes, from frame type to frame_end
+        :return: int
+        """
+        raise NotImplementedError('Override me')
+
+
+class AMQPPayload(object):
+    """Payload is something that can write itself to bytes,
+    or at least provide a buffer to do it."""
+
+    def write_to(self, buf):
+        """
+        Emit itself into a buffer, from length to FRAME_END
+
+        :param buf: buffer to write to (will be written using .write)
+        """
+
+    def get_size(self):
+        """
+        Return size of this payload
+        :return: int
+        """
+        raise NotImplementedError()
+
+
+class AMQPClass(object):
+    """An AMQP class"""
+
+
+class AMQPContentPropertyList(object):
+    """
+    A class is intmately bound with content and content properties
+    """
+    PROPERTIES = []
+
+    @staticmethod
+    def zero_property_flags(property_flags):
+        """
+        Given a binary property_flags, set all bit properties to 0.
+
+        This leaves us with a canonical representation, that can be used
+        in obtaining a particular property list
+        :param property_flags: binary
+        :return: binary
+        """
+        # this is a default implementation.
+        # compiler should emit it's own when the content property list has a
+        # possible bit field
+        return property_flags
+
+    def write_to(self, buf):
+        """Serialize itself (flags + values) to a buffer"""
+        raise Exception('This is an abstract method')
+
+    @staticmethod
+    def from_buffer(self, buf, start_offset):
+        """
+        Return an instance of self, loaded from a buffer.
+
+        This does not have to return length, because it is always passed exactly enough of a buffer.
+
+        Buffer HAS TO start at property_flags
+        """
+        raise Exception('This is an abstract method')
+
+    def get_size(self):
+        """
+        How long is property_flags + property_values
+        :return: int
+        """
+        raise Exception('This is an abstract method')
+
+
+class AMQPMethodPayload(AMQPPayload):
+    RESPONSE_TO = None
+    REPLY_WITH = []
+    FIELDS = []
+
+    def write_to(self, buf):
+        """
+        Write own content to target buffer - starting from LENGTH, ending on FRAME_END
+        :param buf: target buffer
+        """
+        from coolamqp.framing.definitions import FRAME_END
+
+        if self.IS_CONTENT_STATIC:
+            buf.write(self.STATIC_CONTENT)
+        else:
+            buf.write(struct.pack('!I', self.get_size()+2))
+            buf.write(self.BINARY_HEADER)
+            self.write_arguments(buf)
+            buf.write(chr(FRAME_END))
+
+    def get_size(self):
+        """
+        Calculate the size of this frame.
+
+        :return: int, size of argument section
+        """
+        if self.IS_CONTENT_STATIC:
+            return len(self.STATIC_CONTENT)-4-4-1  # minus length, class, method, frame_end
+
+        raise NotImplementedError()
+
+    def write_arguments(self, buf):
+        """
+        Write the argument portion of this frame into buffer.
+
+        :param buf: buffer to write to
+        :return: how many bytes written
+        :raise ValueError: some field here is invalid!
+        """
+        raise NotImplementedError()
+
+    @staticmethod
+    def from_buffer(buf, offset):
+        """
+        Construct this frame from a buffer
+
+        :param buf: a buffer to construct the frame from
+        :type buf: buffer or memoryview
+        :param offset: offset the argument portion begins at
+        :type offset: int
+        :return: tuple of (an instance of %s, amount of bytes consumed as int)
+        :raise ValueError: invalid data
+        """
+        raise NotImplementedError('')
diff --git a/coolamqp/framing/compilation/__init__.py b/coolamqp/framing/compilation/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..c932491a7c750218a3d62edb6656415c22a09be1
--- /dev/null
+++ b/coolamqp/framing/compilation/__init__.py
@@ -0,0 +1,5 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+"""
+Function that compiles amqp0-9-1.xml to definitions.py
+"""
\ No newline at end of file
diff --git a/coolamqp/framing/compilation/compile_definitions.py b/coolamqp/framing/compilation/compile_definitions.py
new file mode 100644
index 0000000000000000000000000000000000000000..ec0022e019e19b7d7dddef07abb0773b4b16a95c
--- /dev/null
+++ b/coolamqp/framing/compilation/compile_definitions.py
@@ -0,0 +1,467 @@
+from __future__ import division
+
+import collections
+import math
+import struct
+from xml.etree import ElementTree
+
+import six
+from coolamqp.framing.base import BASIC_TYPES
+
+from coolamqp.framing.compilation.utilities import get_constants, get_classes, get_domains, \
+    name_class, format_method_class_name, format_field_name, ffmt, to_docstring, pythonify_name, to_code_binary, \
+    frepr, get_size
+
+TYPE_TRANSLATOR = {
+        'shortstr': 'binary type (max length 255)',
+        'longstr': 'binary type',
+        'table': 'table. See coolamqp.uplink.framing.field_table',
+        'bit': 'bool',
+        'octet': 'int, 8 bit unsigned',
+        'short': 'int, 16 bit unsigned',
+        'long': 'int, 32 bit unsigned',
+        'longlong': 'int, 64 bit unsigned',
+        'timestamp': '64 bit signed POSIX timestamp (in seconds)',
+}
+
+def compile_definitions(xml_file='resources/amqp0-9-1.extended.xml', out_file='coolamqp/framing/definitions.py'):
+    """parse resources/amqp-0-9-1.xml into """
+
+    xml = ElementTree.parse(xml_file)
+    out = open(out_file, 'wb')
+
+    out.write('''# coding=UTF-8
+from __future__ import print_function, absolute_import
+"""
+A Python version of the AMQP machine-readable specification.
+
+Generated automatically by CoolAMQP from AMQP machine-readable specification.
+See coolamqp.uplink.framing.compilation for the tool
+
+AMQP is copyright (c) 2016 OASIS
+CoolAMQP is copyright (c) 2016 DMS Serwis s.c.
+"""
+
+import struct, collections, warnings, logging, six
+
+from coolamqp.framing.base import AMQPClass, AMQPMethodPayload, AMQPContentPropertyList
+from coolamqp.framing.field_table import enframe_table, deframe_table, frame_table_size
+from coolamqp.framing.compilation.content_property import compile_particular_content_property_list_class
+
+logger = logging.getLogger(__name__)
+
+Field = collections.namedtuple('Field', ('name', 'type', 'basic_type', 'reserved'))
+
+''')
+
+    def line(data, *args, **kwargs):
+        out.write(ffmt(data, *args, sane=True))
+
+    # Output core ones
+    FRAME_END = None
+    con_classes = collections.defaultdict(list)
+    line('# Core constants\n')
+    for constant in get_constants(xml):
+        if pythonify_name(constant.name) == 'FRAME_END':
+            FRAME_END = constant.value
+        g = ffmt('%s = %s', pythonify_name(constant.name), constant.value)
+        line(g)
+        if constant.docs:
+            lines = constant.docs.split('\n')
+            line(' # %s\n', lines[0])
+            if len(lines) > 1:
+                for ln in lines[1:]:
+                    line(u' '*len(g))
+                    line(u' # %s\n', ln)
+        else:
+            line('\n')
+
+        if constant.kind:
+            con_classes[constant.kind].append(pythonify_name(constant.name))
+
+    for constant_kind, constants in con_classes.items():
+        line('\n%s = [%s]', pythonify_name(constant_kind), u', '.join(constants))
+
+    # get domains
+    domain_to_basic_type = {}
+    line('\n\n\nDOMAIN_TO_BASIC_TYPE = {\n')
+    for domain in get_domains(xml):
+        line(u'    %s: %s,\n', frepr(domain.name), frepr(None if domain.elementary else domain.type))
+        domain_to_basic_type[domain.name] = domain.type
+
+    line('}\n')
+
+    class_id_to_contentpropertylist = {}
+
+    # below are stored as strings!
+    methods_that_are_reply_reasons_for = {}   # eg. ConnectionOpenOk: ConnectionOk
+    methods_that_are_replies_for = {}    # eg. ConnectionOk: [ConnectionOpenOk]
+
+    # Output classes
+    for cls in get_classes(xml):
+
+        cls = cls._replace(properties=[p._replace(basic_type=domain_to_basic_type[p.type]) for p in cls.properties])
+
+        line('''\nclass %s(AMQPClass):
+    """
+    %s
+    """
+    NAME = %s
+    INDEX = %s
+
+''',
+             name_class(cls.name), to_docstring(None, cls.docs), frepr(cls.name), cls.index)
+
+        if len(cls.properties) > 0:
+            class_id_to_contentpropertylist[cls.index] = name_class(cls.name)+'ContentPropertyList'
+
+            line('''\nclass %sContentPropertyList(AMQPContentPropertyList):
+    """
+    %s
+    """
+    FIELDS = [
+''',
+
+                 name_class(cls.name), to_docstring(None, cls.docs), frepr(cls.name), cls.index, name_class(cls.name))
+
+            is_static = all(property.basic_type not in ('table', 'longstr', 'shortstr') for property in cls.properties)
+
+            for property in cls.properties:
+                if property.basic_type == 'bit':
+                    raise ValueError('bit properties are not supported!'
+                                     )
+                line('        Field(%s, %s, %s, %s),\n', frepr(property.name), frepr(property.type), frepr(property.basic_type), repr(property.reserved))
+            line('''    ]
+    # A dictionary from a zero property list to a class typized with
+    # some fields
+    PARTICULAR_CLASSES = {}
+\n''',
+                 name_class(cls.name))
+
+            if any(prop.basic_type == 'bit' for prop in cls.properties):
+                raise NotImplementedError('I should emit a custom zero_property_list staticmethod :(')
+            line(u'''    def __new__(self, **kwargs):
+        """
+        Return a property list.
+''')
+            property_strs = []
+
+            my_props = [prop for prop in cls.properties if (not prop.reserved)]
+            for property in my_props:
+                line('        :param %s: %s\n', format_field_name(property.name), property.label)
+                line('        :type %s: %s (AMQP as %s)\n', format_field_name(property.name), TYPE_TRANSLATOR[property.basic_type], property.basic_type)
+            line('        """\n')
+            zpf_len = int(math.ceil(len(cls.properties) // 15))
+
+            first_byte = True   # in 2-byte group
+            piece_index = 7 # from 7 downto 0
+            fields_remaining = len(cls.properties)
+
+            byte_chunk = []
+            line(u'        zpf = bytearray([\n')
+
+            for field in cls.properties:
+                # a bit
+                if piece_index > 0:
+                    if field.reserved or field.basic_type == 'bit':
+                        pass # zero anyway
+                    else:
+                        byte_chunk.append(u"(('%s' in kwargs) << %s)" % (format_field_name(field.name), piece_index))
+                    piece_index -= 1
+                else:
+                    if first_byte:
+                        if field.reserved or field.basic_type == 'bit':
+                            pass    # zero anyway
+                        else:
+                            byte_chunk.append(u"int('%s' in kwargs)" % (format_field_name(field.name),))
+                    else:
+                       # this is the "do we need moar flags" section
+                        byte_chunk.append(u"kwargs['%s']" % (
+                            int(fields_remaining > 1)
+                        ))
+
+                    # Emit the byte
+                    line(u'            %s,\n', u' | '.join(byte_chunk))
+                    byte_chunk = []
+                    first_byte = not first_byte
+                    piece_index = 7
+                fields_remaining -= 1
+
+            if len(byte_chunk) > 0:
+                line(u'            %s\n', u' | '.join(byte_chunk))    # We did not finish
+
+            line(u'        ])\n        zpf = six.binary_type(zpf)\n')
+            line(u'''
+        if zpf in %s.PARTICULAR_CLASSES:
+            warnings.warn(u"""You could go faster.
+
+        If you know in advance what properties you will be using, use typized constructors like
+
+            # runs once
+            my_type = BasicContentPropertyList.typize('content_type', 'content_encoding')
+            # runs many times
+            props = my_type('text/plain', 'utf8')
+
+        instead of
+
+            # runs many times
+            props = BasicContentPropertyList(content_type='text/plain', content_encoding='utf8')
+
+        This way you will be faster.
+
+        If you do not know in advance what properties you will be using, it is correct to use
+        this constructor.
+        """)
+
+            return %s.PARTICULAR_CLASSES[zpf](**kwargs)
+        else:
+            logger.debug('Property field (%s:%d) not seen yet, compiling', repr(zpf))
+            c = compile_particular_content_property_list_class(zpf, %s.FIELDS)
+            %s.PARTICULAR_CLASSES[zpf] = c
+            return c(**kwargs)
+'''.replace('%s', name_class(cls.name) + 'ContentPropertyList').replace('%d', '%s'))
+
+            line(u'''
+    @staticmethod
+    def typize(*fields):
+    ''')
+            line(u'    zpf = bytearray([\n')
+
+            first_byte = True  # in 2-byte group
+            piece_index = 7  # from 7 downto 0
+            fields_remaining = len(cls.properties)
+            byte_chunk = []
+
+            for field in cls.properties:
+                # a bit
+                if piece_index > 0:
+                    if field.reserved or field.basic_type == 'bit':
+                        pass # zero
+                    else:
+                        byte_chunk.append(u"(('%s' in fields) << %s)" % (format_field_name(field.name), piece_index))
+                    piece_index -= 1
+                else:
+                    if first_byte:
+                        if field.reserved or field.basic_type == 'bit':
+                            pass #zero
+                        else:
+                            byte_chunk.append(u"int('%s' in kwargs)" % (format_field_name(field.name),))
+                    else:
+                        # this is the "do we need moar flags" section
+                        byte_chunk.append(u"kwargs['%s']" % (
+                            int(fields_remaining > 1)
+                        ))
+
+                    # Emit the byte
+                    line(u'        %s,\n', u' | '.join(byte_chunk))
+                    byte_chunk = []
+                    first_byte = not first_byte
+                    piece_index = 7
+                fields_remaining -= 1
+
+            if len(byte_chunk) > 0:
+                line(u'        %s\n', u' | '.join(byte_chunk))  # We did not finish
+
+            line(u'''        ])
+        zpf = six.binary_type(zpf)
+        if zpf in %s.PARTICULAR_CLASSES:
+            return %s.PARTICULAR_CLASSES[zpf]
+        else:
+            logger.debug('Property field (%s:%d) not seen yet, compiling', repr(zpf))
+            c = compile_particular_content_property_list_class(zpf, %s.FIELDS)
+            %s.PARTICULAR_CLASSES[zpf] = c
+            return c
+'''.replace("%s", name_class(cls.name) + 'ContentPropertyList').replace('%d', '%s'))
+
+            line(u'''
+    @staticmethod
+    def from_buffer(buf, offset):
+        """
+        Return a content property list instance unserialized from
+        buffer, so that buf[offset] marks the start of property flags
+        """
+        # extract property flags
+        pfl = 2
+        while ord(buf[offset + pfl - 1]) & 1:
+            pfl += 2
+        zpf = %s.zero_property_flags(buf[offset:offset+pfl])
+        if zpf in %s.PARTICULAR_CLASSES:
+            return %s.PARTICULAR_CLASSES[zpf].from_buffer(buf, offset)
+        else:
+            logger.debug('Property field (%s:%d) not seen yet, compiling', repr(zpf))
+            c = compile_particular_content_property_list_class(zpf, %s.FIELDS)
+            %s.PARTICULAR_CLASSES[zpf] = c
+            return c.from_buffer(buf, offset)
+
+'''.replace('%s', name_class(cls.name) + 'ContentPropertyList').replace("%d", "%s"))
+
+        # ============================================ Do methods for this class
+        for method in cls.methods:
+            full_class_name = '%s%s' % (name_class(cls.name), format_method_class_name(method.name))
+
+            # annotate types
+            method.fields = [field._replace(basic_type=domain_to_basic_type[field.type]) for field in method.fields]
+
+            non_reserved_fields = [field for field in method.fields if not field.reserved]
+
+            is_static = method.is_static()
+            if is_static:
+                static_size = get_size(method.fields)
+
+            is_content_static = len([f for f in method.fields if not f.reserved]) == 0
+
+            if len(non_reserved_fields) == 0:
+                slots = u''
+            else:
+                slots = (u', '.join(map(lambda f: frepr(format_field_name(f.name)), non_reserved_fields)))+u', '
+
+            line('''\nclass %s(AMQPMethodPayload):
+    """
+    %s
+    """
+    __slots__ = (%s)
+
+    NAME = %s
+
+    INDEX = (%s, %s)          # (Class ID, Method ID)
+    BINARY_HEADER = %s      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = %s, %s
+
+    IS_SIZE_STATIC = %s     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = %s  # this means that argument part has always the same content
+''',
+
+                 full_class_name,
+                 to_docstring(method.label, method.docs),
+                 slots,
+                 frepr(cls.name + '.' + method.name),
+                 frepr(cls.index), frepr(method.index),
+                 to_code_binary(struct.pack("!HH", cls.index, method.index)),
+                 repr(method.sent_by_client),
+                 repr(method.sent_by_server),
+                 repr(is_static),
+                 repr(is_content_static)
+                 )
+
+            _namify = lambda x: name_class(cls.name) + format_method_class_name(x)
+
+            methods_that_are_replies_for[full_class_name] = []
+            for response in method.response:
+                methods_that_are_reply_reasons_for[_namify(response)] = full_class_name
+                methods_that_are_replies_for[full_class_name].append(_namify(response))
+
+            if is_content_static:
+
+                line('''    STATIC_CONTENT = %s  # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END
+''',
+                     to_code_binary(struct.pack('!LHH', static_size + 4, cls.index, method.index) + \
+                                    method.get_static_body() + \
+                                    struct.pack('!B', FRAME_END)))
+
+            # fields
+            if len(method.fields) > 0:
+                line('\n    # See constructor pydoc for details\n')
+                line('    FIELDS = [ \n')
+
+                for field in method.fields:
+                    line('        Field(%s, %s, %s, reserved=%s),\n', frepr(field.name), frepr(field.type), frepr(field.basic_type), repr(field.reserved))
+
+                line('    ]\n')
+
+
+
+            # constructor
+            line('''\n    def __init__(%s):
+        """
+        Create frame %s
+''',
+                 u', '.join(['self'] + [format_field_name(field.name) for field in non_reserved_fields]),
+                 cls.name + '.' + method.name,
+                 )
+
+            if len(non_reserved_fields) > 0:
+                line('\n')
+
+            for field in non_reserved_fields:
+                if (field.label is not None) or (field.docs is not None):
+                    line('        :param %s: %s\n', format_field_name(field.name),
+                         to_docstring(field.label, field.docs, prefix=12, blank=False))
+
+
+
+                line('        :type %s: %s (%s in AMQP)\n', format_field_name(field.name), TYPE_TRANSLATOR[field.basic_type], field.type)
+
+            line('        """\n')
+
+            for field in non_reserved_fields:
+                line('        self.%s = %s\n', format_field_name(field.name), format_field_name(field.name))
+
+            if len(non_reserved_fields) == 0:
+                line('\n')
+
+            # end
+            if not is_content_static:
+                from coolamqp.framing.compilation.textcode_fields import get_serializer, get_counter, get_from_buffer
+                line('\n    def write_arguments(self, buf):\n')
+                line(get_serializer(method.fields, 'self.', 2))
+
+                line('    def get_size(self):\n')
+                line(get_counter(method.fields, 'self.', 2))
+
+            line('''\n    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+''')
+
+            line(get_from_buffer(method.fields, '', 2, remark=(method.name == 'deliver')))
+            line("        return %s(%s)",
+                 full_class_name,
+                 u', '.join(format_field_name(field.name) for field in method.fields if not field.reserved))
+
+            line('\n\n')
+
+        # Get me a dict - (classid, methodid) => class of method
+        dct = {}
+        for cls in get_classes(xml):
+            for method in cls.methods:
+                dct[((cls.index, method.index))] = '%s%s' % (name_class(cls.name), format_method_class_name(method.name))
+
+    line('\nIDENT_TO_METHOD = {\n')
+    for k, v in dct.items():
+        line('    %s: %s,\n', repr(k), v)
+    line('}\n\n')
+
+    line('\nBINARY_HEADER_TO_METHOD = {\n')
+    for k, v in dct.items():
+        line('    %s: %s,\n', to_code_binary(struct.pack('!HH', *k)), v)
+    line('}\n\n')
+
+    line('\nCLASS_ID_TO_CONTENT_PROPERTY_LIST = {\n')
+    for k,v in class_id_to_contentpropertylist.items():
+        line('    %s: %s,\n', k, v)
+    line('}\n\n')
+
+    line(u'''# Methods that are sent as replies to other methods, ie. ConnectionOpenOk: ConnectionOpen
+# if a method is NOT a reply, it will not be in this dict
+# a method may be a reply for AT MOST one method
+REPLY_REASONS_FOR = {\n''')
+    for k,v in methods_that_are_reply_reasons_for.items():
+        line(u'    %s: %s,\n' % (k, v))
+
+    line(u'''}
+
+# Methods that are replies for other, ie. ConnectionOpenOk: ConnectionOpen
+# a method may be a reply for ONE or NONE other methods
+# if a method has no replies, it will have an empty list as value here
+REPLIES_FOR= {\n''')
+
+    for k,v in methods_that_are_replies_for.items():
+        line(u'    %s: [%s],\n' % (k, u', '.join(map(str, v))))
+    line(u'}\n')
+
+    out.close()
+
+
+if __name__ == '__main__':
+    compile_definitions()
diff --git a/coolamqp/framing/compilation/content_property.py b/coolamqp/framing/compilation/content_property.py
new file mode 100644
index 0000000000000000000000000000000000000000..73732e443079dd9fa83f59b1d7679ea3f2ef1c36
--- /dev/null
+++ b/coolamqp/framing/compilation/content_property.py
@@ -0,0 +1,122 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+"""Generate serializers/unserializers/length getters for given property_flags"""
+import six
+import struct
+import logging
+from coolamqp.framing.compilation.textcode_fields import get_counter, get_from_buffer, get_serializer
+from coolamqp.framing.base import AMQPContentPropertyList
+from coolamqp.framing.field_table import enframe_table, deframe_table, frame_table_size
+
+
+logger = logging.getLogger(__name__)
+
+
+def _compile_particular_content_property_list_class(zpf, fields):
+    """
+    Compile a particular content property list.
+
+    Particularity stems from
+    :param zpf: zero property list, as bytearray
+    :param fields: list of all possible fields in this content property
+    """
+    from coolamqp.framing.compilation.utilities import format_field_name
+
+    if any(field.basic_type == 'bit' for field in fields):
+        return u"raise NotImplementedError('I don't support bits in properties yet')"
+
+    # Convert ZPF to a list of [if_exists::bool]
+    even = True
+    zpf_bits = []
+    for q in bytearray(zpf):
+        p = bin(q)[2:]
+        p = (u'0' * (8 - len(p))) + p
+
+        if not even:
+            p = p[:7]
+
+        zpf_bits.extend(map(lambda x: bool(int(x)), p))
+
+    zpf_length = len(zpf)
+
+    # 1 here does not mean that field is present. All bit fields are present, but 0 in a ZPF. Fix this.
+    zpf_bits = [zpf_bit or field.type == 'bit' for zpf_bit, field in zip(zpf_bits, fields)]
+
+    mod = [u'''class ParticularContentTypeList(AMQPContentPropertyList):
+    """
+    For fields:
+''']
+
+    for field in fields:
+        mod.append(u'    * %s::%s' % (format_field_name(field.name), field.type))
+        if field.reserved:
+            mod.append(u' (reserved)')
+        mod.append(u'\n')
+
+    x = repr(six.binary_type(zpf))
+    if not x.startswith('b'):
+        x = 'b'+x
+
+    present_fields = [field for field, present in zip(fields, zpf_bits) if present]
+
+    mod.append(u'''
+    """
+''')
+
+    if len(present_fields) == 0:
+        slots = u''
+    else:
+        slots = (u', '.join((u"u'%s'" % format_field_name(field.name) for field in present_fields)))+u', '
+
+    mod.append(u'''
+    __slots__ = (%s)
+''' % slots)
+
+    mod.append(u'''
+    # A value for property flags that is used, assuming all bit fields are FALSE (0)
+    ZERO_PROPERTY_FLAGS = %s
+''' % (x, ))
+
+    if len(present_fields) > 0:
+        mod.append(u'''
+    def __init__(self, %s):
+''' % (u', '.join(format_field_name(field.name) for field in present_fields)))
+
+    for field in present_fields:
+        mod.append(u'        self.%s = %s\n'.replace(u'%s', format_field_name(field.name)))
+
+    # Let's do write_to
+    mod.append(u'\n    def write_to(self, buf):\n')
+    mod.append(u'        buf.write(')
+    repred_zpf = repr(zpf)
+    if not zpf.startswith('b'):
+        repred_zpf = 'b' + repred_zpf
+    mod.append(repred_zpf)
+    mod.append(u')\n')
+
+    mod.append(get_serializer(present_fields, prefix='self.', indent_level=2))
+
+    # from_buffer
+    # note that non-bit values
+    mod.append(u'    @classmethod\n')
+    mod.append(u'    def from_buffer(cls, buf, start_offset):\n        offset = start_offset + %s\n' % (zpf_length, ))
+    mod.append(get_from_buffer(
+        present_fields
+        , prefix='', indent_level=2))
+    mod.append(u'        return cls(%s)\n' %
+               u', '.join(format_field_name(field.name) for field in present_fields))
+
+
+    # get_size
+    mod.append(u'\n    def get_size(self):\n')
+    mod.append(get_counter(present_fields, prefix='self.', indent_level=2)[:-1])    # skip eol
+    mod.append(u' + %s\n' % (zpf_length, ))   # account for pf length
+
+    return u''.join(mod)
+
+
+def compile_particular_content_property_list_class(zpf, fields):
+    q = _compile_particular_content_property_list_class(zpf, fields)
+    logger.debug('Compiling\n%s', q)
+    exec(q)
+    return ParticularContentTypeList
diff --git a/coolamqp/framing/compilation/textcode_fields.py b/coolamqp/framing/compilation/textcode_fields.py
new file mode 100644
index 0000000000000000000000000000000000000000..3a1722031e875d33ce39bb86cfc4dd9ba9ec85a0
--- /dev/null
+++ b/coolamqp/framing/compilation/textcode_fields.py
@@ -0,0 +1,252 @@
+# coding=UTF-8
+"""
+Return Python code used to serialize/unserialize/get_size of lists of fields.
+
+If you are going to paste the code you get here, note you nede to paste it into
+a module that has following ok:
+
+    * local variables denoted with a list of Field (namedtuple) with optional prefix exist
+    * function header was already emitted
+    * indent_level is in multiple of fours
+    * following imports exists:
+        * import struct
+        * from coolamqp.framing.field_table import enframe_table, deframe_table, frame_table_size
+    * local variables buf and offset exist
+    * local variable start_offset can be created
+
+"""
+from __future__ import absolute_import, division, print_function
+import math
+
+from coolamqp.framing.base import BASIC_TYPES, DYNAMIC_BASIC_TYPES
+from coolamqp.framing.compilation.utilities import format_field_name, get_size
+
+def get_counter(fields, prefix='', indent_level=2):
+    """
+    Emit code that counts how long this struct is.
+
+    :param fields: list of Field instances
+    :param prefix: pass "self." is inside a class
+    :param indent_level: amount of tabs
+    :return: block of code that does that
+    """
+
+    parts = []
+    accumulator = 0
+    bits = 0
+    for field in fields:
+        bt = field.basic_type
+        nam = prefix+format_field_name(field.name)
+
+        if (bits > 0) and (bt != 'bit'):  # sync bits if not
+            accumulator += int(math.ceil(bits / 8))
+            bits = 0
+
+        if field.basic_type == 'bit':
+            bits += 1
+        elif field.reserved:
+            accumulator += BASIC_TYPES[field.basic_type][3]
+        elif BASIC_TYPES[bt][0] is not None:
+            accumulator += BASIC_TYPES[field.basic_type][0]
+        elif bt == 'shortstr':
+            parts.append('len('+nam+')')
+            accumulator += 1
+        elif bt == 'longstr':
+            parts.append('len(' + nam + ')')
+            accumulator += 4
+        elif bt == 'table':
+            parts.append('frame_table_size(' + nam + ')')
+            accumulator += 0    # because frame_table_size accounts for that 4 leading bytes
+        else:
+            raise Exception()
+
+    if bits > 0:  # sync bits
+        accumulator += int(math.ceil(bits / 8))
+
+    return (u'    '*indent_level)+u'return '+(u' + '.join([str(accumulator)]+parts))+u'\n'
+
+
+def get_from_buffer(fields, prefix='', indent_level=2, remark=False):
+    """
+    Emit code that collects values from buf:offset, updating offset as progressing.
+    :param remark: BE FUCKING VERBOSE! #DEBUG
+    """
+    code = []
+    def emit(fmt, *args):
+        args = list(args)
+        code.append(u'    '*indent_level)
+        assert fmt.count('%s') == len(args)
+        for arg in args:
+            fmt = fmt.replace('%s', str(arg), 1)
+        code.append(fmt)
+        code.append('\n')
+
+
+    # actually go and load it
+
+    bits = []
+    ln = {'ln': 0}  # so I can modify from outside
+    to_struct = []
+
+    def emit_bits():
+        if len(bits) == 0:
+            return
+        if remark:
+            print('Bits are being banged')
+        if all(n == '_' for n in bits):
+            # everything is reserved, lol
+            emit('offset += 1')
+        else:
+            to_struct.append(('_bit', 'B'))
+            emit_structures(dont_do_bits=True)
+
+            for multiplier, bit in enumerate(bits):
+                if bit != '_':
+                    emit("%s = bool(_bit >> %s)", bit, multiplier)
+            emit('offset += 1')
+
+        del bits[:]
+
+    def emit_structures(dont_do_bits=False):
+        if not dont_do_bits:
+            emit_bits()
+        if len(to_struct) == 0:
+            return
+        fffnames = [a for a, b in to_struct if a != u'_'] # skip reserved
+        ffffmts = [b for a, b in to_struct]
+        emit("%s, = struct.unpack_from('!%s', buf, offset)", u', '.join(fffnames), u''.join(ffffmts))
+        emit("offset += %s", ln['ln'])
+        ln['ln'] = 0
+        del to_struct[:]
+
+    for field in fields:
+        fieldname = prefix+format_field_name(field.name)
+
+        if (len(bits) > 0) and (field.basic_type != u'bit'):
+            emit_bits()
+
+        if remark:
+            print('Doing', fieldname, 'of type', field.basic_type)
+
+        # offset is current start
+        # length is length to read
+        if BASIC_TYPES[field.basic_type][0] is not None:
+            # static type shit has
+
+            assert len(bits) == 0
+
+            if field.reserved:
+                to_struct.append((u'_', '%sx' % (BASIC_TYPES[field.basic_type][0],)))
+            else:
+                to_struct.append((fieldname, BASIC_TYPES[field.basic_type][1]))
+
+            ln['ln'] += BASIC_TYPES[field.basic_type][0]
+        elif field.basic_type == u'bit':
+            bits.append('_' if field.reserved else fieldname)
+        elif field.basic_type == u'table': # oh my god
+            emit_structures()
+
+            assert len(bits) == 0
+            assert len(to_struct) == 0
+
+            emit("%s, delta = deframe_table(buf, offset)", fieldname)
+            emit("offset += delta")
+        else:   # longstr or shortstr
+            f_q, f_l = ('L', 4) if field.basic_type == u'longstr' else ('B', 1)
+            to_struct.append(('s_len', f_q))
+            ln['ln'] += f_l
+            emit_structures()
+            if field.reserved:
+                emit("offset += s_len # reserved field!")
+            else:
+                emit("%s = buf[offset:offset+s_len]", fieldname)
+                emit("offset += s_len")
+
+        # check bits for overflow
+        if len(bits) == 8:
+            emit_bits()
+
+    emit_structures()
+
+    return u''.join(code)
+
+
+def get_serializer(fields, prefix='', indent_level=2):
+    """
+    Emit code that serializes the fields into buf at offset
+
+    :param fields: list of Field instances
+    :param prefix: pass "self." is inside a class
+    :return: block of code that does that
+    """
+    code = []
+
+    def emit(fmt, *args):
+        args = list(args)
+        code.append(u'    '*indent_level)
+        while len(args) > 0:
+            fmt = fmt.replace('%s', args[0], 1)
+            del args[0]
+        code.append(fmt)
+        code.append('\n')
+
+    formats = []
+    format_args = []
+    bits = []
+
+    def emit_bits():
+        p = []
+        formats.append('B')
+        if all(bit_name == 'False' for bit_name in bits):
+            format_args.append('0')
+        else:
+            for bit_name, modif in zip(bits, range(8)):
+                if bit_name != 'False':
+                    p.append('('+bit_name+' << %s)' % (modif, ))  # yes you can << bools
+            format_args.append(u' | '.join(p))
+        del bits[:]
+
+    def emit_single_struct_pack():
+        emit("buf.write(struct.pack('!%s', %s))", u''.join(formats), u', '.join(format_args))
+        del formats[:]
+        del format_args[:]
+
+    for field in fields:
+        nam = prefix+format_field_name(field.name)
+
+        if (len(bits) == 8) or ((len(bits) > 0) and field.basic_type != 'bit'):
+            emit_bits()
+
+        if field.basic_type == 'bit':
+            if field.reserved:
+                bits.append("False")
+            else:
+                bits.append(nam)
+        elif field.reserved:
+            # Just pasta
+            emit('buf.write(%s)', BASIC_TYPES[field.basic_type][2])
+        else:
+            if field.basic_type in ('shortstr', 'longstr'):
+                formats.append('B' if field.basic_type == 'shortstr' else 'I')
+                format_args.append('len('+nam+')')
+                emit_single_struct_pack()
+                emit('buf.write(%s)', nam)
+            elif field.basic_type == 'table':
+                if len(bits) > 0:
+                    emit_bits()
+                if len(formats) > 0:
+                    emit_single_struct_pack()
+                emit('enframe_table(buf, %s)', nam)
+            else:
+                formats.append(BASIC_TYPES[field.basic_type][1])
+                format_args.append(nam)
+
+    if len(bits) > 0:
+        emit_bits()
+    if len(formats) > 0:
+        emit_single_struct_pack()
+
+    emit('')    # eol
+
+    return u''.join(code)
+
diff --git a/coolamqp/framing/compilation/utilities.py b/coolamqp/framing/compilation/utilities.py
new file mode 100644
index 0000000000000000000000000000000000000000..2504eab9b23dcf1f20d5889bbfafb47e62d884df
--- /dev/null
+++ b/coolamqp/framing/compilation/utilities.py
@@ -0,0 +1,222 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+
+import math
+from collections import namedtuple
+
+import six
+
+from coolamqp.framing.base import BASIC_TYPES, DYNAMIC_BASIC_TYPES
+
+# docs may be None
+
+
+
+Constant = namedtuple('Constant', ('name', 'value', 'kind', 'docs'))  # kind is AMQP constant class # value is int
+Field = namedtuple('Field', ('name', 'type', 'label', 'docs', 'reserved', 'basic_type')) # reserved is bool
+Method = namedtuple('Method', ('name', 'synchronous', 'index', 'label', 'docs', 'fields', 'response',
+                               'sent_by_client', 'sent_by_server', 'constant'))
+        # synchronous is bool, constant is bool
+        # repponse is a list of method.name
+Class_ = namedtuple('Class_', ('name', 'index', 'docs', 'methods', 'properties'))   # label is int
+Domain = namedtuple('Domain', ('name', 'type', 'elementary'))   # elementary is bool
+
+
+class Method(object):
+    def __init__(self, name, synchronous, index, label, docs, fields, response, sent_by_client, sent_by_server):
+        self.name = name
+        self.synchronous = synchronous
+        self.index = index
+        self.fields = fields
+        self.response = response
+        self.label = label
+        self.docs = docs
+        self.sent_by_client = sent_by_client
+        self.sent_by_server = sent_by_server
+
+        self.constant = len([f for f in self.fields if not f.reserved]) == 0
+
+    def get_static_body(self):  # only arguments part
+        body = []
+        bits = 0
+        for field in self.fields:
+
+            if bits > 0 and field.basic_type != 'bit':
+                body.append(b'\x00' * math.ceil(bits / 8))
+                bits = 0
+
+            if field.basic_type == 'bit':
+                bits += 1
+            else:
+                body.append(eval(BASIC_TYPES[field.basic_type][2]))
+        return b''.join(body)
+
+    def is_static(self, domain_to_type=None):    # is size constant?
+        for field in self.fields:
+            if field.basic_type in DYNAMIC_BASIC_TYPES:
+                return False
+        return True
+
+
+def get_size(fields):   # assume all fields have static length
+    size = 0
+    bits = 0
+    for field in fields:
+
+        if (bits > 0) and (field.basic_type != 'bit'):  # sync bits
+            size += int(math.ceil(bits / 8))
+            bits = 0
+
+        if BASIC_TYPES[field.basic_type][0] is None:
+            if field.basic_type == 'bit':
+                bits += 1
+            else:
+                size += len(BASIC_TYPES[field.basic_type][2])   # default minimum entry
+        else:
+            size += BASIC_TYPES[field.basic_type][0]
+
+    if bits > 0:    # sync bits
+        size += int(math.ceil(bits / 8))
+
+    return size
+
+
+def get_docs(elem):
+    for kid in elem.getchildren():
+
+        if kid.tag == 'rule':
+            return get_docs(kid)
+
+        s = kid.text.strip().split('\n')
+        return u'\n'.join([u.strip() for u in s if len(u.strip()) > 0])
+
+    return None
+
+
+def for_domain(elem):
+    a = elem.attrib
+    return Domain(six.text_type(a['name']), a['type'], a['type'] == a['name'])
+
+
+def for_field(elem): # for <field> in <method>
+    a = elem.attrib
+    return Field(six.text_type(a['name']), a['domain'] if 'domain' in a else a['type'],
+                 a.get('label', None),
+                 get_docs(elem),
+                 a.get('reserved', '0') == '1',
+                 None)
+
+def for_method(elem):       # for <method>
+    a = elem.attrib
+    return Method(six.text_type(a['name']), bool(int(a.get('synchronous', '0'))), int(a['index']), a.get('label', None), get_docs(elem),
+                  [for_field(fie) for fie in elem.getchildren() if fie.tag == 'field'],
+                  [e.attrib['name'] for e in elem.findall('response')],
+                  # if chassis=server that means server has to accept it
+                  any([e.attrib.get('name', '') == 'server' for e in elem.getchildren() if e.tag == 'chassis']),
+                  any([e.attrib.get('name', '') == 'client' for e in elem.getchildren() if e.tag == 'chassis'])
+                  )
+
+def for_class(elem):        # for <class>
+    a = elem.attrib
+    methods = sorted([for_method(me) for me in elem.getchildren() if me.tag == 'method'], key=lambda m: (m.name.strip('-')[0], -len(m.response)))
+    return Class_(six.text_type(a['name']), int(a['index']), get_docs(elem) or a['label'], methods,
+                  [for_field(e) for e in elem.getchildren() if e.tag == 'field'])
+
+def for_constant(elem):     # for <constant>
+    a = elem.attrib
+    return Constant(a['name'], int(a['value']), a.get('class', ''), get_docs(elem))
+
+
+def get_constants(xml):
+    return [for_constant(e) for e in xml.findall('constant')]
+
+def get_classes(xml):
+    return [for_class(e) for e in xml.findall('class')]
+
+def get_domains(xml):
+    return [for_domain(e) for e in xml.findall('domain')]
+
+
+def as_unicode(callable):
+    def roll(*args, **kwargs):
+        return six.text_type(callable(*args, **kwargs))
+    return roll
+
+def to_dict_by_name(list_of_things):
+    return dict((a.name, a) for a in list_of_things)
+
+@as_unicode
+def name_class(classname):
+    """Change AMQP class name to Python class name"""
+    return classname.capitalize()
+
+@as_unicode
+def format_method_class_name(methodname):
+    if '-' in methodname:
+        i = methodname.find('-')
+        return methodname[0:i].capitalize() + methodname[i+1].upper() + methodname[i+2:]
+    else:
+        return methodname.capitalize()
+
+@as_unicode
+def format_field_name(field):
+    if field in (u'global', u'type'):
+        field = field + '_'
+    return field.replace('-', '_')
+
+def frepr(p, sop=six.text_type):
+    if isinstance(p, basestring):
+        p = sop(p)
+    s = repr(p)
+
+    if isinstance(p, basestring) and not s.startswith('u'):
+        return ('u' if sop == six.text_type else 'b') + s
+    else:
+        return s
+
+def to_code_binary(p):
+    body = []
+    for q in p:
+        z = (hex(ord(q))[2:].upper())
+        if len(z) == 1:
+            z = u'0' + z
+        body.append(u'\\x' + z)
+    return u"b'"+(u''.join(body))+u"'"
+
+def pythonify_name(p):
+    return p.strip().replace('-', '_').upper()
+
+def try_to_int(p):
+    try:
+        return int(p)
+    except ValueError:
+        return p
+
+def to_docstring(label, doc, prefix=4, blank=True): # output a full docstring section
+    label = [] if label is None else [label]
+    doc = [] if doc is None else [q.strip() for q in doc.split(u'\n') if len(q.strip()) > 0]
+    pre = u' '*prefix
+
+    doc = label + doc
+
+    if len(doc) == 0:
+        return u''
+
+    doc[0] = doc[0].capitalize()
+
+    if len(doc) == 1:
+        return doc[0]
+
+    doc = filter(lambda p: len(p.strip()) > 0, doc)
+
+    if blank:
+        doc = [doc[0], u''] + doc[1:]
+
+    f = (u'\n'.join(pre + lin for lin in doc))[prefix:]
+    return f
+
+def ffmt(data, *args, **kwargs):
+    for arg in args:
+        op = str if kwargs.get('sane', True) else frepr
+        data = data.replace('%s', op(arg), 1)
+    return data
diff --git a/coolamqp/framing/definitions.py b/coolamqp/framing/definitions.py
new file mode 100644
index 0000000000000000000000000000000000000000..83251c5b6541bb2b74b99ee088a19f4f696e7c08
--- /dev/null
+++ b/coolamqp/framing/definitions.py
@@ -0,0 +1,4063 @@
+# coding=UTF-8
+from __future__ import print_function, absolute_import
+"""
+A Python version of the AMQP machine-readable specification.
+
+Generated automatically by CoolAMQP from AMQP machine-readable specification.
+See coolamqp.uplink.framing.compilation for the tool
+
+AMQP is copyright (c) 2016 OASIS
+CoolAMQP is copyright (c) 2016 DMS Serwis s.c.
+"""
+
+import struct, collections, warnings, logging, six
+
+from coolamqp.framing.base import AMQPClass, AMQPMethodPayload, AMQPContentPropertyList
+from coolamqp.framing.field_table import enframe_table, deframe_table, frame_table_size
+from coolamqp.framing.compilation.content_property import compile_particular_content_property_list_class
+
+logger = logging.getLogger(__name__)
+
+Field = collections.namedtuple('Field', ('name', 'type', 'basic_type', 'reserved'))
+
+# Core constants
+FRAME_METHOD = 1
+FRAME_HEADER = 2
+FRAME_BODY = 3
+FRAME_HEARTBEAT = 8
+FRAME_MIN_SIZE = 4096
+FRAME_END = 206
+REPLY_SUCCESS = 200 # Indicates that the method completed successfully. This reply code is
+                    # reserved for future use - the current protocol design does not use positive
+                    # confirmation and reply codes are sent only in case of an error.
+CONTENT_TOO_LARGE = 311 # The client attempted to transfer content larger than the server could accept
+                        # at the present time. The client may retry at a later time.
+NO_CONSUMERS = 313 # When the exchange cannot deliver to a consumer when the immediate flag is
+                   # set. As a result of pending data on the queue or the absence of any
+                   # consumers of the queue.
+CONNECTION_FORCED = 320 # An operator intervened to close the connection for some reason. The client
+                        # may retry at some later date.
+INVALID_PATH = 402 # The client tried to work with an unknown virtual host.
+ACCESS_REFUSED = 403 # The client attempted to work with a server entity to which it has no
+                     # access due to security settings.
+NOT_FOUND = 404 # The client attempted to work with a server entity that does not exist.
+RESOURCE_LOCKED = 405 # The client attempted to work with a server entity to which it has no
+                      # access because another client is working with it.
+PRECONDITION_FAILED = 406 # The client requested a method that was not allowed because some precondition
+                          # failed.
+FRAME_ERROR = 501 # The sender sent a malformed frame that the recipient could not decode.
+                  # This strongly implies a programming error in the sending peer.
+SYNTAX_ERROR = 502 # The sender sent a frame that contained illegal values for one or more
+                   # fields. This strongly implies a programming error in the sending peer.
+COMMAND_INVALID = 503 # The client sent an invalid sequence of frames, attempting to perform an
+                      # operation that was considered invalid by the server. This usually implies
+                      # a programming error in the client.
+CHANNEL_ERROR = 504 # The client attempted to work with a channel that had not been correctly
+                    # opened. This most likely indicates a fault in the client layer.
+UNEXPECTED_FRAME = 505 # The peer sent a frame that was not expected, usually in the context of
+                       # a content header and body.  This strongly indicates a fault in the peer's
+                       # content processing.
+RESOURCE_ERROR = 506 # The server could not complete the method because it lacked sufficient
+                     # resources. This may be due to the client creating too many of some type
+                     # of entity.
+NOT_ALLOWED = 530 # The client tried to work with some entity in a manner that is prohibited
+                  # by the server, due to security settings or by some other criteria.
+NOT_IMPLEMENTED = 540 # The client tried to use functionality that is not implemented in the
+                      # server.
+INTERNAL_ERROR = 541 # The server could not complete the method because of an internal error.
+                     # The server may require intervention by an operator in order to resume
+                     # normal operations.
+
+HARD_ERROR = [CONNECTION_FORCED, INVALID_PATH, FRAME_ERROR, SYNTAX_ERROR, COMMAND_INVALID, CHANNEL_ERROR, UNEXPECTED_FRAME, RESOURCE_ERROR, NOT_ALLOWED, NOT_IMPLEMENTED, INTERNAL_ERROR]
+SOFT_ERROR = [CONTENT_TOO_LARGE, NO_CONSUMERS, ACCESS_REFUSED, NOT_FOUND, RESOURCE_LOCKED, PRECONDITION_FAILED]
+
+
+DOMAIN_TO_BASIC_TYPE = {
+    u'class-id': u'short',
+    u'consumer-tag': u'shortstr',
+    u'delivery-tag': u'longlong',
+    u'exchange-name': u'shortstr',
+    u'method-id': u'short',
+    u'no-ack': u'bit',
+    u'no-local': u'bit',
+    u'no-wait': u'bit',
+    u'path': u'shortstr',
+    u'peer-properties': u'table',
+    u'queue-name': u'shortstr',
+    u'redelivered': u'bit',
+    u'message-count': u'long',
+    u'reply-code': u'short',
+    u'reply-text': u'shortstr',
+    u'bit': None,
+    u'octet': None,
+    u'short': None,
+    u'long': None,
+    u'longlong': None,
+    u'shortstr': None,
+    u'longstr': None,
+    u'timestamp': None,
+    u'table': None,
+}
+
+class Connection(AMQPClass):
+    """
+    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.
+    """
+    NAME = u'connection'
+    INDEX = 10
+
+
+class ConnectionBlocked(AMQPMethodPayload):
+    """
+    This method indicates that a connection has been blocked
+    
+    and does not accept new publishes.
+    """
+    __slots__ = (u'reason', )
+
+    NAME = u'connection.blocked'
+
+    INDEX = (10, 60)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x0A\x00\x3C'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, True
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'reason', u'shortstr', u'shortstr', reserved=False),
+    ]
+
+    def __init__(self, reason):
+        """
+        Create frame connection.blocked
+
+        :type reason: binary type (max length 255) (shortstr in AMQP)
+        """
+        self.reason = reason
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!B', len(self.reason)))
+        buf.write(self.reason)
+        
+    def get_size(self):
+        return 1 + len(self.reason)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        reason = buf[offset:offset+s_len]
+        offset += s_len
+        return ConnectionBlocked(reason)
+
+
+class ConnectionClose(AMQPMethodPayload):
+    """
+    Request a connection close
+    
+    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
+    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.
+    """
+    __slots__ = (u'reply_code', u'reply_text', u'class_id', u'method_id', )
+
+    NAME = u'connection.close'
+
+    INDEX = (10, 50)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x0A\x00\x32'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, True
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'reply-code', u'reply-code', u'short', reserved=False),
+        Field(u'reply-text', u'reply-text', u'shortstr', reserved=False),
+        Field(u'class-id', u'class-id', u'short', reserved=False),
+        Field(u'method-id', u'method-id', u'short', reserved=False),
+    ]
+
+    def __init__(self, reply_code, reply_text, class_id, method_id):
+        """
+        Create frame connection.close
+
+        :type reply_code: int, 16 bit unsigned (reply-code in AMQP)
+        :type reply_text: binary type (max length 255) (reply-text in AMQP)
+        :param class_id: Failing method class
+            When the close is provoked by a method exception, this is the class of the
+            method.
+        :type class_id: int, 16 bit unsigned (class-id in AMQP)
+        :param method_id: Failing method id
+            When the close is provoked by a method exception, this is the ID of the method.
+        :type method_id: int, 16 bit unsigned (method-id in AMQP)
+        """
+        self.reply_code = reply_code
+        self.reply_text = reply_text
+        self.class_id = class_id
+        self.method_id = method_id
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!HB', self.reply_code, len(self.reply_text)))
+        buf.write(self.reply_text)
+        buf.write(struct.pack('!HH', self.class_id, self.method_id))
+        
+    def get_size(self):
+        return 7 + len(self.reply_text)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        reply_code, s_len, = struct.unpack_from('!HB', buf, offset)
+        offset += 3
+        reply_text = buf[offset:offset+s_len]
+        offset += s_len
+        class_id, method_id, = struct.unpack_from('!HH', buf, offset)
+        offset += 4
+        return ConnectionClose(reply_code, reply_text, class_id, method_id)
+
+
+class ConnectionCloseOk(AMQPMethodPayload):
+    """
+    Confirm a connection close
+    
+    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.
+    """
+    __slots__ = ()
+
+    NAME = u'connection.close-ok'
+
+    INDEX = (10, 51)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x0A\x00\x33'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, True
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = True  # this means that argument part has always the same content
+    STATIC_CONTENT = b'\x00\x00\x00\x04\x00\x0A\x00\x33\xCE'  # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END
+
+    def __init__(self):
+        """
+        Create frame connection.close-ok
+        """
+
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        return ConnectionCloseOk()
+
+
+class ConnectionOpen(AMQPMethodPayload):
+    """
+    Open connection to virtual host
+    
+    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.
+    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.
+    """
+    __slots__ = (u'virtual_host', )
+
+    NAME = u'connection.open'
+
+    INDEX = (10, 40)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x0A\x00\x28'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'virtual-host', u'path', u'shortstr', reserved=False),
+        Field(u'reserved-1', u'shortstr', u'shortstr', reserved=True),
+        Field(u'reserved-2', u'bit', u'bit', reserved=True),
+    ]
+
+    def __init__(self, virtual_host):
+        """
+        Create frame connection.open
+
+        :param virtual_host: Virtual host name
+            The name of the virtual host to work with.
+        :type virtual_host: binary type (max length 255) (path in AMQP)
+        """
+        self.virtual_host = virtual_host
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!B', len(self.virtual_host)))
+        buf.write(self.virtual_host)
+        buf.write(b'\x00')
+        buf.write(struct.pack('!B', 0))
+        
+    def get_size(self):
+        return 3 + len(self.virtual_host)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        virtual_host = buf[offset:offset+s_len]
+        offset += s_len
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        offset += s_len # reserved field!
+        offset += 1
+        return ConnectionOpen(virtual_host)
+
+
+class ConnectionOpenOk(AMQPMethodPayload):
+    """
+    Signal that connection is ready
+    
+    This method signals to the client that the connection is ready for use.
+    """
+    __slots__ = ()
+
+    NAME = u'connection.open-ok'
+
+    INDEX = (10, 41)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x0A\x00\x29'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = True  # this means that argument part has always the same content
+    STATIC_CONTENT = b'\x00\x00\x00\x04\x00\x0A\x00\x29\x00\xCE'  # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'reserved-1', u'shortstr', u'shortstr', reserved=True),
+    ]
+
+    def __init__(self):
+        """
+        Create frame connection.open-ok
+        """
+
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        offset += s_len # reserved field!
+        return ConnectionOpenOk()
+
+
+class ConnectionStart(AMQPMethodPayload):
+    """
+    Start connection negotiation
+    
+    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
+    which the client can use for authentication.
+    """
+    __slots__ = (u'version_major', u'version_minor', u'server_properties', u'mechanisms', u'locales', )
+
+    NAME = u'connection.start'
+
+    INDEX = (10, 10)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x0A\x00\x0A'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'version-major', u'octet', u'octet', reserved=False),
+        Field(u'version-minor', u'octet', u'octet', reserved=False),
+        Field(u'server-properties', u'peer-properties', u'table', reserved=False),
+        Field(u'mechanisms', u'longstr', u'longstr', reserved=False),
+        Field(u'locales', u'longstr', u'longstr', reserved=False),
+    ]
+
+    def __init__(self, version_major, version_minor, server_properties, mechanisms, locales):
+        """
+        Create frame connection.start
+
+        :param version_major: Protocol major version
+            The major version number can take any value from 0 to 99 as defined in the
+            AMQP specification.
+        :type version_major: int, 8 bit unsigned (octet in AMQP)
+        :param version_minor: Protocol minor version
+            The minor version number can take any value from 0 to 99 as defined in the
+            AMQP specification.
+        :type version_minor: int, 8 bit unsigned (octet in AMQP)
+        :param server_properties: Server properties
+            The properties SHOULD contain at least these fields: "host", specifying the
+            server host name or address, "product", giving the name of the server product,
+            "version", giving the name of the server version, "platform", giving the name
+            of the operating system, "copyright", if appropriate, and "information", giving
+            other general information.
+        :type server_properties: table. See coolamqp.uplink.framing.field_table (peer-properties in AMQP)
+        :param mechanisms: Available security mechanisms
+            A list of the security mechanisms that the server supports, delimited by spaces.
+        :type mechanisms: binary type (longstr in AMQP)
+        :param locales: Available message locales
+            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.
+        :type locales: binary type (longstr in AMQP)
+        """
+        self.version_major = version_major
+        self.version_minor = version_minor
+        self.server_properties = server_properties
+        self.mechanisms = mechanisms
+        self.locales = locales
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!BB', self.version_major, self.version_minor))
+        enframe_table(buf, self.server_properties)
+        buf.write(struct.pack('!I', len(self.mechanisms)))
+        buf.write(self.mechanisms)
+        buf.write(struct.pack('!I', len(self.locales)))
+        buf.write(self.locales)
+        
+    def get_size(self):
+        return 10 + frame_table_size(self.server_properties) + len(self.mechanisms) + len(self.locales)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        version_major, version_minor, = struct.unpack_from('!BB', buf, offset)
+        offset += 2
+        server_properties, delta = deframe_table(buf, offset)
+        offset += delta
+        s_len, = struct.unpack_from('!L', buf, offset)
+        offset += 4
+        mechanisms = buf[offset:offset+s_len]
+        offset += s_len
+        s_len, = struct.unpack_from('!L', buf, offset)
+        offset += 4
+        locales = buf[offset:offset+s_len]
+        offset += s_len
+        return ConnectionStart(version_major, version_minor, server_properties, mechanisms, locales)
+
+
+class ConnectionSecure(AMQPMethodPayload):
+    """
+    Security mechanism challenge
+    
+    The SASL protocol works by exchanging challenges and responses until both peers have
+    received sufficient information to authenticate each other. This method challenges
+    the client to provide more information.
+    """
+    __slots__ = (u'challenge', )
+
+    NAME = u'connection.secure'
+
+    INDEX = (10, 20)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x0A\x00\x14'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'challenge', u'longstr', u'longstr', reserved=False),
+    ]
+
+    def __init__(self, challenge):
+        """
+        Create frame connection.secure
+
+        :param challenge: Security challenge data
+            Challenge information, a block of opaque binary data passed to the security
+            mechanism.
+        :type challenge: binary type (longstr in AMQP)
+        """
+        self.challenge = challenge
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!I', len(self.challenge)))
+        buf.write(self.challenge)
+        
+    def get_size(self):
+        return 4 + len(self.challenge)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!L', buf, offset)
+        offset += 4
+        challenge = buf[offset:offset+s_len]
+        offset += s_len
+        return ConnectionSecure(challenge)
+
+
+class ConnectionStartOk(AMQPMethodPayload):
+    """
+    Select security mechanism and locale
+    
+    This method selects a SASL security mechanism.
+    """
+    __slots__ = (u'client_properties', u'mechanism', u'response', u'locale', )
+
+    NAME = u'connection.start-ok'
+
+    INDEX = (10, 11)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x0A\x00\x0B'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'client-properties', u'peer-properties', u'table', reserved=False),
+        Field(u'mechanism', u'shortstr', u'shortstr', reserved=False),
+        Field(u'response', u'longstr', u'longstr', reserved=False),
+        Field(u'locale', u'shortstr', u'shortstr', reserved=False),
+    ]
+
+    def __init__(self, client_properties, mechanism, response, locale):
+        """
+        Create frame connection.start-ok
+
+        :param client_properties: Client properties
+            The properties SHOULD contain at least these fields: "product", giving the name
+            of the client product, "version", giving the name of the client version, "platform",
+            giving the name of the operating system, "copyright", if appropriate, and
+            "information", giving other general information.
+        :type client_properties: table. See coolamqp.uplink.framing.field_table (peer-properties in AMQP)
+        :param mechanism: Selected security mechanism
+            A single security mechanisms selected by the client, which must be one of those
+            specified by the server.
+        :type mechanism: binary type (max length 255) (shortstr in AMQP)
+        :param response: Security response data
+            A block of opaque data passed to the security mechanism. The contents of this
+            data are defined by the SASL security mechanism.
+        :type response: binary type (longstr in AMQP)
+        :param locale: Selected message locale
+            A single message locale selected by the client, which must be one of those
+            specified by the server.
+        :type locale: binary type (max length 255) (shortstr in AMQP)
+        """
+        self.client_properties = client_properties
+        self.mechanism = mechanism
+        self.response = response
+        self.locale = locale
+
+    def write_arguments(self, buf):
+        enframe_table(buf, self.client_properties)
+        buf.write(struct.pack('!B', len(self.mechanism)))
+        buf.write(self.mechanism)
+        buf.write(struct.pack('!I', len(self.response)))
+        buf.write(self.response)
+        buf.write(struct.pack('!B', len(self.locale)))
+        buf.write(self.locale)
+        
+    def get_size(self):
+        return 6 + frame_table_size(self.client_properties) + len(self.mechanism) + len(self.response) + len(self.locale)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        client_properties, delta = deframe_table(buf, offset)
+        offset += delta
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        mechanism = buf[offset:offset+s_len]
+        offset += s_len
+        s_len, = struct.unpack_from('!L', buf, offset)
+        offset += 4
+        response = buf[offset:offset+s_len]
+        offset += s_len
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        locale = buf[offset:offset+s_len]
+        offset += s_len
+        return ConnectionStartOk(client_properties, mechanism, response, locale)
+
+
+class ConnectionSecureOk(AMQPMethodPayload):
+    """
+    Security mechanism response
+    
+    This method attempts to authenticate, passing a block of SASL data for the security
+    mechanism at the server side.
+    """
+    __slots__ = (u'response', )
+
+    NAME = u'connection.secure-ok'
+
+    INDEX = (10, 21)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x0A\x00\x15'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'response', u'longstr', u'longstr', reserved=False),
+    ]
+
+    def __init__(self, response):
+        """
+        Create frame connection.secure-ok
+
+        :param response: Security response data
+            A block of opaque data passed to the security mechanism. The contents of this
+            data are defined by the SASL security mechanism.
+        :type response: binary type (longstr in AMQP)
+        """
+        self.response = response
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!I', len(self.response)))
+        buf.write(self.response)
+        
+    def get_size(self):
+        return 4 + len(self.response)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!L', buf, offset)
+        offset += 4
+        response = buf[offset:offset+s_len]
+        offset += s_len
+        return ConnectionSecureOk(response)
+
+
+class ConnectionTune(AMQPMethodPayload):
+    """
+    Propose connection tuning parameters
+    
+    This method proposes a set of connection configuration values to the client. The
+    client can accept and/or adjust these.
+    """
+    __slots__ = (u'channel_max', u'frame_max', u'heartbeat', )
+
+    NAME = u'connection.tune'
+
+    INDEX = (10, 30)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x0A\x00\x1E'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'channel-max', u'short', u'short', reserved=False),
+        Field(u'frame-max', u'long', u'long', reserved=False),
+        Field(u'heartbeat', u'short', u'short', reserved=False),
+    ]
+
+    def __init__(self, channel_max, frame_max, heartbeat):
+        """
+        Create frame connection.tune
+
+        :param channel_max: Proposed maximum channels
+            Specifies highest channel number that the server permits.  Usable channel numbers
+            are in the range 1..channel-max.  Zero indicates no specified limit.
+        :type channel_max: int, 16 bit unsigned (short in AMQP)
+        :param frame_max: Proposed maximum frame size
+            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
+            that the server does not impose any specific limit but may reject very large
+            frames if it cannot allocate resources for them.
+        :type frame_max: int, 32 bit unsigned (long in AMQP)
+        :param heartbeat: Desired heartbeat delay
+            The delay, in seconds, of the connection heartbeat that the server wants.
+            Zero means the server does not want a heartbeat.
+        :type heartbeat: int, 16 bit unsigned (short in AMQP)
+        """
+        self.channel_max = channel_max
+        self.frame_max = frame_max
+        self.heartbeat = heartbeat
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!HIH', self.channel_max, self.frame_max, self.heartbeat))
+        
+    def get_size(self):
+        return 8
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        channel_max, frame_max, heartbeat, = struct.unpack_from('!HIH', buf, offset)
+        offset += 8
+        return ConnectionTune(channel_max, frame_max, heartbeat)
+
+
+class ConnectionTuneOk(AMQPMethodPayload):
+    """
+    Negotiate connection tuning parameters
+    
+    This method sends the client's connection tuning parameters to the server.
+    Certain fields are negotiated, others provide capability information.
+    """
+    __slots__ = (u'channel_max', u'frame_max', u'heartbeat', )
+
+    NAME = u'connection.tune-ok'
+
+    INDEX = (10, 31)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x0A\x00\x1F'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'channel-max', u'short', u'short', reserved=False),
+        Field(u'frame-max', u'long', u'long', reserved=False),
+        Field(u'heartbeat', u'short', u'short', reserved=False),
+    ]
+
+    def __init__(self, channel_max, frame_max, heartbeat):
+        """
+        Create frame connection.tune-ok
+
+        :param channel_max: Negotiated maximum channels
+            The maximum total number of channels that the client will use per connection.
+        :type channel_max: int, 16 bit unsigned (short in AMQP)
+        :param frame_max: Negotiated maximum frame size
+            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
+            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
+            be broken into frames of arbitrary size.
+        :type frame_max: int, 32 bit unsigned (long in AMQP)
+        :param heartbeat: Desired heartbeat delay
+            The delay, in seconds, of the connection heartbeat that the client wants. Zero
+            means the client does not want a heartbeat.
+        :type heartbeat: int, 16 bit unsigned (short in AMQP)
+        """
+        self.channel_max = channel_max
+        self.frame_max = frame_max
+        self.heartbeat = heartbeat
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!HIH', self.channel_max, self.frame_max, self.heartbeat))
+        
+    def get_size(self):
+        return 8
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        channel_max, frame_max, heartbeat, = struct.unpack_from('!HIH', buf, offset)
+        offset += 8
+        return ConnectionTuneOk(channel_max, frame_max, heartbeat)
+
+
+class ConnectionUnblocked(AMQPMethodPayload):
+    """
+    This method indicates that a connection has been unblocked
+    
+    and now accepts publishes.
+    """
+    __slots__ = ()
+
+    NAME = u'connection.unblocked'
+
+    INDEX = (10, 61)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x0A\x00\x3D'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, True
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = True  # this means that argument part has always the same content
+    STATIC_CONTENT = b'\x00\x00\x00\x04\x00\x0A\x00\x3D\xCE'  # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END
+
+    def __init__(self):
+        """
+        Create frame connection.unblocked
+        """
+
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        return ConnectionUnblocked()
+
+
+class Channel(AMQPClass):
+    """
+    The channel class provides methods for a client to establish a channel to a
+    
+    server and for both peers to operate the channel thereafter.
+    """
+    NAME = u'channel'
+    INDEX = 20
+
+
+class ChannelClose(AMQPMethodPayload):
+    """
+    Request a channel close
+    
+    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
+    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.
+    """
+    __slots__ = (u'reply_code', u'reply_text', u'class_id', u'method_id', )
+
+    NAME = u'channel.close'
+
+    INDEX = (20, 40)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x14\x00\x28'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, True
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'reply-code', u'reply-code', u'short', reserved=False),
+        Field(u'reply-text', u'reply-text', u'shortstr', reserved=False),
+        Field(u'class-id', u'class-id', u'short', reserved=False),
+        Field(u'method-id', u'method-id', u'short', reserved=False),
+    ]
+
+    def __init__(self, reply_code, reply_text, class_id, method_id):
+        """
+        Create frame channel.close
+
+        :type reply_code: int, 16 bit unsigned (reply-code in AMQP)
+        :type reply_text: binary type (max length 255) (reply-text in AMQP)
+        :param class_id: Failing method class
+            When the close is provoked by a method exception, this is the class of the
+            method.
+        :type class_id: int, 16 bit unsigned (class-id in AMQP)
+        :param method_id: Failing method id
+            When the close is provoked by a method exception, this is the ID of the method.
+        :type method_id: int, 16 bit unsigned (method-id in AMQP)
+        """
+        self.reply_code = reply_code
+        self.reply_text = reply_text
+        self.class_id = class_id
+        self.method_id = method_id
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!HB', self.reply_code, len(self.reply_text)))
+        buf.write(self.reply_text)
+        buf.write(struct.pack('!HH', self.class_id, self.method_id))
+        
+    def get_size(self):
+        return 7 + len(self.reply_text)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        reply_code, s_len, = struct.unpack_from('!HB', buf, offset)
+        offset += 3
+        reply_text = buf[offset:offset+s_len]
+        offset += s_len
+        class_id, method_id, = struct.unpack_from('!HH', buf, offset)
+        offset += 4
+        return ChannelClose(reply_code, reply_text, class_id, method_id)
+
+
+class ChannelCloseOk(AMQPMethodPayload):
+    """
+    Confirm a channel close
+    
+    This method confirms a Channel.Close method and tells the recipient that it is safe
+    to release resources for the channel.
+    """
+    __slots__ = ()
+
+    NAME = u'channel.close-ok'
+
+    INDEX = (20, 41)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x14\x00\x29'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, True
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = True  # this means that argument part has always the same content
+    STATIC_CONTENT = b'\x00\x00\x00\x04\x00\x14\x00\x29\xCE'  # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END
+
+    def __init__(self):
+        """
+        Create frame channel.close-ok
+        """
+
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        return ChannelCloseOk()
+
+
+class ChannelFlow(AMQPMethodPayload):
+    """
+    Enable/disable flow from peer
+    
+    This method asks the peer to pause or restart the flow of content data sent by
+    a consumer. This is a simple flow-control mechanism that a peer can use to avoid
+    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
+    not affect contents returned by Basic.Get-Ok methods.
+    """
+    __slots__ = (u'active', )
+
+    NAME = u'channel.flow'
+
+    INDEX = (20, 20)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x14\x00\x14'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, True
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'active', u'bit', u'bit', reserved=False),
+    ]
+
+    def __init__(self, active):
+        """
+        Create frame channel.flow
+
+        :param active: Start/stop content frames
+            If 1, the peer starts sending content frames. If 0, the peer stops sending
+            content frames.
+        :type active: bool (bit in AMQP)
+        """
+        self.active = active
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!B', (self.active << 0)))
+        
+    def get_size(self):
+        return 1
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        _bit, = struct.unpack_from('!B', buf, offset)
+        offset += 0
+        active = bool(_bit >> 0)
+        offset += 1
+        return ChannelFlow(active)
+
+
+class ChannelFlowOk(AMQPMethodPayload):
+    """
+    Confirm a flow method
+    
+    Confirms to the peer that a flow command was received and processed.
+    """
+    __slots__ = (u'active', )
+
+    NAME = u'channel.flow-ok'
+
+    INDEX = (20, 21)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x14\x00\x15'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, True
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'active', u'bit', u'bit', reserved=False),
+    ]
+
+    def __init__(self, active):
+        """
+        Create frame channel.flow-ok
+
+        :param active: Current flow setting
+            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.
+        :type active: bool (bit in AMQP)
+        """
+        self.active = active
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!B', (self.active << 0)))
+        
+    def get_size(self):
+        return 1
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        _bit, = struct.unpack_from('!B', buf, offset)
+        offset += 0
+        active = bool(_bit >> 0)
+        offset += 1
+        return ChannelFlowOk(active)
+
+
+class ChannelOpen(AMQPMethodPayload):
+    """
+    Open a channel for use
+    
+    This method opens a channel to the server.
+    """
+    __slots__ = ()
+
+    NAME = u'channel.open'
+
+    INDEX = (20, 10)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x14\x00\x0A'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = True  # this means that argument part has always the same content
+    STATIC_CONTENT = b'\x00\x00\x00\x05\x00\x14\x00\x0A\x00\xCE'  # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'reserved-1', u'shortstr', u'shortstr', reserved=True),
+    ]
+
+    def __init__(self):
+        """
+        Create frame channel.open
+        """
+
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        offset += s_len # reserved field!
+        return ChannelOpen()
+
+
+class ChannelOpenOk(AMQPMethodPayload):
+    """
+    Signal that the channel is ready
+    
+    This method signals to the client that the channel is ready for use.
+    """
+    __slots__ = ()
+
+    NAME = u'channel.open-ok'
+
+    INDEX = (20, 11)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x14\x00\x0B'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = True  # this means that argument part has always the same content
+    STATIC_CONTENT = b'\x00\x00\x00\x05\x00\x14\x00\x0B\x00\x00\x00\x00\xCE'  # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'reserved-1', u'longstr', u'longstr', reserved=True),
+    ]
+
+    def __init__(self):
+        """
+        Create frame channel.open-ok
+        """
+
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!L', buf, offset)
+        offset += 4
+        offset += s_len # reserved field!
+        return ChannelOpenOk()
+
+
+class Exchange(AMQPClass):
+    """
+    Exchanges match and distribute messages across queues. exchanges can be configured in
+    
+    the server or declared at runtime.
+    """
+    NAME = u'exchange'
+    INDEX = 40
+
+
+class ExchangeBind(AMQPMethodPayload):
+    """
+    Bind exchange to an exchange
+    
+    This method binds an exchange to an exchange.
+    """
+    __slots__ = (u'destination', u'source', u'routing_key', u'no_wait', u'arguments', )
+
+    NAME = u'exchange.bind'
+
+    INDEX = (40, 30)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x28\x00\x1E'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'reserved-1', u'short', u'short', reserved=True),
+        Field(u'destination', u'exchange-name', u'shortstr', reserved=False),
+        Field(u'source', u'exchange-name', u'shortstr', reserved=False),
+        Field(u'routing-key', u'shortstr', u'shortstr', reserved=False),
+        Field(u'no-wait', u'no-wait', u'bit', reserved=False),
+        Field(u'arguments', u'table', u'table', reserved=False),
+    ]
+
+    def __init__(self, destination, source, routing_key, no_wait, arguments):
+        """
+        Create frame exchange.bind
+
+        :param destination: Name of the destination exchange to bind to
+            Specifies the name of the destination exchange to bind.
+        :type destination: binary type (max length 255) (exchange-name in AMQP)
+        :param source: Name of the source exchange to bind to
+            Specifies the name of the source exchange to bind.
+        :type source: binary type (max length 255) (exchange-name in AMQP)
+        :param routing_key: Message routing key
+            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 routing key - refer
+            to the specific exchange documentation.
+        :type routing_key: binary type (max length 255) (shortstr in AMQP)
+        :type no_wait: bool (no-wait in AMQP)
+        :param arguments: Arguments for binding
+            A set of arguments for the binding. The syntax and semantics
+            of these arguments depends on the exchange class.
+        :type arguments: table. See coolamqp.uplink.framing.field_table (table in AMQP)
+        """
+        self.destination = destination
+        self.source = source
+        self.routing_key = routing_key
+        self.no_wait = no_wait
+        self.arguments = arguments
+
+    def write_arguments(self, buf):
+        buf.write(b'\x00\x00')
+        buf.write(struct.pack('!B', len(self.destination)))
+        buf.write(self.destination)
+        buf.write(struct.pack('!B', len(self.source)))
+        buf.write(self.source)
+        buf.write(struct.pack('!B', len(self.routing_key)))
+        buf.write(self.routing_key)
+        buf.write(struct.pack('!B', (self.no_wait << 0)))
+        enframe_table(buf, self.arguments)
+        
+    def get_size(self):
+        return 6 + len(self.destination) + len(self.source) + len(self.routing_key) + frame_table_size(self.arguments)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!2xB', buf, offset)
+        offset += 3
+        destination = buf[offset:offset+s_len]
+        offset += s_len
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        source = buf[offset:offset+s_len]
+        offset += s_len
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        routing_key = buf[offset:offset+s_len]
+        offset += s_len
+        _bit, = struct.unpack_from('!B', buf, offset)
+        offset += 0
+        no_wait = bool(_bit >> 0)
+        offset += 1
+        arguments, delta = deframe_table(buf, offset)
+        offset += delta
+        return ExchangeBind(destination, source, routing_key, no_wait, arguments)
+
+
+class ExchangeBindOk(AMQPMethodPayload):
+    """
+    Confirm bind successful
+    
+    This method confirms that the bind was successful.
+    """
+    __slots__ = ()
+
+    NAME = u'exchange.bind-ok'
+
+    INDEX = (40, 31)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x28\x00\x1F'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = True  # this means that argument part has always the same content
+    STATIC_CONTENT = b'\x00\x00\x00\x04\x00\x28\x00\x1F\xCE'  # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END
+
+    def __init__(self):
+        """
+        Create frame exchange.bind-ok
+        """
+
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        return ExchangeBindOk()
+
+
+class ExchangeDeclare(AMQPMethodPayload):
+    """
+    Verify exchange exists, create if needed
+    
+    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.
+    """
+    __slots__ = (u'exchange', u'type_', u'passive', u'durable', u'auto_delete', u'internal', u'no_wait', u'arguments', )
+
+    NAME = u'exchange.declare'
+
+    INDEX = (40, 10)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x28\x00\x0A'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'reserved-1', u'short', u'short', reserved=True),
+        Field(u'exchange', u'exchange-name', u'shortstr', reserved=False),
+        Field(u'type', u'shortstr', u'shortstr', reserved=False),
+        Field(u'passive', u'bit', u'bit', reserved=False),
+        Field(u'durable', u'bit', u'bit', reserved=False),
+        Field(u'auto-delete', u'bit', u'bit', reserved=False),
+        Field(u'internal', u'bit', u'bit', reserved=False),
+        Field(u'no-wait', u'no-wait', u'bit', reserved=False),
+        Field(u'arguments', u'table', u'table', reserved=False),
+    ]
+
+    def __init__(self, exchange, type_, passive, durable, auto_delete, internal, no_wait, arguments):
+        """
+        Create frame exchange.declare
+
+        :param exchange: Exchange names starting with "amq." are reserved for pre-declared and
+            standardised exchanges. The client MAY declare an exchange starting with
+            "amq." if the passive option is set, or the exchange already exists.
+        :type exchange: binary type (max length 255) (exchange-name in AMQP)
+        :param type_: Exchange type
+            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
+            messages are routed through it. It is not valid or meaningful to attempt to
+            change the type of an existing exchange.
+        :type type_: binary type (max length 255) (shortstr in AMQP)
+        :param passive: Do not create exchange
+            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
+            use this to check whether an exchange exists without modifying the
+            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.
+            Arguments are compared for semantic equivalence.
+        :type passive: bool (bit in AMQP)
+        :param durable: Request a durable exchange
+            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
+            (transient exchanges) are purged if/when a server restarts.
+        :type durable: bool (bit in AMQP)
+        :param auto_delete: Auto-delete when unused
+            If set, the exchange is deleted when all queues have
+            finished using it.
+        :type auto_delete: bool (bit in AMQP)
+        :param internal: Create internal exchange
+            If set, the exchange may not be used directly by publishers,
+            but only when bound to other exchanges. Internal exchanges
+            are used to construct wiring that is not visible to
+            applications.
+        :type internal: bool (bit in AMQP)
+        :type no_wait: bool (no-wait in AMQP)
+        :param arguments: Arguments for declaration
+            A set of arguments for the declaration. The syntax and semantics of these
+            arguments depends on the server implementation.
+        :type arguments: table. See coolamqp.uplink.framing.field_table (table in AMQP)
+        """
+        self.exchange = exchange
+        self.type_ = type_
+        self.passive = passive
+        self.durable = durable
+        self.auto_delete = auto_delete
+        self.internal = internal
+        self.no_wait = no_wait
+        self.arguments = arguments
+
+    def write_arguments(self, buf):
+        buf.write(b'\x00\x00')
+        buf.write(struct.pack('!B', len(self.exchange)))
+        buf.write(self.exchange)
+        buf.write(struct.pack('!B', len(self.type_)))
+        buf.write(self.type_)
+        buf.write(struct.pack('!B', (self.passive << 0) | (self.durable << 1) | (self.auto_delete << 2) | (self.internal << 3) | (self.no_wait << 4)))
+        enframe_table(buf, self.arguments)
+        
+    def get_size(self):
+        return 5 + len(self.exchange) + len(self.type_) + frame_table_size(self.arguments)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!2xB', buf, offset)
+        offset += 3
+        exchange = buf[offset:offset+s_len]
+        offset += s_len
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        type_ = buf[offset:offset+s_len]
+        offset += s_len
+        _bit, = struct.unpack_from('!B', buf, offset)
+        offset += 0
+        passive = bool(_bit >> 0)
+        durable = bool(_bit >> 1)
+        auto_delete = bool(_bit >> 2)
+        internal = bool(_bit >> 3)
+        no_wait = bool(_bit >> 4)
+        offset += 1
+        arguments, delta = deframe_table(buf, offset)
+        offset += delta
+        return ExchangeDeclare(exchange, type_, passive, durable, auto_delete, internal, no_wait, arguments)
+
+
+class ExchangeDelete(AMQPMethodPayload):
+    """
+    Delete an exchange
+    
+    This method deletes an exchange. When an exchange is deleted all queue bindings on
+    the exchange are cancelled.
+    """
+    __slots__ = (u'exchange', u'if_unused', u'no_wait', )
+
+    NAME = u'exchange.delete'
+
+    INDEX = (40, 20)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x28\x00\x14'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'reserved-1', u'short', u'short', reserved=True),
+        Field(u'exchange', u'exchange-name', u'shortstr', reserved=False),
+        Field(u'if-unused', u'bit', u'bit', reserved=False),
+        Field(u'no-wait', u'no-wait', u'bit', reserved=False),
+    ]
+
+    def __init__(self, exchange, if_unused, no_wait):
+        """
+        Create frame exchange.delete
+
+        :param exchange: The client must not attempt to delete an exchange that does not exist.
+        :type exchange: binary type (max length 255) (exchange-name in AMQP)
+        :param if_unused: Delete only if unused
+            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
+            channel exception instead.
+        :type if_unused: bool (bit in AMQP)
+        :type no_wait: bool (no-wait in AMQP)
+        """
+        self.exchange = exchange
+        self.if_unused = if_unused
+        self.no_wait = no_wait
+
+    def write_arguments(self, buf):
+        buf.write(b'\x00\x00')
+        buf.write(struct.pack('!B', len(self.exchange)))
+        buf.write(self.exchange)
+        buf.write(struct.pack('!B', (self.if_unused << 0) | (self.no_wait << 1)))
+        
+    def get_size(self):
+        return 4 + len(self.exchange)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!2xB', buf, offset)
+        offset += 3
+        exchange = buf[offset:offset+s_len]
+        offset += s_len
+        _bit, = struct.unpack_from('!B', buf, offset)
+        offset += 0
+        if_unused = bool(_bit >> 0)
+        no_wait = bool(_bit >> 1)
+        offset += 1
+        return ExchangeDelete(exchange, if_unused, no_wait)
+
+
+class ExchangeDeclareOk(AMQPMethodPayload):
+    """
+    Confirm exchange declaration
+    
+    This method confirms a Declare method and confirms the name of the exchange,
+    essential for automatically-named exchanges.
+    """
+    __slots__ = ()
+
+    NAME = u'exchange.declare-ok'
+
+    INDEX = (40, 11)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x28\x00\x0B'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = True  # this means that argument part has always the same content
+    STATIC_CONTENT = b'\x00\x00\x00\x04\x00\x28\x00\x0B\xCE'  # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END
+
+    def __init__(self):
+        """
+        Create frame exchange.declare-ok
+        """
+
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        return ExchangeDeclareOk()
+
+
+class ExchangeDeleteOk(AMQPMethodPayload):
+    """
+    Confirm deletion of an exchange
+    
+    This method confirms the deletion of an exchange.
+    """
+    __slots__ = ()
+
+    NAME = u'exchange.delete-ok'
+
+    INDEX = (40, 21)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x28\x00\x15'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = True  # this means that argument part has always the same content
+    STATIC_CONTENT = b'\x00\x00\x00\x04\x00\x28\x00\x15\xCE'  # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END
+
+    def __init__(self):
+        """
+        Create frame exchange.delete-ok
+        """
+
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        return ExchangeDeleteOk()
+
+
+class ExchangeUnbind(AMQPMethodPayload):
+    """
+    Unbind an exchange from an exchange
+    
+    This method unbinds an exchange from an exchange.
+    """
+    __slots__ = (u'destination', u'source', u'routing_key', u'no_wait', u'arguments', )
+
+    NAME = u'exchange.unbind'
+
+    INDEX = (40, 40)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x28\x00\x28'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'reserved-1', u'short', u'short', reserved=True),
+        Field(u'destination', u'exchange-name', u'shortstr', reserved=False),
+        Field(u'source', u'exchange-name', u'shortstr', reserved=False),
+        Field(u'routing-key', u'shortstr', u'shortstr', reserved=False),
+        Field(u'no-wait', u'no-wait', u'bit', reserved=False),
+        Field(u'arguments', u'table', u'table', reserved=False),
+    ]
+
+    def __init__(self, destination, source, routing_key, no_wait, arguments):
+        """
+        Create frame exchange.unbind
+
+        :param destination: Specifies the name of the destination exchange to unbind.
+        :type destination: binary type (max length 255) (exchange-name in AMQP)
+        :param source: Specifies the name of the source exchange to unbind.
+        :type source: binary type (max length 255) (exchange-name in AMQP)
+        :param routing_key: Routing key of binding
+            Specifies the routing key of the binding to unbind.
+        :type routing_key: binary type (max length 255) (shortstr in AMQP)
+        :type no_wait: bool (no-wait in AMQP)
+        :param arguments: Arguments of binding
+            Specifies the arguments of the binding to unbind.
+        :type arguments: table. See coolamqp.uplink.framing.field_table (table in AMQP)
+        """
+        self.destination = destination
+        self.source = source
+        self.routing_key = routing_key
+        self.no_wait = no_wait
+        self.arguments = arguments
+
+    def write_arguments(self, buf):
+        buf.write(b'\x00\x00')
+        buf.write(struct.pack('!B', len(self.destination)))
+        buf.write(self.destination)
+        buf.write(struct.pack('!B', len(self.source)))
+        buf.write(self.source)
+        buf.write(struct.pack('!B', len(self.routing_key)))
+        buf.write(self.routing_key)
+        buf.write(struct.pack('!B', (self.no_wait << 0)))
+        enframe_table(buf, self.arguments)
+        
+    def get_size(self):
+        return 6 + len(self.destination) + len(self.source) + len(self.routing_key) + frame_table_size(self.arguments)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!2xB', buf, offset)
+        offset += 3
+        destination = buf[offset:offset+s_len]
+        offset += s_len
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        source = buf[offset:offset+s_len]
+        offset += s_len
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        routing_key = buf[offset:offset+s_len]
+        offset += s_len
+        _bit, = struct.unpack_from('!B', buf, offset)
+        offset += 0
+        no_wait = bool(_bit >> 0)
+        offset += 1
+        arguments, delta = deframe_table(buf, offset)
+        offset += delta
+        return ExchangeUnbind(destination, source, routing_key, no_wait, arguments)
+
+
+class ExchangeUnbindOk(AMQPMethodPayload):
+    """
+    Confirm unbind successful
+    
+    This method confirms that the unbind was successful.
+    """
+    __slots__ = ()
+
+    NAME = u'exchange.unbind-ok'
+
+    INDEX = (40, 51)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x28\x00\x33'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = True  # this means that argument part has always the same content
+    STATIC_CONTENT = b'\x00\x00\x00\x04\x00\x28\x00\x33\xCE'  # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END
+
+    def __init__(self):
+        """
+        Create frame exchange.unbind-ok
+        """
+
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        return ExchangeUnbindOk()
+
+
+class Queue(AMQPClass):
+    """
+    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
+    from publishers.
+    """
+    NAME = u'queue'
+    INDEX = 50
+
+
+class QueueBind(AMQPMethodPayload):
+    """
+    Bind queue to an exchange
+    
+    This method binds a queue to an exchange. Until a queue is bound it will not
+    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
+    exchange.
+    """
+    __slots__ = (u'queue', u'exchange', u'routing_key', u'no_wait', u'arguments', )
+
+    NAME = u'queue.bind'
+
+    INDEX = (50, 20)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x32\x00\x14'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'reserved-1', u'short', u'short', reserved=True),
+        Field(u'queue', u'queue-name', u'shortstr', reserved=False),
+        Field(u'exchange', u'exchange-name', u'shortstr', reserved=False),
+        Field(u'routing-key', u'shortstr', u'shortstr', reserved=False),
+        Field(u'no-wait', u'no-wait', u'bit', reserved=False),
+        Field(u'arguments', u'table', u'table', reserved=False),
+    ]
+
+    def __init__(self, queue, exchange, routing_key, no_wait, arguments):
+        """
+        Create frame queue.bind
+
+        :param queue: Specifies the name of the queue to bind.
+        :type queue: binary type (max length 255) (queue-name in AMQP)
+        :param exchange: Name of the exchange to bind to
+            A client MUST NOT be allowed to bind a queue to a non-existent exchange.
+        :type exchange: binary type (max length 255) (exchange-name in AMQP)
+        :param routing_key: Message routing key
+            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
+            routing key - refer to the specific exchange documentation.  If the queue name
+            is empty, the server uses the last queue declared on the channel.  If the
+            routing key is also empty, the server uses this queue name for the routing
+            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
+            routing keys depends on the exchange implementation.
+        :type routing_key: binary type (max length 255) (shortstr in AMQP)
+        :type no_wait: bool (no-wait in AMQP)
+        :param arguments: Arguments for binding
+            A set of arguments for the binding. The syntax and semantics of these arguments
+            depends on the exchange class.
+        :type arguments: table. See coolamqp.uplink.framing.field_table (table in AMQP)
+        """
+        self.queue = queue
+        self.exchange = exchange
+        self.routing_key = routing_key
+        self.no_wait = no_wait
+        self.arguments = arguments
+
+    def write_arguments(self, buf):
+        buf.write(b'\x00\x00')
+        buf.write(struct.pack('!B', len(self.queue)))
+        buf.write(self.queue)
+        buf.write(struct.pack('!B', len(self.exchange)))
+        buf.write(self.exchange)
+        buf.write(struct.pack('!B', len(self.routing_key)))
+        buf.write(self.routing_key)
+        buf.write(struct.pack('!B', (self.no_wait << 0)))
+        enframe_table(buf, self.arguments)
+        
+    def get_size(self):
+        return 6 + len(self.queue) + len(self.exchange) + len(self.routing_key) + frame_table_size(self.arguments)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!2xB', buf, offset)
+        offset += 3
+        queue = buf[offset:offset+s_len]
+        offset += s_len
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        exchange = buf[offset:offset+s_len]
+        offset += s_len
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        routing_key = buf[offset:offset+s_len]
+        offset += s_len
+        _bit, = struct.unpack_from('!B', buf, offset)
+        offset += 0
+        no_wait = bool(_bit >> 0)
+        offset += 1
+        arguments, delta = deframe_table(buf, offset)
+        offset += delta
+        return QueueBind(queue, exchange, routing_key, no_wait, arguments)
+
+
+class QueueBindOk(AMQPMethodPayload):
+    """
+    Confirm bind successful
+    
+    This method confirms that the bind was successful.
+    """
+    __slots__ = ()
+
+    NAME = u'queue.bind-ok'
+
+    INDEX = (50, 21)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x32\x00\x15'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = True  # this means that argument part has always the same content
+    STATIC_CONTENT = b'\x00\x00\x00\x04\x00\x32\x00\x15\xCE'  # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END
+
+    def __init__(self):
+        """
+        Create frame queue.bind-ok
+        """
+
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        return QueueBindOk()
+
+
+class QueueDeclare(AMQPMethodPayload):
+    """
+    Declare queue, create if needed
+    
+    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
+    contents, and the level of sharing for the queue.
+    """
+    __slots__ = (u'queue', u'passive', u'durable', u'exclusive', u'auto_delete', u'no_wait', u'arguments', )
+
+    NAME = u'queue.declare'
+
+    INDEX = (50, 10)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x32\x00\x0A'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'reserved-1', u'short', u'short', reserved=True),
+        Field(u'queue', u'queue-name', u'shortstr', reserved=False),
+        Field(u'passive', u'bit', u'bit', reserved=False),
+        Field(u'durable', u'bit', u'bit', reserved=False),
+        Field(u'exclusive', u'bit', u'bit', reserved=False),
+        Field(u'auto-delete', u'bit', u'bit', reserved=False),
+        Field(u'no-wait', u'no-wait', u'bit', reserved=False),
+        Field(u'arguments', u'table', u'table', reserved=False),
+    ]
+
+    def __init__(self, queue, passive, durable, exclusive, auto_delete, no_wait, arguments):
+        """
+        Create frame queue.declare
+
+        :param queue: The queue name may be empty, in which case the server must create a new
+            queue with a unique generated name and return this to the client in the
+            Declare-Ok method.
+        :type queue: binary type (max length 255) (queue-name in AMQP)
+        :param passive: Do not create queue
+            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
+            use this to check whether a queue exists without modifying the
+            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.
+            Arguments are compared for semantic equivalence.
+        :type passive: bool (bit in AMQP)
+        :param durable: Request a durable queue
+            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) 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
+            persistent messages to a transient queue.
+        :type durable: bool (bit in AMQP)
+        :param exclusive: Request an exclusive queue
+            Exclusive queues may only be accessed by the current connection, and are
+            deleted when that connection closes.  Passive declaration of an exclusive
+            queue by other connections are not allowed.
+        :type exclusive: bool (bit in AMQP)
+        :param auto_delete: Auto-delete queue when unused
+            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
+            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.
+        :type auto_delete: bool (bit in AMQP)
+        :type no_wait: bool (no-wait in AMQP)
+        :param arguments: Arguments for declaration
+            A set of arguments for the declaration. The syntax and semantics of these
+            arguments depends on the server implementation.
+        :type arguments: table. See coolamqp.uplink.framing.field_table (table in AMQP)
+        """
+        self.queue = queue
+        self.passive = passive
+        self.durable = durable
+        self.exclusive = exclusive
+        self.auto_delete = auto_delete
+        self.no_wait = no_wait
+        self.arguments = arguments
+
+    def write_arguments(self, buf):
+        buf.write(b'\x00\x00')
+        buf.write(struct.pack('!B', len(self.queue)))
+        buf.write(self.queue)
+        buf.write(struct.pack('!B', (self.passive << 0) | (self.durable << 1) | (self.exclusive << 2) | (self.auto_delete << 3) | (self.no_wait << 4)))
+        enframe_table(buf, self.arguments)
+        
+    def get_size(self):
+        return 4 + len(self.queue) + frame_table_size(self.arguments)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!2xB', buf, offset)
+        offset += 3
+        queue = buf[offset:offset+s_len]
+        offset += s_len
+        _bit, = struct.unpack_from('!B', buf, offset)
+        offset += 0
+        passive = bool(_bit >> 0)
+        durable = bool(_bit >> 1)
+        exclusive = bool(_bit >> 2)
+        auto_delete = bool(_bit >> 3)
+        no_wait = bool(_bit >> 4)
+        offset += 1
+        arguments, delta = deframe_table(buf, offset)
+        offset += delta
+        return QueueDeclare(queue, passive, durable, exclusive, auto_delete, no_wait, arguments)
+
+
+class QueueDelete(AMQPMethodPayload):
+    """
+    Delete a queue
+    
+    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
+    consumers on the queue are cancelled.
+    """
+    __slots__ = (u'queue', u'if_unused', u'if_empty', u'no_wait', )
+
+    NAME = u'queue.delete'
+
+    INDEX = (50, 40)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x32\x00\x28'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'reserved-1', u'short', u'short', reserved=True),
+        Field(u'queue', u'queue-name', u'shortstr', reserved=False),
+        Field(u'if-unused', u'bit', u'bit', reserved=False),
+        Field(u'if-empty', u'bit', u'bit', reserved=False),
+        Field(u'no-wait', u'no-wait', u'bit', reserved=False),
+    ]
+
+    def __init__(self, queue, if_unused, if_empty, no_wait):
+        """
+        Create frame queue.delete
+
+        :param queue: Specifies the name of the queue to delete.
+        :type queue: binary type (max length 255) (queue-name in AMQP)
+        :param if_unused: Delete only if unused
+            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
+            exception instead.
+        :type if_unused: bool (bit in AMQP)
+        :param if_empty: Delete only if empty
+            If set, the server will only delete the queue if it has no messages.
+        :type if_empty: bool (bit in AMQP)
+        :type no_wait: bool (no-wait in AMQP)
+        """
+        self.queue = queue
+        self.if_unused = if_unused
+        self.if_empty = if_empty
+        self.no_wait = no_wait
+
+    def write_arguments(self, buf):
+        buf.write(b'\x00\x00')
+        buf.write(struct.pack('!B', len(self.queue)))
+        buf.write(self.queue)
+        buf.write(struct.pack('!B', (self.if_unused << 0) | (self.if_empty << 1) | (self.no_wait << 2)))
+        
+    def get_size(self):
+        return 4 + len(self.queue)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!2xB', buf, offset)
+        offset += 3
+        queue = buf[offset:offset+s_len]
+        offset += s_len
+        _bit, = struct.unpack_from('!B', buf, offset)
+        offset += 0
+        if_unused = bool(_bit >> 0)
+        if_empty = bool(_bit >> 1)
+        no_wait = bool(_bit >> 2)
+        offset += 1
+        return QueueDelete(queue, if_unused, if_empty, no_wait)
+
+
+class QueueDeclareOk(AMQPMethodPayload):
+    """
+    Confirms a queue definition
+    
+    This method confirms a Declare method and confirms the name of the queue, essential
+    for automatically-named queues.
+    """
+    __slots__ = (u'queue', u'message_count', u'consumer_count', )
+
+    NAME = u'queue.declare-ok'
+
+    INDEX = (50, 11)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x32\x00\x0B'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'queue', u'queue-name', u'shortstr', reserved=False),
+        Field(u'message-count', u'message-count', u'long', reserved=False),
+        Field(u'consumer-count', u'long', u'long', reserved=False),
+    ]
+
+    def __init__(self, queue, message_count, consumer_count):
+        """
+        Create frame queue.declare-ok
+
+        :param queue: Reports the name of the queue. if the server generated a queue name, this field
+            contains that name.
+        :type queue: binary type (max length 255) (queue-name in AMQP)
+        :type message_count: int, 32 bit unsigned (message-count in AMQP)
+        :param consumer_count: Number of consumers
+            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.
+        :type consumer_count: int, 32 bit unsigned (long in AMQP)
+        """
+        self.queue = queue
+        self.message_count = message_count
+        self.consumer_count = consumer_count
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!B', len(self.queue)))
+        buf.write(self.queue)
+        buf.write(struct.pack('!II', self.message_count, self.consumer_count))
+        
+    def get_size(self):
+        return 9 + len(self.queue)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        queue = buf[offset:offset+s_len]
+        offset += s_len
+        message_count, consumer_count, = struct.unpack_from('!II', buf, offset)
+        offset += 8
+        return QueueDeclareOk(queue, message_count, consumer_count)
+
+
+class QueueDeleteOk(AMQPMethodPayload):
+    """
+    Confirm deletion of a queue
+    
+    This method confirms the deletion of a queue.
+    """
+    __slots__ = (u'message_count', )
+
+    NAME = u'queue.delete-ok'
+
+    INDEX = (50, 41)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x32\x00\x29'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'message-count', u'message-count', u'long', reserved=False),
+    ]
+
+    def __init__(self, message_count):
+        """
+        Create frame queue.delete-ok
+
+        :param message_count: Reports the number of messages deleted.
+        :type message_count: int, 32 bit unsigned (message-count in AMQP)
+        """
+        self.message_count = message_count
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!I', self.message_count))
+        
+    def get_size(self):
+        return 4
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        message_count, = struct.unpack_from('!I', buf, offset)
+        offset += 4
+        return QueueDeleteOk(message_count)
+
+
+class QueuePurge(AMQPMethodPayload):
+    """
+    Purge a queue
+    
+    This method removes all messages from a queue which are not awaiting
+    acknowledgment.
+    """
+    __slots__ = (u'queue', u'no_wait', )
+
+    NAME = u'queue.purge'
+
+    INDEX = (50, 30)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x32\x00\x1E'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'reserved-1', u'short', u'short', reserved=True),
+        Field(u'queue', u'queue-name', u'shortstr', reserved=False),
+        Field(u'no-wait', u'no-wait', u'bit', reserved=False),
+    ]
+
+    def __init__(self, queue, no_wait):
+        """
+        Create frame queue.purge
+
+        :param queue: Specifies the name of the queue to purge.
+        :type queue: binary type (max length 255) (queue-name in AMQP)
+        :type no_wait: bool (no-wait in AMQP)
+        """
+        self.queue = queue
+        self.no_wait = no_wait
+
+    def write_arguments(self, buf):
+        buf.write(b'\x00\x00')
+        buf.write(struct.pack('!B', len(self.queue)))
+        buf.write(self.queue)
+        buf.write(struct.pack('!B', (self.no_wait << 0)))
+        
+    def get_size(self):
+        return 4 + len(self.queue)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!2xB', buf, offset)
+        offset += 3
+        queue = buf[offset:offset+s_len]
+        offset += s_len
+        _bit, = struct.unpack_from('!B', buf, offset)
+        offset += 0
+        no_wait = bool(_bit >> 0)
+        offset += 1
+        return QueuePurge(queue, no_wait)
+
+
+class QueuePurgeOk(AMQPMethodPayload):
+    """
+    Confirms a queue purge
+    
+    This method confirms the purge of a queue.
+    """
+    __slots__ = (u'message_count', )
+
+    NAME = u'queue.purge-ok'
+
+    INDEX = (50, 31)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x32\x00\x1F'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'message-count', u'message-count', u'long', reserved=False),
+    ]
+
+    def __init__(self, message_count):
+        """
+        Create frame queue.purge-ok
+
+        :param message_count: Reports the number of messages purged.
+        :type message_count: int, 32 bit unsigned (message-count in AMQP)
+        """
+        self.message_count = message_count
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!I', self.message_count))
+        
+    def get_size(self):
+        return 4
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        message_count, = struct.unpack_from('!I', buf, offset)
+        offset += 4
+        return QueuePurgeOk(message_count)
+
+
+class QueueUnbind(AMQPMethodPayload):
+    """
+    Unbind a queue from an exchange
+    
+    This method unbinds a queue from an exchange.
+    """
+    __slots__ = (u'queue', u'exchange', u'routing_key', u'arguments', )
+
+    NAME = u'queue.unbind'
+
+    INDEX = (50, 50)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x32\x00\x32'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'reserved-1', u'short', u'short', reserved=True),
+        Field(u'queue', u'queue-name', u'shortstr', reserved=False),
+        Field(u'exchange', u'exchange-name', u'shortstr', reserved=False),
+        Field(u'routing-key', u'shortstr', u'shortstr', reserved=False),
+        Field(u'arguments', u'table', u'table', reserved=False),
+    ]
+
+    def __init__(self, queue, exchange, routing_key, arguments):
+        """
+        Create frame queue.unbind
+
+        :param queue: Specifies the name of the queue to unbind.
+        :type queue: binary type (max length 255) (queue-name in AMQP)
+        :param exchange: The name of the exchange to unbind from.
+        :type exchange: binary type (max length 255) (exchange-name in AMQP)
+        :param routing_key: Routing key of binding
+            Specifies the routing key of the binding to unbind.
+        :type routing_key: binary type (max length 255) (shortstr in AMQP)
+        :param arguments: Arguments of binding
+            Specifies the arguments of the binding to unbind.
+        :type arguments: table. See coolamqp.uplink.framing.field_table (table in AMQP)
+        """
+        self.queue = queue
+        self.exchange = exchange
+        self.routing_key = routing_key
+        self.arguments = arguments
+
+    def write_arguments(self, buf):
+        buf.write(b'\x00\x00')
+        buf.write(struct.pack('!B', len(self.queue)))
+        buf.write(self.queue)
+        buf.write(struct.pack('!B', len(self.exchange)))
+        buf.write(self.exchange)
+        buf.write(struct.pack('!B', len(self.routing_key)))
+        buf.write(self.routing_key)
+        enframe_table(buf, self.arguments)
+        
+    def get_size(self):
+        return 5 + len(self.queue) + len(self.exchange) + len(self.routing_key) + frame_table_size(self.arguments)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!2xB', buf, offset)
+        offset += 3
+        queue = buf[offset:offset+s_len]
+        offset += s_len
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        exchange = buf[offset:offset+s_len]
+        offset += s_len
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        routing_key = buf[offset:offset+s_len]
+        offset += s_len
+        arguments, delta = deframe_table(buf, offset)
+        offset += delta
+        return QueueUnbind(queue, exchange, routing_key, arguments)
+
+
+class QueueUnbindOk(AMQPMethodPayload):
+    """
+    Confirm unbind successful
+    
+    This method confirms that the unbind was successful.
+    """
+    __slots__ = ()
+
+    NAME = u'queue.unbind-ok'
+
+    INDEX = (50, 51)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x32\x00\x33'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = True  # this means that argument part has always the same content
+    STATIC_CONTENT = b'\x00\x00\x00\x04\x00\x32\x00\x33\xCE'  # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END
+
+    def __init__(self):
+        """
+        Create frame queue.unbind-ok
+        """
+
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        return QueueUnbindOk()
+
+
+class Basic(AMQPClass):
+    """
+    The basic class provides methods that support an industry-standard messaging model.
+    """
+    NAME = u'basic'
+    INDEX = 60
+
+
+class BasicContentPropertyList(AMQPContentPropertyList):
+    """
+    The basic class provides methods that support an industry-standard messaging model.
+    """
+    FIELDS = [
+        Field(u'content-type', u'shortstr', u'shortstr', False),
+        Field(u'content-encoding', u'shortstr', u'shortstr', False),
+        Field(u'headers', u'table', u'table', False),
+        Field(u'delivery-mode', u'octet', u'octet', False),
+        Field(u'priority', u'octet', u'octet', False),
+        Field(u'correlation-id', u'shortstr', u'shortstr', False),
+        Field(u'reply-to', u'shortstr', u'shortstr', False),
+        Field(u'expiration', u'shortstr', u'shortstr', False),
+        Field(u'message-id', u'shortstr', u'shortstr', False),
+        Field(u'timestamp', u'timestamp', u'timestamp', False),
+        Field(u'type', u'shortstr', u'shortstr', False),
+        Field(u'user-id', u'shortstr', u'shortstr', False),
+        Field(u'app-id', u'shortstr', u'shortstr', False),
+        Field(u'reserved', u'shortstr', u'shortstr', False),
+    ]
+    # A dictionary from a zero property list to a class typized with
+    # some fields
+    PARTICULAR_CLASSES = {}
+
+    def __new__(self, **kwargs):
+        """
+        Return a property list.
+        :param content_type: MIME content type
+        :type content_type: binary type (max length 255) (AMQP as shortstr)
+        :param content_encoding: MIME content encoding
+        :type content_encoding: binary type (max length 255) (AMQP as shortstr)
+        :param headers: message header field table
+        :type headers: table. See coolamqp.uplink.framing.field_table (AMQP as table)
+        :param delivery_mode: non-persistent (1) or persistent (2)
+        :type delivery_mode: int, 8 bit unsigned (AMQP as octet)
+        :param priority: message priority, 0 to 9
+        :type priority: int, 8 bit unsigned (AMQP as octet)
+        :param correlation_id: application correlation identifier
+        :type correlation_id: binary type (max length 255) (AMQP as shortstr)
+        :param reply_to: address to reply to
+        :type reply_to: binary type (max length 255) (AMQP as shortstr)
+        :param expiration: message expiration specification
+        :type expiration: binary type (max length 255) (AMQP as shortstr)
+        :param message_id: application message identifier
+        :type message_id: binary type (max length 255) (AMQP as shortstr)
+        :param timestamp: message timestamp
+        :type timestamp: 64 bit signed POSIX timestamp (in seconds) (AMQP as timestamp)
+        :param type_: message type name
+        :type type_: binary type (max length 255) (AMQP as shortstr)
+        :param user_id: creating user id
+        :type user_id: binary type (max length 255) (AMQP as shortstr)
+        :param app_id: creating application id
+        :type app_id: binary type (max length 255) (AMQP as shortstr)
+        :param reserved: reserved, must be empty
+        :type reserved: binary type (max length 255) (AMQP as shortstr)
+        """
+        zpf = bytearray([
+            (('content_type' in kwargs) << 7) | (('content_encoding' in kwargs) << 6) | (('headers' in kwargs) << 5) | (('delivery_mode' in kwargs) << 4) | (('priority' in kwargs) << 3) | (('correlation_id' in kwargs) << 2) | (('reply_to' in kwargs) << 1) | int('expiration' in kwargs),
+            (('message_id' in kwargs) << 7) | (('timestamp' in kwargs) << 6) | (('type_' in kwargs) << 5) | (('user_id' in kwargs) << 4) | (('app_id' in kwargs) << 3) | (('reserved' in kwargs) << 2)
+        ])
+        zpf = six.binary_type(zpf)
+
+        if zpf in BasicContentPropertyList.PARTICULAR_CLASSES:
+            warnings.warn(u"""You could go faster.
+
+        If you know in advance what properties you will be using, use typized constructors like
+
+            # runs once
+            my_type = BasicContentPropertyList.typize('content_type', 'content_encoding')
+            # runs many times
+            props = my_type('text/plain', 'utf8')
+
+        instead of
+
+            # runs many times
+            props = BasicContentPropertyList(content_type='text/plain', content_encoding='utf8')
+
+        This way you will be faster.
+
+        If you do not know in advance what properties you will be using, it is correct to use
+        this constructor.
+        """)
+
+            return BasicContentPropertyList.PARTICULAR_CLASSES[zpf](**kwargs)
+        else:
+            logger.debug('Property field (BasicContentPropertyList:%s) not seen yet, compiling', repr(zpf))
+            c = compile_particular_content_property_list_class(zpf, BasicContentPropertyList.FIELDS)
+            BasicContentPropertyList.PARTICULAR_CLASSES[zpf] = c
+            return c(**kwargs)
+
+    @staticmethod
+    def typize(*fields):
+        zpf = bytearray([
+        (('content_type' in fields) << 7) | (('content_encoding' in fields) << 6) | (('headers' in fields) << 5) | (('delivery_mode' in fields) << 4) | (('priority' in fields) << 3) | (('correlation_id' in fields) << 2) | (('reply_to' in fields) << 1) | int('expiration' in kwargs),
+        (('message_id' in fields) << 7) | (('timestamp' in fields) << 6) | (('type_' in fields) << 5) | (('user_id' in fields) << 4) | (('app_id' in fields) << 3) | (('reserved' in fields) << 2)
+        ])
+        zpf = six.binary_type(zpf)
+        if zpf in BasicContentPropertyList.PARTICULAR_CLASSES:
+            return BasicContentPropertyList.PARTICULAR_CLASSES[zpf]
+        else:
+            logger.debug('Property field (BasicContentPropertyList:%s) not seen yet, compiling', repr(zpf))
+            c = compile_particular_content_property_list_class(zpf, BasicContentPropertyList.FIELDS)
+            BasicContentPropertyList.PARTICULAR_CLASSES[zpf] = c
+            return c
+
+    @staticmethod
+    def from_buffer(buf, offset):
+        """
+        Return a content property list instance unserialized from
+        buffer, so that buf[offset] marks the start of property flags
+        """
+        # extract property flags
+        pfl = 2
+        while ord(buf[offset + pfl - 1]) & 1:
+            pfl += 2
+        zpf = BasicContentPropertyList.zero_property_flags(buf[offset:offset+pfl])
+        if zpf in BasicContentPropertyList.PARTICULAR_CLASSES:
+            return BasicContentPropertyList.PARTICULAR_CLASSES[zpf].from_buffer(buf, offset)
+        else:
+            logger.debug('Property field (BasicContentPropertyList:%s) not seen yet, compiling', repr(zpf))
+            c = compile_particular_content_property_list_class(zpf, BasicContentPropertyList.FIELDS)
+            BasicContentPropertyList.PARTICULAR_CLASSES[zpf] = c
+            return c.from_buffer(buf, offset)
+
+
+class BasicAck(AMQPMethodPayload):
+    """
+    Acknowledge one or more messages
+    
+    When sent by the client, this method acknowledges one or more
+    messages delivered via the Deliver or Get-Ok methods.
+    When sent by server, this method acknowledges one or more
+    messages published with the Publish method on a channel in
+    confirm mode.
+    The acknowledgement can be for a single message or a set of
+    messages up to and including a specific message.
+    """
+    __slots__ = (u'delivery_tag', u'multiple', )
+
+    NAME = u'basic.ack'
+
+    INDEX = (60, 80)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x3C\x00\x50'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, True
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'delivery-tag', u'delivery-tag', u'longlong', reserved=False),
+        Field(u'multiple', u'bit', u'bit', reserved=False),
+    ]
+
+    def __init__(self, delivery_tag, multiple):
+        """
+        Create frame basic.ack
+
+        :type delivery_tag: int, 64 bit unsigned (delivery-tag in AMQP)
+        :param multiple: Acknowledge multiple messages
+            If set to 1, the delivery tag is treated as "up to and
+            including", so that multiple messages can be acknowledged
+            with a single method. If set to zero, the delivery tag
+            refers to a single message. If the multiple field is 1, and
+            the delivery tag is zero, this indicates acknowledgement of
+            all outstanding messages.
+        :type multiple: bool (bit in AMQP)
+        """
+        self.delivery_tag = delivery_tag
+        self.multiple = multiple
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!QB', self.delivery_tag, (self.multiple << 0)))
+        
+    def get_size(self):
+        return 9
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        delivery_tag, _bit, = struct.unpack_from('!QB', buf, offset)
+        offset += 8
+        multiple = bool(_bit >> 0)
+        offset += 1
+        return BasicAck(delivery_tag, multiple)
+
+
+class BasicConsume(AMQPMethodPayload):
+    """
+    Start a queue consumer
+    
+    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
+    declared on, or until the client cancels them.
+    """
+    __slots__ = (u'queue', u'consumer_tag', u'no_local', u'no_ack', u'exclusive', u'no_wait', u'arguments', )
+
+    NAME = u'basic.consume'
+
+    INDEX = (60, 20)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x3C\x00\x14'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'reserved-1', u'short', u'short', reserved=True),
+        Field(u'queue', u'queue-name', u'shortstr', reserved=False),
+        Field(u'consumer-tag', u'consumer-tag', u'shortstr', reserved=False),
+        Field(u'no-local', u'no-local', u'bit', reserved=False),
+        Field(u'no-ack', u'no-ack', u'bit', reserved=False),
+        Field(u'exclusive', u'bit', u'bit', reserved=False),
+        Field(u'no-wait', u'no-wait', u'bit', reserved=False),
+        Field(u'arguments', u'table', u'table', reserved=False),
+    ]
+
+    def __init__(self, queue, consumer_tag, no_local, no_ack, exclusive, no_wait, arguments):
+        """
+        Create frame basic.consume
+
+        :param queue: Specifies the name of the queue to consume from.
+        :type queue: binary type (max length 255) (queue-name in AMQP)
+        :param consumer_tag: 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
+            empty the server will generate a unique tag.
+        :type consumer_tag: binary type (max length 255) (consumer-tag in AMQP)
+        :type no_local: bool (no-local in AMQP)
+        :type no_ack: bool (no-ack in AMQP)
+        :param exclusive: Request exclusive access
+            Request exclusive consumer access, meaning only this consumer can access the
+            queue.
+        :type exclusive: bool (bit in AMQP)
+        :type no_wait: bool (no-wait in AMQP)
+        :param arguments: Arguments for declaration
+            A set of arguments for the consume. The syntax and semantics of these
+            arguments depends on the server implementation.
+        :type arguments: table. See coolamqp.uplink.framing.field_table (table in AMQP)
+        """
+        self.queue = queue
+        self.consumer_tag = consumer_tag
+        self.no_local = no_local
+        self.no_ack = no_ack
+        self.exclusive = exclusive
+        self.no_wait = no_wait
+        self.arguments = arguments
+
+    def write_arguments(self, buf):
+        buf.write(b'\x00\x00')
+        buf.write(struct.pack('!B', len(self.queue)))
+        buf.write(self.queue)
+        buf.write(struct.pack('!B', len(self.consumer_tag)))
+        buf.write(self.consumer_tag)
+        buf.write(struct.pack('!B', (self.no_local << 0) | (self.no_ack << 1) | (self.exclusive << 2) | (self.no_wait << 3)))
+        enframe_table(buf, self.arguments)
+        
+    def get_size(self):
+        return 5 + len(self.queue) + len(self.consumer_tag) + frame_table_size(self.arguments)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!2xB', buf, offset)
+        offset += 3
+        queue = buf[offset:offset+s_len]
+        offset += s_len
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        consumer_tag = buf[offset:offset+s_len]
+        offset += s_len
+        _bit, = struct.unpack_from('!B', buf, offset)
+        offset += 0
+        no_local = bool(_bit >> 0)
+        no_ack = bool(_bit >> 1)
+        exclusive = bool(_bit >> 2)
+        no_wait = bool(_bit >> 3)
+        offset += 1
+        arguments, delta = deframe_table(buf, offset)
+        offset += delta
+        return BasicConsume(queue, consumer_tag, no_local, no_ack, exclusive, no_wait, arguments)
+
+
+class BasicCancel(AMQPMethodPayload):
+    """
+    End a queue consumer
+    
+    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
+    that consumer. The client may receive an arbitrary number of messages in
+    between sending the cancel method and receiving the cancel-ok reply.
+    It may also be sent from the server to the client in the event
+    of the consumer being unexpectedly cancelled (i.e. cancelled
+    for any reason other than the server receiving the
+    corresponding basic.cancel from the client). This allows
+    clients to be notified of the loss of consumers due to events
+    such as queue deletion. Note that as it is not a MUST for
+    clients to accept this method from the server, it is advisable
+    for the broker to be able to identify those clients that are
+    capable of accepting the method, through some means of
+    capability negotiation.
+    """
+    __slots__ = (u'consumer_tag', u'no_wait', )
+
+    NAME = u'basic.cancel'
+
+    INDEX = (60, 30)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x3C\x00\x1E'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, True
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'consumer-tag', u'consumer-tag', u'shortstr', reserved=False),
+        Field(u'no-wait', u'no-wait', u'bit', reserved=False),
+    ]
+
+    def __init__(self, consumer_tag, no_wait):
+        """
+        Create frame basic.cancel
+
+        :type consumer_tag: binary type (max length 255) (consumer-tag in AMQP)
+        :type no_wait: bool (no-wait in AMQP)
+        """
+        self.consumer_tag = consumer_tag
+        self.no_wait = no_wait
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!B', len(self.consumer_tag)))
+        buf.write(self.consumer_tag)
+        buf.write(struct.pack('!B', (self.no_wait << 0)))
+        
+    def get_size(self):
+        return 2 + len(self.consumer_tag)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        consumer_tag = buf[offset:offset+s_len]
+        offset += s_len
+        _bit, = struct.unpack_from('!B', buf, offset)
+        offset += 0
+        no_wait = bool(_bit >> 0)
+        offset += 1
+        return BasicCancel(consumer_tag, no_wait)
+
+
+class BasicConsumeOk(AMQPMethodPayload):
+    """
+    Confirm a new consumer
+    
+    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.
+    """
+    __slots__ = (u'consumer_tag', )
+
+    NAME = u'basic.consume-ok'
+
+    INDEX = (60, 21)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x3C\x00\x15'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'consumer-tag', u'consumer-tag', u'shortstr', reserved=False),
+    ]
+
+    def __init__(self, consumer_tag):
+        """
+        Create frame basic.consume-ok
+
+        :param consumer_tag: Holds the consumer tag specified by the client or provided by the server.
+        :type consumer_tag: binary type (max length 255) (consumer-tag in AMQP)
+        """
+        self.consumer_tag = consumer_tag
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!B', len(self.consumer_tag)))
+        buf.write(self.consumer_tag)
+        
+    def get_size(self):
+        return 1 + len(self.consumer_tag)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        consumer_tag = buf[offset:offset+s_len]
+        offset += s_len
+        return BasicConsumeOk(consumer_tag)
+
+
+class BasicCancelOk(AMQPMethodPayload):
+    """
+    Confirm a cancelled consumer
+    
+    This method confirms that the cancellation was completed.
+    """
+    __slots__ = (u'consumer_tag', )
+
+    NAME = u'basic.cancel-ok'
+
+    INDEX = (60, 31)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x3C\x00\x1F'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, True
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'consumer-tag', u'consumer-tag', u'shortstr', reserved=False),
+    ]
+
+    def __init__(self, consumer_tag):
+        """
+        Create frame basic.cancel-ok
+
+        :type consumer_tag: binary type (max length 255) (consumer-tag in AMQP)
+        """
+        self.consumer_tag = consumer_tag
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!B', len(self.consumer_tag)))
+        buf.write(self.consumer_tag)
+        
+    def get_size(self):
+        return 1 + len(self.consumer_tag)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        consumer_tag = buf[offset:offset+s_len]
+        offset += s_len
+        return BasicCancelOk(consumer_tag)
+
+
+class BasicDeliver(AMQPMethodPayload):
+    """
+    Notify the client of a consumer message
+    
+    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
+    the server responds with Deliver methods as and when messages arrive for that
+    consumer.
+    """
+    __slots__ = (u'consumer_tag', u'delivery_tag', u'redelivered', u'exchange', u'routing_key', )
+
+    NAME = u'basic.deliver'
+
+    INDEX = (60, 60)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x3C\x00\x3C'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'consumer-tag', u'consumer-tag', u'shortstr', reserved=False),
+        Field(u'delivery-tag', u'delivery-tag', u'longlong', reserved=False),
+        Field(u'redelivered', u'redelivered', u'bit', reserved=False),
+        Field(u'exchange', u'exchange-name', u'shortstr', reserved=False),
+        Field(u'routing-key', u'shortstr', u'shortstr', reserved=False),
+    ]
+
+    def __init__(self, consumer_tag, delivery_tag, redelivered, exchange, routing_key):
+        """
+        Create frame basic.deliver
+
+        :type consumer_tag: binary type (max length 255) (consumer-tag in AMQP)
+        :type delivery_tag: int, 64 bit unsigned (delivery-tag in AMQP)
+        :type redelivered: bool (redelivered in AMQP)
+        :param exchange: Specifies the name of the exchange that the message was originally published to.
+            May be empty, indicating the default exchange.
+        :type exchange: binary type (max length 255) (exchange-name in AMQP)
+        :param routing_key: Message routing key
+            Specifies the routing key name specified when the message was published.
+        :type routing_key: binary type (max length 255) (shortstr in AMQP)
+        """
+        self.consumer_tag = consumer_tag
+        self.delivery_tag = delivery_tag
+        self.redelivered = redelivered
+        self.exchange = exchange
+        self.routing_key = routing_key
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!B', len(self.consumer_tag)))
+        buf.write(self.consumer_tag)
+        buf.write(struct.pack('!QBB', self.delivery_tag, (self.redelivered << 0), len(self.exchange)))
+        buf.write(self.exchange)
+        buf.write(struct.pack('!B', len(self.routing_key)))
+        buf.write(self.routing_key)
+        
+    def get_size(self):
+        return 12 + len(self.consumer_tag) + len(self.exchange) + len(self.routing_key)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        consumer_tag = buf[offset:offset+s_len]
+        offset += s_len
+        delivery_tag, _bit, = struct.unpack_from('!QB', buf, offset)
+        offset += 8
+        redelivered = bool(_bit >> 0)
+        offset += 1
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        exchange = buf[offset:offset+s_len]
+        offset += s_len
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        routing_key = buf[offset:offset+s_len]
+        offset += s_len
+        return BasicDeliver(consumer_tag, delivery_tag, redelivered, exchange, routing_key)
+
+
+class BasicGet(AMQPMethodPayload):
+    """
+    Direct access to a queue
+    
+    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
+    functionality is more important than performance.
+    """
+    __slots__ = (u'queue', u'no_ack', )
+
+    NAME = u'basic.get'
+
+    INDEX = (60, 70)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x3C\x00\x46'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'reserved-1', u'short', u'short', reserved=True),
+        Field(u'queue', u'queue-name', u'shortstr', reserved=False),
+        Field(u'no-ack', u'no-ack', u'bit', reserved=False),
+    ]
+
+    def __init__(self, queue, no_ack):
+        """
+        Create frame basic.get
+
+        :param queue: Specifies the name of the queue to get a message from.
+        :type queue: binary type (max length 255) (queue-name in AMQP)
+        :type no_ack: bool (no-ack in AMQP)
+        """
+        self.queue = queue
+        self.no_ack = no_ack
+
+    def write_arguments(self, buf):
+        buf.write(b'\x00\x00')
+        buf.write(struct.pack('!B', len(self.queue)))
+        buf.write(self.queue)
+        buf.write(struct.pack('!B', (self.no_ack << 0)))
+        
+    def get_size(self):
+        return 4 + len(self.queue)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!2xB', buf, offset)
+        offset += 3
+        queue = buf[offset:offset+s_len]
+        offset += s_len
+        _bit, = struct.unpack_from('!B', buf, offset)
+        offset += 0
+        no_ack = bool(_bit >> 0)
+        offset += 1
+        return BasicGet(queue, no_ack)
+
+
+class BasicGetOk(AMQPMethodPayload):
+    """
+    Provide client with 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
+    get method.
+    """
+    __slots__ = (u'delivery_tag', u'redelivered', u'exchange', u'routing_key', u'message_count', )
+
+    NAME = u'basic.get-ok'
+
+    INDEX = (60, 71)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x3C\x00\x47'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'delivery-tag', u'delivery-tag', u'longlong', reserved=False),
+        Field(u'redelivered', u'redelivered', u'bit', reserved=False),
+        Field(u'exchange', u'exchange-name', u'shortstr', reserved=False),
+        Field(u'routing-key', u'shortstr', u'shortstr', reserved=False),
+        Field(u'message-count', u'message-count', u'long', reserved=False),
+    ]
+
+    def __init__(self, delivery_tag, redelivered, exchange, routing_key, message_count):
+        """
+        Create frame basic.get-ok
+
+        :type delivery_tag: int, 64 bit unsigned (delivery-tag in AMQP)
+        :type redelivered: bool (redelivered in AMQP)
+        :param exchange: Specifies the name of the exchange that the message was originally published to.
+            If empty, the message was published to the default exchange.
+        :type exchange: binary type (max length 255) (exchange-name in AMQP)
+        :param routing_key: Message routing key
+            Specifies the routing key name specified when the message was published.
+        :type routing_key: binary type (max length 255) (shortstr in AMQP)
+        :type message_count: int, 32 bit unsigned (message-count in AMQP)
+        """
+        self.delivery_tag = delivery_tag
+        self.redelivered = redelivered
+        self.exchange = exchange
+        self.routing_key = routing_key
+        self.message_count = message_count
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!QBB', self.delivery_tag, (self.redelivered << 0), len(self.exchange)))
+        buf.write(self.exchange)
+        buf.write(struct.pack('!B', len(self.routing_key)))
+        buf.write(self.routing_key)
+        buf.write(struct.pack('!I', self.message_count))
+        
+    def get_size(self):
+        return 15 + len(self.exchange) + len(self.routing_key)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        delivery_tag, _bit, = struct.unpack_from('!QB', buf, offset)
+        offset += 8
+        redelivered = bool(_bit >> 0)
+        offset += 1
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        exchange = buf[offset:offset+s_len]
+        offset += s_len
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        routing_key = buf[offset:offset+s_len]
+        offset += s_len
+        message_count, = struct.unpack_from('!I', buf, offset)
+        offset += 4
+        return BasicGetOk(delivery_tag, redelivered, exchange, routing_key, message_count)
+
+
+class BasicGetEmpty(AMQPMethodPayload):
+    """
+    Indicate no messages available
+    
+    This method tells the client that the queue has no messages available for the
+    client.
+    """
+    __slots__ = ()
+
+    NAME = u'basic.get-empty'
+
+    INDEX = (60, 72)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x3C\x00\x48'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = True  # this means that argument part has always the same content
+    STATIC_CONTENT = b'\x00\x00\x00\x0D\x00\x3C\x00\x48\x00\xCE'  # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'reserved-1', u'shortstr', u'shortstr', reserved=True),
+    ]
+
+    def __init__(self):
+        """
+        Create frame basic.get-empty
+        """
+
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        offset += s_len # reserved field!
+        return BasicGetEmpty()
+
+
+class BasicNack(AMQPMethodPayload):
+    """
+    Reject one or more incoming messages
+    
+    This method allows a client to reject one or more incoming messages. It can be
+    used to interrupt and cancel large incoming messages, or return untreatable
+    messages to their original queue.
+    This method is also used by the server to inform publishers on channels in
+    confirm mode of unhandled messages.  If a publisher receives this method, it
+    probably needs to republish the offending messages.
+    """
+    __slots__ = (u'delivery_tag', u'multiple', u'requeue', )
+
+    NAME = u'basic.nack'
+
+    INDEX = (60, 120)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x3C\x00\x78'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, True
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'delivery-tag', u'delivery-tag', u'longlong', reserved=False),
+        Field(u'multiple', u'bit', u'bit', reserved=False),
+        Field(u'requeue', u'bit', u'bit', reserved=False),
+    ]
+
+    def __init__(self, delivery_tag, multiple, requeue):
+        """
+        Create frame basic.nack
+
+        :type delivery_tag: int, 64 bit unsigned (delivery-tag in AMQP)
+        :param multiple: Reject multiple messages
+            If set to 1, the delivery tag is treated as "up to and
+            including", so that multiple messages can be rejected
+            with a single method. If set to zero, the delivery tag
+            refers to a single message. If the multiple field is 1, and
+            the delivery tag is zero, this indicates rejection of
+            all outstanding messages.
+        :type multiple: bool (bit in AMQP)
+        :param requeue: Requeue the message
+            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.
+            Clients receiving the Nack methods should ignore this flag.
+        :type requeue: bool (bit in AMQP)
+        """
+        self.delivery_tag = delivery_tag
+        self.multiple = multiple
+        self.requeue = requeue
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!QB', self.delivery_tag, (self.multiple << 0) | (self.requeue << 1)))
+        
+    def get_size(self):
+        return 9
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        delivery_tag, _bit, = struct.unpack_from('!QB', buf, offset)
+        offset += 8
+        multiple = bool(_bit >> 0)
+        requeue = bool(_bit >> 1)
+        offset += 1
+        return BasicNack(delivery_tag, multiple, requeue)
+
+
+class BasicPublish(AMQPMethodPayload):
+    """
+    Publish a message
+    
+    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
+    consumers when the transaction, if any, is committed.
+    """
+    __slots__ = (u'exchange', u'routing_key', u'mandatory', u'immediate', )
+
+    NAME = u'basic.publish'
+
+    INDEX = (60, 40)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x3C\x00\x28'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'reserved-1', u'short', u'short', reserved=True),
+        Field(u'exchange', u'exchange-name', u'shortstr', reserved=False),
+        Field(u'routing-key', u'shortstr', u'shortstr', reserved=False),
+        Field(u'mandatory', u'bit', u'bit', reserved=False),
+        Field(u'immediate', u'bit', u'bit', reserved=False),
+    ]
+
+    def __init__(self, exchange, routing_key, mandatory, immediate):
+        """
+        Create frame basic.publish
+
+        :param exchange: 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
+            exchange does not exist, the server will raise a channel exception.
+        :type exchange: binary type (max length 255) (exchange-name in AMQP)
+        :param routing_key: Message routing key
+            Specifies the routing key for the message. The routing key is used for routing
+            messages depending on the exchange configuration.
+        :type routing_key: binary type (max length 255) (shortstr in AMQP)
+        :param mandatory: Indicate mandatory routing
+            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
+            Return method. If this flag is zero, the server silently drops the message.
+        :type mandatory: bool (bit in AMQP)
+        :param immediate: Request immediate delivery
+            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
+            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.
+        :type immediate: bool (bit in AMQP)
+        """
+        self.exchange = exchange
+        self.routing_key = routing_key
+        self.mandatory = mandatory
+        self.immediate = immediate
+
+    def write_arguments(self, buf):
+        buf.write(b'\x00\x00')
+        buf.write(struct.pack('!B', len(self.exchange)))
+        buf.write(self.exchange)
+        buf.write(struct.pack('!B', len(self.routing_key)))
+        buf.write(self.routing_key)
+        buf.write(struct.pack('!B', (self.mandatory << 0) | (self.immediate << 1)))
+        
+    def get_size(self):
+        return 5 + len(self.exchange) + len(self.routing_key)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        s_len, = struct.unpack_from('!2xB', buf, offset)
+        offset += 3
+        exchange = buf[offset:offset+s_len]
+        offset += s_len
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        routing_key = buf[offset:offset+s_len]
+        offset += s_len
+        _bit, = struct.unpack_from('!B', buf, offset)
+        offset += 0
+        mandatory = bool(_bit >> 0)
+        immediate = bool(_bit >> 1)
+        offset += 1
+        return BasicPublish(exchange, routing_key, mandatory, immediate)
+
+
+class BasicQos(AMQPMethodPayload):
+    """
+    Specify quality of service
+    
+    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
+    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
+    for the server.
+    """
+    __slots__ = (u'prefetch_size', u'prefetch_count', u'global_', )
+
+    NAME = u'basic.qos'
+
+    INDEX = (60, 10)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x3C\x00\x0A'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'prefetch-size', u'long', u'long', reserved=False),
+        Field(u'prefetch-count', u'short', u'short', reserved=False),
+        Field(u'global', u'bit', u'bit', reserved=False),
+    ]
+
+    def __init__(self, prefetch_size, prefetch_count, global_):
+        """
+        Create frame basic.qos
+
+        :param prefetch_size: Prefetch window in octets
+            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,
+            rather than needing to be sent down the channel. Prefetching gives a performance
+            improvement. This field specifies the prefetch window size in octets. The server
+            will send a message in advance if it is equal to or smaller in size than the
+            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
+            apply. The prefetch-size is ignored if the no-ack option is set.
+        :type prefetch_size: int, 32 bit unsigned (long in AMQP)
+        :param prefetch_count: Prefetch window in messages
+            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
+            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.
+        :type prefetch_count: int, 16 bit unsigned (short in AMQP)
+        :param global_: Apply to entire connection
+            RabbitMQ has reinterpreted this field. The original
+            specification said: "By default the QoS settings apply to
+            the current channel only. If this field is set, they are
+            applied to the entire connection." Instead, RabbitMQ takes
+            global=false to mean that the QoS settings should apply
+            per-consumer (for new consumers on the channel; existing
+            ones being unaffected) and global=true to mean that the QoS
+            settings should apply per-channel.
+        :type global_: bool (bit in AMQP)
+        """
+        self.prefetch_size = prefetch_size
+        self.prefetch_count = prefetch_count
+        self.global_ = global_
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!IHB', self.prefetch_size, self.prefetch_count, (self.global_ << 0)))
+        
+    def get_size(self):
+        return 7
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        prefetch_size, prefetch_count, _bit, = struct.unpack_from('!IHB', buf, offset)
+        offset += 6
+        global_ = bool(_bit >> 0)
+        offset += 1
+        return BasicQos(prefetch_size, prefetch_count, global_)
+
+
+class BasicQosOk(AMQPMethodPayload):
+    """
+    Confirm the requested qos
+    
+    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
+    defined.
+    """
+    __slots__ = ()
+
+    NAME = u'basic.qos-ok'
+
+    INDEX = (60, 11)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x3C\x00\x0B'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = True  # this means that argument part has always the same content
+    STATIC_CONTENT = b'\x00\x00\x00\x04\x00\x3C\x00\x0B\xCE'  # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END
+
+    def __init__(self):
+        """
+        Create frame basic.qos-ok
+        """
+
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        return BasicQosOk()
+
+
+class BasicReturn(AMQPMethodPayload):
+    """
+    Return a failed message
+    
+    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
+    reply code and text provide information about the reason that the message was
+    undeliverable.
+    """
+    __slots__ = (u'reply_code', u'reply_text', u'exchange', u'routing_key', )
+
+    NAME = u'basic.return'
+
+    INDEX = (60, 50)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x3C\x00\x32'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = False     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'reply-code', u'reply-code', u'short', reserved=False),
+        Field(u'reply-text', u'reply-text', u'shortstr', reserved=False),
+        Field(u'exchange', u'exchange-name', u'shortstr', reserved=False),
+        Field(u'routing-key', u'shortstr', u'shortstr', reserved=False),
+    ]
+
+    def __init__(self, reply_code, reply_text, exchange, routing_key):
+        """
+        Create frame basic.return
+
+        :type reply_code: int, 16 bit unsigned (reply-code in AMQP)
+        :type reply_text: binary type (max length 255) (reply-text in AMQP)
+        :param exchange: Specifies the name of the exchange that the message was originally published
+            to.  May be empty, meaning the default exchange.
+        :type exchange: binary type (max length 255) (exchange-name in AMQP)
+        :param routing_key: Message routing key
+            Specifies the routing key name specified when the message was published.
+        :type routing_key: binary type (max length 255) (shortstr in AMQP)
+        """
+        self.reply_code = reply_code
+        self.reply_text = reply_text
+        self.exchange = exchange
+        self.routing_key = routing_key
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!HB', self.reply_code, len(self.reply_text)))
+        buf.write(self.reply_text)
+        buf.write(struct.pack('!B', len(self.exchange)))
+        buf.write(self.exchange)
+        buf.write(struct.pack('!B', len(self.routing_key)))
+        buf.write(self.routing_key)
+        
+    def get_size(self):
+        return 5 + len(self.reply_text) + len(self.exchange) + len(self.routing_key)
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        reply_code, s_len, = struct.unpack_from('!HB', buf, offset)
+        offset += 3
+        reply_text = buf[offset:offset+s_len]
+        offset += s_len
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        exchange = buf[offset:offset+s_len]
+        offset += s_len
+        s_len, = struct.unpack_from('!B', buf, offset)
+        offset += 1
+        routing_key = buf[offset:offset+s_len]
+        offset += s_len
+        return BasicReturn(reply_code, reply_text, exchange, routing_key)
+
+
+class BasicReject(AMQPMethodPayload):
+    """
+    Reject an incoming message
+    
+    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
+    queue.
+    """
+    __slots__ = (u'delivery_tag', u'requeue', )
+
+    NAME = u'basic.reject'
+
+    INDEX = (60, 90)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x3C\x00\x5A'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'delivery-tag', u'delivery-tag', u'longlong', reserved=False),
+        Field(u'requeue', u'bit', u'bit', reserved=False),
+    ]
+
+    def __init__(self, delivery_tag, requeue):
+        """
+        Create frame basic.reject
+
+        :type delivery_tag: int, 64 bit unsigned (delivery-tag in AMQP)
+        :param requeue: Requeue the message
+            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.
+        :type requeue: bool (bit in AMQP)
+        """
+        self.delivery_tag = delivery_tag
+        self.requeue = requeue
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!QB', self.delivery_tag, (self.requeue << 0)))
+        
+    def get_size(self):
+        return 9
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        delivery_tag, _bit, = struct.unpack_from('!QB', buf, offset)
+        offset += 8
+        requeue = bool(_bit >> 0)
+        offset += 1
+        return BasicReject(delivery_tag, requeue)
+
+
+class BasicRecoverAsync(AMQPMethodPayload):
+    """
+    Redeliver unacknowledged messages
+    
+    This method asks the server to redeliver all unacknowledged messages on a
+    specified channel. Zero or more messages may be redelivered.  This method
+    is deprecated in favour of the synchronous Recover/Recover-Ok.
+    """
+    __slots__ = (u'requeue', )
+
+    NAME = u'basic.recover-async'
+
+    INDEX = (60, 100)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x3C\x00\x64'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'requeue', u'bit', u'bit', reserved=False),
+    ]
+
+    def __init__(self, requeue):
+        """
+        Create frame basic.recover-async
+
+        :param requeue: Requeue the message
+            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,
+            potentially then delivering it to an alternative subscriber.
+        :type requeue: bool (bit in AMQP)
+        """
+        self.requeue = requeue
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!B', (self.requeue << 0)))
+        
+    def get_size(self):
+        return 1
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        _bit, = struct.unpack_from('!B', buf, offset)
+        offset += 0
+        requeue = bool(_bit >> 0)
+        offset += 1
+        return BasicRecoverAsync(requeue)
+
+
+class BasicRecover(AMQPMethodPayload):
+    """
+    Redeliver unacknowledged messages
+    
+    This method asks the server to redeliver all unacknowledged messages on a
+    specified channel. Zero or more messages may be redelivered.  This method
+    replaces the asynchronous Recover.
+    """
+    __slots__ = (u'requeue', )
+
+    NAME = u'basic.recover'
+
+    INDEX = (60, 110)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x3C\x00\x6E'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'requeue', u'bit', u'bit', reserved=False),
+    ]
+
+    def __init__(self, requeue):
+        """
+        Create frame basic.recover
+
+        :param requeue: Requeue the message
+            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,
+            potentially then delivering it to an alternative subscriber.
+        :type requeue: bool (bit in AMQP)
+        """
+        self.requeue = requeue
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!B', (self.requeue << 0)))
+        
+    def get_size(self):
+        return 1
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        _bit, = struct.unpack_from('!B', buf, offset)
+        offset += 0
+        requeue = bool(_bit >> 0)
+        offset += 1
+        return BasicRecover(requeue)
+
+
+class BasicRecoverOk(AMQPMethodPayload):
+    """
+    Confirm recovery
+    
+    This method acknowledges a Basic.Recover method.
+    """
+    __slots__ = ()
+
+    NAME = u'basic.recover-ok'
+
+    INDEX = (60, 111)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x3C\x00\x6F'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = True  # this means that argument part has always the same content
+    STATIC_CONTENT = b'\x00\x00\x00\x04\x00\x3C\x00\x6F\xCE'  # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END
+
+    def __init__(self):
+        """
+        Create frame basic.recover-ok
+        """
+
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        return BasicRecoverOk()
+
+
+class Tx(AMQPClass):
+    """
+    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
+    within a transaction will complete successfully or none of them will.
+    Servers SHOULD implement atomic transactions at least where all publish
+    or ack requests affect a single queue.  Transactions that cover multiple
+    queues may be non-atomic, given that queues can be created and destroyed
+    asynchronously, and such events do not form part of any transaction.
+    Further, the behaviour of transactions with respect to the immediate and
+    mandatory flags on Basic.Publish methods is not defined.
+    """
+    NAME = u'tx'
+    INDEX = 90
+
+
+class TxCommit(AMQPMethodPayload):
+    """
+    Commit the current transaction
+    
+    This method commits all message publications and acknowledgments performed in
+    the current transaction.  A new transaction starts immediately after a commit.
+    """
+    __slots__ = ()
+
+    NAME = u'tx.commit'
+
+    INDEX = (90, 20)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x5A\x00\x14'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = True  # this means that argument part has always the same content
+    STATIC_CONTENT = b'\x00\x00\x00\x04\x00\x5A\x00\x14\xCE'  # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END
+
+    def __init__(self):
+        """
+        Create frame tx.commit
+        """
+
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        return TxCommit()
+
+
+class TxCommitOk(AMQPMethodPayload):
+    """
+    Confirm a successful commit
+    
+    This method confirms to the client that the commit succeeded. Note that if a commit
+    fails, the server raises a channel exception.
+    """
+    __slots__ = ()
+
+    NAME = u'tx.commit-ok'
+
+    INDEX = (90, 21)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x5A\x00\x15'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = True  # this means that argument part has always the same content
+    STATIC_CONTENT = b'\x00\x00\x00\x04\x00\x5A\x00\x15\xCE'  # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END
+
+    def __init__(self):
+        """
+        Create frame tx.commit-ok
+        """
+
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        return TxCommitOk()
+
+
+class TxRollback(AMQPMethodPayload):
+    """
+    Abandon the current transaction
+    
+    This method abandons all message publications and acknowledgments performed in
+    the current transaction. A new transaction starts immediately after a rollback.
+    Note that unacked messages will not be automatically redelivered by rollback;
+    if that is required an explicit recover call should be issued.
+    """
+    __slots__ = ()
+
+    NAME = u'tx.rollback'
+
+    INDEX = (90, 30)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x5A\x00\x1E'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = True  # this means that argument part has always the same content
+    STATIC_CONTENT = b'\x00\x00\x00\x04\x00\x5A\x00\x1E\xCE'  # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END
+
+    def __init__(self):
+        """
+        Create frame tx.rollback
+        """
+
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        return TxRollback()
+
+
+class TxRollbackOk(AMQPMethodPayload):
+    """
+    Confirm successful rollback
+    
+    This method confirms to the client that the rollback succeeded. Note that if an
+    rollback fails, the server raises a channel exception.
+    """
+    __slots__ = ()
+
+    NAME = u'tx.rollback-ok'
+
+    INDEX = (90, 31)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x5A\x00\x1F'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = True  # this means that argument part has always the same content
+    STATIC_CONTENT = b'\x00\x00\x00\x04\x00\x5A\x00\x1F\xCE'  # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END
+
+    def __init__(self):
+        """
+        Create frame tx.rollback-ok
+        """
+
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        return TxRollbackOk()
+
+
+class TxSelect(AMQPMethodPayload):
+    """
+    Select standard transaction mode
+    
+    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.
+    """
+    __slots__ = ()
+
+    NAME = u'tx.select'
+
+    INDEX = (90, 10)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x5A\x00\x0A'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = True  # this means that argument part has always the same content
+    STATIC_CONTENT = b'\x00\x00\x00\x04\x00\x5A\x00\x0A\xCE'  # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END
+
+    def __init__(self):
+        """
+        Create frame tx.select
+        """
+
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        return TxSelect()
+
+
+class TxSelectOk(AMQPMethodPayload):
+    """
+    Confirm transaction mode
+    
+    This method confirms to the client that the channel was successfully set to use
+    standard transactions.
+    """
+    __slots__ = ()
+
+    NAME = u'tx.select-ok'
+
+    INDEX = (90, 11)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x5A\x00\x0B'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = True  # this means that argument part has always the same content
+    STATIC_CONTENT = b'\x00\x00\x00\x04\x00\x5A\x00\x0B\xCE'  # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END
+
+    def __init__(self):
+        """
+        Create frame tx.select-ok
+        """
+
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        return TxSelectOk()
+
+
+class Confirm(AMQPClass):
+    """
+    The confirm class allows publishers to put the channel in
+    
+    confirm mode and subsequently be notified when messages have been
+    handled by the broker.  The intention is that all messages
+    published on a channel in confirm mode will be acknowledged at
+    some point.  By acknowledging a message the broker assumes
+    responsibility for it and indicates that it has done something
+    it deems reasonable with it.
+    Unroutable mandatory or immediate messages are acknowledged
+    right after the Basic.Return method. Messages are acknowledged
+    when all queues to which the message has been routed
+    have either delivered the message and received an
+    acknowledgement (if required), or enqueued the message (and
+    persisted it if required).
+    Published messages are assigned ascending sequence numbers,
+    starting at 1 with the first Confirm.Select method. The server
+    confirms messages by sending Basic.Ack methods referring to these
+    sequence numbers.
+    """
+    NAME = u'confirm'
+    INDEX = 85
+
+
+class ConfirmSelect(AMQPMethodPayload):
+    """
+    This method sets the channel to use publisher acknowledgements.
+    
+    The client can only use this method on a non-transactional
+    channel.
+    """
+    __slots__ = (u'nowait', )
+
+    NAME = u'confirm.select'
+
+    INDEX = (85, 10)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x55\x00\x0A'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = True, False
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = False  # this means that argument part has always the same content
+
+    # See constructor pydoc for details
+    FIELDS = [ 
+        Field(u'nowait', u'bit', u'bit', reserved=False),
+    ]
+
+    def __init__(self, nowait):
+        """
+        Create frame confirm.select
+
+        :param nowait: If set, the server will not respond to the method. the client should
+            not wait for a reply method.  If the server could not complete the
+            method it will raise a channel or connection exception.
+        :type nowait: bool (bit in AMQP)
+        """
+        self.nowait = nowait
+
+    def write_arguments(self, buf):
+        buf.write(struct.pack('!B', (self.nowait << 0)))
+        
+    def get_size(self):
+        return 1
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        _bit, = struct.unpack_from('!B', buf, offset)
+        offset += 0
+        nowait = bool(_bit >> 0)
+        offset += 1
+        return ConfirmSelect(nowait)
+
+
+class ConfirmSelectOk(AMQPMethodPayload):
+    """
+    This method confirms to the client that the channel was successfully
+    
+    set to use publisher acknowledgements.
+    """
+    __slots__ = ()
+
+    NAME = u'confirm.select-ok'
+
+    INDEX = (85, 11)          # (Class ID, Method ID)
+    BINARY_HEADER = b'\x00\x55\x00\x0B'      # CLASS ID + METHOD ID
+
+    SENT_BY_CLIENT, SENT_BY_SERVER = False, True
+
+    IS_SIZE_STATIC = True     # this means that argument part has always the same length
+    IS_CONTENT_STATIC = True  # this means that argument part has always the same content
+    STATIC_CONTENT = b'\x00\x00\x00\x04\x00\x55\x00\x0B\xCE'  # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END
+
+    def __init__(self):
+        """
+        Create frame confirm.select-ok
+        """
+
+
+    @staticmethod
+    def from_buffer(buf, start_offset):
+        offset = start_offset
+        return ConfirmSelectOk()
+
+
+IDENT_TO_METHOD = {
+    (90, 21): TxCommitOk,
+    (60, 100): BasicRecoverAsync,
+    (10, 11): ConnectionStartOk,
+    (60, 40): BasicPublish,
+    (60, 50): BasicReturn,
+    (40, 21): ExchangeDeleteOk,
+    (20, 20): ChannelFlow,
+    (40, 31): ExchangeBindOk,
+    (60, 21): BasicConsumeOk,
+    (10, 21): ConnectionSecureOk,
+    (90, 30): TxRollback,
+    (90, 10): TxSelect,
+    (85, 11): ConfirmSelectOk,
+    (10, 61): ConnectionUnblocked,
+    (50, 11): QueueDeclareOk,
+    (60, 70): BasicGet,
+    (90, 11): TxSelectOk,
+    (10, 30): ConnectionTune,
+    (60, 11): BasicQosOk,
+    (60, 80): BasicAck,
+    (20, 21): ChannelFlowOk,
+    (60, 60): BasicDeliver,
+    (90, 31): TxRollbackOk,
+    (60, 20): BasicConsume,
+    (85, 10): ConfirmSelect,
+    (20, 40): ChannelClose,
+    (60, 71): BasicGetOk,
+    (50, 30): QueuePurge,
+    (10, 31): ConnectionTuneOk,
+    (10, 40): ConnectionOpen,
+    (60, 30): BasicCancel,
+    (50, 50): QueueUnbind,
+    (40, 10): ExchangeDeclare,
+    (10, 50): ConnectionClose,
+    (20, 10): ChannelOpen,
+    (20, 41): ChannelCloseOk,
+    (60, 110): BasicRecover,
+    (60, 90): BasicReject,
+    (50, 31): QueuePurgeOk,
+    (50, 40): QueueDelete,
+    (40, 20): ExchangeDelete,
+    (50, 20): QueueBind,
+    (10, 41): ConnectionOpenOk,
+    (60, 120): BasicNack,
+    (60, 31): BasicCancelOk,
+    (90, 20): TxCommit,
+    (10, 10): ConnectionStart,
+    (60, 10): BasicQos,
+    (40, 11): ExchangeDeclareOk,
+    (10, 51): ConnectionCloseOk,
+    (40, 51): ExchangeUnbindOk,
+    (20, 11): ChannelOpenOk,
+    (60, 72): BasicGetEmpty,
+    (40, 30): ExchangeBind,
+    (60, 111): BasicRecoverOk,
+    (40, 40): ExchangeUnbind,
+    (10, 20): ConnectionSecure,
+    (50, 41): QueueDeleteOk,
+    (50, 51): QueueUnbindOk,
+    (50, 21): QueueBindOk,
+    (10, 60): ConnectionBlocked,
+    (50, 10): QueueDeclare,
+}
+
+
+BINARY_HEADER_TO_METHOD = {
+    b'\x00\x5A\x00\x15': TxCommitOk,
+    b'\x00\x3C\x00\x64': BasicRecoverAsync,
+    b'\x00\x0A\x00\x0B': ConnectionStartOk,
+    b'\x00\x3C\x00\x28': BasicPublish,
+    b'\x00\x3C\x00\x32': BasicReturn,
+    b'\x00\x28\x00\x15': ExchangeDeleteOk,
+    b'\x00\x14\x00\x14': ChannelFlow,
+    b'\x00\x28\x00\x1F': ExchangeBindOk,
+    b'\x00\x3C\x00\x15': BasicConsumeOk,
+    b'\x00\x0A\x00\x15': ConnectionSecureOk,
+    b'\x00\x5A\x00\x1E': TxRollback,
+    b'\x00\x5A\x00\x0A': TxSelect,
+    b'\x00\x55\x00\x0B': ConfirmSelectOk,
+    b'\x00\x0A\x00\x3D': ConnectionUnblocked,
+    b'\x00\x32\x00\x0B': QueueDeclareOk,
+    b'\x00\x3C\x00\x46': BasicGet,
+    b'\x00\x5A\x00\x0B': TxSelectOk,
+    b'\x00\x0A\x00\x1E': ConnectionTune,
+    b'\x00\x3C\x00\x0B': BasicQosOk,
+    b'\x00\x3C\x00\x50': BasicAck,
+    b'\x00\x14\x00\x15': ChannelFlowOk,
+    b'\x00\x3C\x00\x3C': BasicDeliver,
+    b'\x00\x5A\x00\x1F': TxRollbackOk,
+    b'\x00\x3C\x00\x14': BasicConsume,
+    b'\x00\x55\x00\x0A': ConfirmSelect,
+    b'\x00\x14\x00\x28': ChannelClose,
+    b'\x00\x3C\x00\x47': BasicGetOk,
+    b'\x00\x32\x00\x1E': QueuePurge,
+    b'\x00\x0A\x00\x1F': ConnectionTuneOk,
+    b'\x00\x0A\x00\x28': ConnectionOpen,
+    b'\x00\x3C\x00\x1E': BasicCancel,
+    b'\x00\x32\x00\x32': QueueUnbind,
+    b'\x00\x28\x00\x0A': ExchangeDeclare,
+    b'\x00\x0A\x00\x32': ConnectionClose,
+    b'\x00\x14\x00\x0A': ChannelOpen,
+    b'\x00\x14\x00\x29': ChannelCloseOk,
+    b'\x00\x3C\x00\x6E': BasicRecover,
+    b'\x00\x3C\x00\x5A': BasicReject,
+    b'\x00\x32\x00\x1F': QueuePurgeOk,
+    b'\x00\x32\x00\x28': QueueDelete,
+    b'\x00\x28\x00\x14': ExchangeDelete,
+    b'\x00\x32\x00\x14': QueueBind,
+    b'\x00\x0A\x00\x29': ConnectionOpenOk,
+    b'\x00\x3C\x00\x78': BasicNack,
+    b'\x00\x3C\x00\x1F': BasicCancelOk,
+    b'\x00\x5A\x00\x14': TxCommit,
+    b'\x00\x0A\x00\x0A': ConnectionStart,
+    b'\x00\x3C\x00\x0A': BasicQos,
+    b'\x00\x28\x00\x0B': ExchangeDeclareOk,
+    b'\x00\x0A\x00\x33': ConnectionCloseOk,
+    b'\x00\x28\x00\x33': ExchangeUnbindOk,
+    b'\x00\x14\x00\x0B': ChannelOpenOk,
+    b'\x00\x3C\x00\x48': BasicGetEmpty,
+    b'\x00\x28\x00\x1E': ExchangeBind,
+    b'\x00\x3C\x00\x6F': BasicRecoverOk,
+    b'\x00\x28\x00\x28': ExchangeUnbind,
+    b'\x00\x0A\x00\x14': ConnectionSecure,
+    b'\x00\x32\x00\x29': QueueDeleteOk,
+    b'\x00\x32\x00\x33': QueueUnbindOk,
+    b'\x00\x32\x00\x15': QueueBindOk,
+    b'\x00\x0A\x00\x3C': ConnectionBlocked,
+    b'\x00\x32\x00\x0A': QueueDeclare,
+}
+
+
+CLASS_ID_TO_CONTENT_PROPERTY_LIST = {
+    60: BasicContentPropertyList,
+}
+
+# Methods that are sent as replies to other methods, ie. ConnectionOpenOk: ConnectionOpen
+# if a method is NOT a reply, it will not be in this dict
+# a method may be a reply for AT MOST one method
+REPLY_REASONS_FOR = {
+    BasicGetEmpty: BasicGet,
+    BasicGetOk: BasicGet,
+    ExchangeDeleteOk: ExchangeDelete,
+    TxSelectOk: TxSelect,
+    QueueBindOk: QueueBind,
+    BasicConsumeOk: BasicConsume,
+    BasicCancelOk: BasicCancel,
+    TxRollbackOk: TxRollback,
+    TxCommitOk: TxCommit,
+    ChannelOpenOk: ChannelOpen,
+    QueueDeleteOk: QueueDelete,
+    ExchangeUnbindOk: ExchangeUnbind,
+    ExchangeBindOk: ExchangeBind,
+    ChannelCloseOk: ChannelClose,
+    BasicQosOk: BasicQos,
+    ConnectionStartOk: ConnectionStart,
+    QueueUnbindOk: QueueUnbind,
+    ConfirmSelectOk: ConfirmSelect,
+    ConnectionCloseOk: ConnectionClose,
+    QueuePurgeOk: QueuePurge,
+    QueueDeclareOk: QueueDeclare,
+    ExchangeDeclareOk: ExchangeDeclare,
+    ConnectionTuneOk: ConnectionTune,
+    ConnectionSecureOk: ConnectionSecure,
+    ConnectionOpenOk: ConnectionOpen,
+    ChannelFlowOk: ChannelFlow,
+}
+
+# Methods that are replies for other, ie. ConnectionOpenOk: ConnectionOpen
+# a method may be a reply for ONE or NONE other methods
+# if a method has no replies, it will have an empty list as value here
+REPLIES_FOR= {
+    BasicGetEmpty: [],
+    BasicRecoverOk: [],
+    BasicReturn: [],
+    QueueDeclare: [QueueDeclareOk],
+    BasicGetOk: [],
+    ConnectionSecure: [ConnectionSecureOk],
+    ConnectionTune: [ConnectionTuneOk],
+    TxRollback: [TxRollbackOk],
+    TxSelectOk: [],
+    QueueBindOk: [],
+    ChannelFlow: [ChannelFlowOk],
+    BasicConsumeOk: [],
+    BasicConsume: [BasicConsumeOk],
+    BasicRecover: [],
+    BasicCancelOk: [],
+    ConfirmSelect: [ConfirmSelectOk],
+    BasicGet: [BasicGetOk, BasicGetEmpty],
+    TxRollbackOk: [],
+    QueueBind: [QueueBindOk],
+    ExchangeDelete: [ExchangeDeleteOk],
+    BasicAck: [],
+    ConnectionClose: [ConnectionCloseOk],
+    ChannelOpenOk: [],
+    QueueDeleteOk: [],
+    ExchangeUnbindOk: [],
+    ConnectionStart: [ConnectionStartOk],
+    BasicQos: [BasicQosOk],
+    QueueUnbind: [QueueUnbindOk],
+    BasicQosOk: [],
+    BasicReject: [],
+    ExchangeBindOk: [],
+    ChannelCloseOk: [],
+    ExchangeDeclare: [ExchangeDeclareOk],
+    ConnectionBlocked: [],
+    BasicPublish: [],
+    ExchangeUnbind: [ExchangeUnbindOk],
+    ExchangeDeleteOk: [],
+    BasicNack: [],
+    ConnectionStartOk: [],
+    ExchangeBind: [ExchangeBindOk],
+    QueueDelete: [QueueDeleteOk],
+    ConfirmSelectOk: [],
+    ConnectionCloseOk: [],
+    QueuePurge: [QueuePurgeOk],
+    QueueUnbindOk: [],
+    ChannelOpen: [ChannelOpenOk],
+    ChannelClose: [ChannelCloseOk],
+    QueuePurgeOk: [],
+    QueueDeclareOk: [],
+    BasicCancel: [BasicCancelOk],
+    ExchangeDeclareOk: [],
+    TxCommitOk: [],
+    ConnectionTuneOk: [],
+    ConnectionSecureOk: [],
+    ConnectionUnblocked: [],
+    ConnectionOpenOk: [],
+    ChannelFlowOk: [],
+    BasicRecoverAsync: [],
+    TxSelect: [TxSelectOk],
+    BasicDeliver: [],
+    TxCommit: [TxCommitOk],
+    ConnectionOpen: [ConnectionOpenOk],
+}
diff --git a/coolamqp/framing/extensions.py b/coolamqp/framing/extensions.py
new file mode 100644
index 0000000000000000000000000000000000000000..355ed0fc9ce824327970ff7786abef24d4133fc5
--- /dev/null
+++ b/coolamqp/framing/extensions.py
@@ -0,0 +1,5 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+"""Extension definitions"""
+
+PUBLISHER_CONFIRMS = b'publisher_confirms'
\ No newline at end of file
diff --git a/coolamqp/framing/field_table.py b/coolamqp/framing/field_table.py
new file mode 100644
index 0000000000000000000000000000000000000000..b53faa44c86b51039fb799b337a2694a2c29379c
--- /dev/null
+++ b/coolamqp/framing/field_table.py
@@ -0,0 +1,188 @@
+# coding=UTF-8
+"""
+That funny type, field-table...
+
+A field-value is of form (value::any, type::char)
+
+An array is of form [field-value1, field-value2, ...]
+
+A table is of form ( (name1::bytes, fv1), (name2::bytes, fv2), ...)
+
+"""
+from __future__ import absolute_import, division, print_function
+import struct
+import six
+
+
+def enframe_decimal(buf, v):       # convert decimal to bytes
+    dps = 0
+    for k in six.moves.xrange(20):
+        k = v * (10 ** dps)
+        if abs(k-int(k)) < 0.00001: # epsilon
+            return buf.write(struct.pack('!BI', dps, k))
+
+    raise ValueError('Could not convert %s to decimal', v)
+
+
+def deframe_decimal(buf, offset):
+    scale, val = struct.unpack_from('!BI', buf, offset)
+    return val / (10 ** scale), 5
+
+
+def deframe_shortstr(buf, offset):      # -> value, bytes_eaten
+    ln, = struct.unpack_from('!B', buf, offset)
+    return buf[offset+1:offset+1+ln], 1+ln
+
+
+def enframe_shortstr(buf, value):
+    buf.write(struct.pack('!B', len(value)))
+    buf.write(value)
+
+
+def deframe_longstr(buf, offset):  # -> value, bytes_eaten
+    ln, = struct.unpack_from('!I', buf, offset)
+    return buf[offset+4:offset+4+ln], 4 + ln
+
+
+def enframe_longstr(buf, value):
+    buf.write(struct.pack('!I', len(value)))
+    buf.write(value)
+
+
+FIELD_TYPES = {
+        # length, struct, (option)to_bytes (callable(buffer, value)),
+        #                 (option)from_bytes (callable(buffer, offset) -> value, bytes_consumed),
+        #                 (option)get_len (callable(value) -> length in bytes)
+    't': (1, '!?'),      # boolean
+    'b': (1, '!b'),
+    'B': (1, '!B'),
+    'U': (2, '!H'),
+    'u': (2, '!h'),
+    'I': (4, '!I'),
+    'i': (4, '!i'),
+    'L': (8, '!Q'),
+    'l': (8, '!q'),
+    'f': (4, '!f'),
+    'd': (8, '!d'),
+    'D': (5, None, enframe_decimal, deframe_decimal), # decimal-value
+    's': (None, None, enframe_shortstr, deframe_shortstr, lambda val: len(val)+1),    # shortstr
+    'S': (None, None, enframe_longstr, deframe_longstr, lambda val: len(val)+4),  # longstr
+    'T': (8, '!Q'),
+    'V': (0, None, lambda buf, v: None, lambda buf, ofs: None, 0),       # rendered as None
+}
+
+
+def enframe_field_value(buf, fv):
+    value, type = fv
+    buf.write(type)
+
+    opt = FIELD_TYPES[type]
+
+    if opt[1] is not None:
+        buf.write(struct.pack(opt[1], value))
+    else:
+        opt[2](buf, value)
+
+
+def deframe_field_value(buf, offset):  # -> (value, type), bytes_consumed
+    start_offset = offset
+    field_type = buf[offset]
+    offset += 1
+
+    if field_type not in FIELD_TYPES.keys():
+        raise ValueError('Unknown field type %s!', (repr(field_type),))
+
+    opt = FIELD_TYPES[field_type]
+
+    if opt[1] is not None:
+        field_val, = struct.unpack_from(FIELD_TYPES[field_type][1], buf, offset)
+        offset += opt[0]
+    else:
+        field_val, delta = opt[3](buf, offset)
+        offset += delta
+
+    return (field_val, field_type), offset - start_offset
+
+
+def deframe_array(buf, offset):
+    start_offset = offset
+    ln, = struct.unpack_from('!I', buf, offset)
+    offset += 4
+
+    values = []
+    while offset < (start_offset+1+ln):
+        v, t, delta = deframe_field_value(buf, offset)
+        offset += delta
+        values.append((v,t))
+
+    if offset != start_offset+4+ln:
+        raise ValueError('Array longer than expected, took %s, expected %s bytes',
+                         (offset-(start_offset+ln+4), ln+4))
+
+    return values, ln+4
+
+
+def enframe_array(buf, array):
+    buf.write(struct.pack('!I', frame_array_size(array)-4))
+    for fv in array:
+        enframe_field_value(buf, fv)
+
+
+def enframe_table(buf, table):
+    """
+    Write AMQP table to buffer
+    :param buf:
+    :param table:
+    :return:
+    """
+    buf.write(struct.pack('!I', frame_table_size(table)-4))
+
+    for name, fv in table:
+        buf.write(struct.pack('!B', len(name)))
+        buf.write(name)
+        enframe_field_value(buf, fv)
+
+
+def deframe_table(buf, start_offset): # -> (table, bytes_consumed)
+    """:return: tuple (table, bytes consumed)"""
+    offset = start_offset
+    table_length, = struct.unpack_from('!L', buf, start_offset)
+    offset += 4
+
+    # we will check if it's really so.
+    fields = []
+
+    while offset < (start_offset+table_length+4):
+        field_name, ln = deframe_shortstr(buf, offset)
+        offset += ln
+        fv, delta = deframe_field_value(buf, offset)
+        offset += delta
+        fields.append((field_name, fv))
+
+    if offset > (start_offset+table_length+4):
+        raise ValueError('Table turned out longer than expected! Found %s bytes expected %s',
+                         (offset-start_offset, table_length))
+
+    return fields, table_length+4
+
+
+def frame_field_value_size(fv):
+    v,t=fv
+    if FIELD_TYPES[t][0] is None:
+        return FIELD_TYPES[t][4](v) + 1
+    else:
+        return FIELD_TYPES[t][0] + 1
+
+
+def frame_array_size(array):
+    return 4 + sum(frame_field_value_size(fv) for fv in array)
+
+
+def frame_table_size(table):
+    """:return: length of table representation, in bytes, INCLUDING length header"""
+
+    return 4 + sum(1 + len(k) + frame_field_value_size(fv) for k, fv in table)
+
+
+FIELD_TYPES['A'] = (None, None, enframe_array, deframe_array, frame_array_size)
+FIELD_TYPES['F'] = (None, None, enframe_table, deframe_table, frame_table_size)
diff --git a/coolamqp/framing/frames.py b/coolamqp/framing/frames.py
new file mode 100644
index 0000000000000000000000000000000000000000..f45fe5ee263384d9290364a00770ecd440cc21ae
--- /dev/null
+++ b/coolamqp/framing/frames.py
@@ -0,0 +1,127 @@
+# coding=UTF-8
+"""
+Concrete frame definitions
+"""
+from __future__ import absolute_import, division, print_function
+
+import struct
+import six
+
+from coolamqp.framing.base import AMQPFrame
+from coolamqp.framing.definitions import FRAME_METHOD, FRAME_HEARTBEAT, FRAME_BODY, FRAME_HEADER, FRAME_END, \
+    IDENT_TO_METHOD, CLASS_ID_TO_CONTENT_PROPERTY_LIST
+
+
+class AMQPMethodFrame(AMQPFrame):
+    FRAME_TYPE = FRAME_METHOD
+
+    def __init__(self, channel, payload):
+        """
+        :param channel: channel ID
+        :param payload: AMQPMethodPayload instance
+        """
+        AMQPFrame.__init__(self, channel)
+        self.payload = payload
+
+    def write_to(self, buf):
+        if self.payload.IS_CONTENT_STATIC:
+            buf.write(struct.pack('!BH', FRAME_METHOD, self.channel))
+            buf.write(self.payload.STATIC_CONTENT)
+        else:
+            buf.write(struct.pack('!BHL', FRAME_METHOD, self.channel,
+                                  4 + self.payload.get_size()))
+            buf.write(self.payload.BINARY_HEADER)
+            self.payload.write_arguments(buf)
+            buf.write(chr(FRAME_END))
+
+    @staticmethod
+    def unserialize(channel, payload_as_buffer):
+        clsmet = struct.unpack_from('!HH', payload_as_buffer, 0)
+
+        try:
+            method_payload_class = IDENT_TO_METHOD[clsmet]
+            payload = method_payload_class.from_buffer(payload_as_buffer, 4)
+        except KeyError:
+            raise ValueError('Invalid class %s method %s' % clsmet)
+        else:
+            return AMQPMethodFrame(channel, payload)
+
+    def get_size(self):
+        # frame_header = (method(1) + channel(2) + length(4) + class(2) + method(2) + payload(N) + frame_end(1))
+        return 12 + self.payload.get_size()
+
+
+class AMQPHeaderFrame(AMQPFrame):
+    FRAME_TYPE = FRAME_HEADER
+
+    def __init__(self, channel, class_id, weight, body_size, properties):
+        """
+        :param channel: channel ID
+        :param class_id: class ID
+        :param weight: weight (lol wut?)
+        :param body_size: size of the body to follow
+        :param properties: a suitable AMQPContentPropertyList instance
+        """
+        AMQPFrame.__init__(self, channel)
+        self.class_id = class_id
+        self.weight = weight
+        self.body_size = body_size
+        self.properties = properties
+
+    def write_to(self, buf):
+        buf.write(struct.pack('!BHLHHQ', FRAME_HEADER, self.channel,
+                              12+self.properties.get_size(), self.class_id, 0, self.body_size))
+        self.properties.write_to(buf)
+        buf.write(chr(FRAME_END))
+
+    @staticmethod
+    def unserialize(channel, payload_as_buffer):
+        # payload starts with class ID
+        class_id, weight, body_size = struct.unpack_from('!HHQ', payload_as_buffer, 0)
+        properties = CLASS_ID_TO_CONTENT_PROPERTY_LIST[class_id].from_buffer(payload_as_buffer, 12)
+        return AMQPHeaderFrame(channel, class_id, weight, body_size, properties)
+
+    def get_size(self):
+        # frame header is always 7, frame end is 1, content header is 12 + props
+        return 20 + self.properties.get_size()
+
+
+class AMQPBodyFrame(AMQPFrame):
+    FRAME_TYPE = FRAME_BODY
+
+    FRAME_SIZE_WITHOUT_PAYLOAD = 8
+
+    def __init__(self, channel, data):
+        """
+        :type data: binary
+        """
+        AMQPFrame.__init__(self, channel)
+        assert isinstance(data, (six.binary_type, buffer, memoryview))
+        self.data = data
+
+    def write_to(self, buf):
+        buf.write(struct.pack('!BHL', FRAME_BODY, self.channel, len(self.data)))
+        buf.write(self.data)
+        buf.write(chr(FRAME_END))
+
+    @staticmethod
+    def unserialize(channel, payload_as_buffer):
+        return AMQPBodyFrame(channel, payload_as_buffer)
+
+    def get_size(self):
+        return 8 + len(self.data)
+
+
+class AMQPHeartbeatFrame(AMQPFrame):
+    FRAME_TYPE = FRAME_HEARTBEAT
+    LENGTH = 8
+    DATA = struct.pack('!BHLB', FRAME_HEARTBEAT, 0, 0, FRAME_END)
+
+    def __init__(self):
+        AMQPFrame.__init__(self, 0)
+
+    def write_to(self, buf):
+        buf.write(AMQPHeartbeatFrame.DATA)
+
+    def get_size(self):
+        return AMQPHeartbeatFrame.LENGTH
diff --git a/coolamqp/handler.py b/coolamqp/handler.py
deleted file mode 100644
index 1c32fde861bd7ba11eb1fc819d5fdbceb40189d9..0000000000000000000000000000000000000000
--- a/coolamqp/handler.py
+++ /dev/null
@@ -1,294 +0,0 @@
-# coding=UTF-8
-import threading
-from six.moves import queue
-import six
-import logging
-import collections
-import time
-from .backends import ConnectionFailedError, RemoteAMQPError, Cancelled
-from .messages import Exchange
-from .events import ConnectionUp, ConnectionDown, ConsumerCancelled, MessageReceived
-from .orders import SendMessage, DeclareExchange, ConsumeQueue, CancelQueue, \
-                    AcknowledgeMessage, NAcknowledgeMessage, DeleteQueue, \
-                    DeleteExchange, SetQoS, DeclareQueue
-
-logger = logging.getLogger(__name__)
-
-
-class _ImOuttaHere(Exception):
-    """Thrown upon thread terminating.
-    Thrown only if complete_remaining_upon_termination is False"""
-
-
-class ClusterHandlerThread(threading.Thread):
-    """
-    Thread that does bookkeeping for a Cluster.
-    """
-    def __init__(self, cluster):
-        """
-        :param cluster: coolamqp.Cluster
-        """
-        threading.Thread.__init__(self)
-
-        self.cluster = cluster
-        self.daemon = True      # if you don't explicitly wait for me, that means you don't need to
-        self.is_terminating = False
-        self.complete_remaining_upon_termination = False
-        self.order_queue = collections.deque()    # queue for inbound orders
-        self.event_queue = queue.Queue()    # queue for tasks done
-        self.connect_id = -1                # connectID of current connection
-
-        self.declared_exchanges = {}        # declared exchanges, by their names
-        self.queues_by_consumer_tags = {}   # tuple of (subbed queue, no_ack::bool), by consumer tags
-
-        self.backend = None
-        self.first_connect = True
-
-        self.qos = None # or tuple (prefetch_size, prefetch_count) if QoS set
-
-    def _reconnect_attempt(self):
-        """Single attempt to regain connectivity. May raise ConnectionFailedError"""
-        self.backend = None
-        if self.backend is not None:
-            self.backend.shutdown()
-            self.backend = None
-
-        self.connect_id += 1
-        node = six.next(self.cluster.node_to_connect_to)
-        logger.info('Connecting to %s', node)
-
-        self.backend = self.cluster.backend(node, self)
-
-        if self.qos is not None:
-            pre_siz, pre_cou, glob = self.qos
-            self.backend.basic_qos(pre_siz, pre_cou, glob)
-
-        for exchange in self.declared_exchanges.values():
-            self.backend.exchange_declare(exchange)
-
-        failed_queues = []
-        for queue, no_ack in self.queues_by_consumer_tags.values():
-            while True:
-                try:
-                    self.backend.queue_declare(queue)
-                    if queue.exchange is not None:
-                        self.backend.queue_bind(queue, queue.exchange)
-                    self.backend.basic_consume(queue, no_ack=no_ack)
-                    logger.info('Consuming from %s no_ack=%s', queue, no_ack)
-                except RemoteAMQPError as e:
-                    if e.code in (403, 405):  # access refused, resource locked
-                        # Ok, queue, what should we do?
-                        if queue.locked_after_reconnect == 'retry':
-                            time.sleep(0.1)
-                            continue    # retry until works
-                        elif queue.locked_after_reconnect == 'cancel':
-                            self.event_queue.put(ConsumerCancelled(queue, ConsumerCancelled.REFUSED_ON_RECONNECT))
-                            failed_queues.append(queue)
-                        elif queue.locked_after_reconnect == 'defer':
-                            self.order_queue.append(ConsumeQueue(queue, no_ack=no_ack))
-                            failed_queues.append(queue)
-                        else:
-                            raise Exception('wtf')
-                    else:
-                        raise  # idk
-                break
-
-        for failed_queue in failed_queues:
-            del self.queues_by_consumer_tags[failed_queue.consumer_tag]
-
-    def _reconnect(self):
-        """Regain connectivity to cluster. May block for a very long time,
-        as it will not """
-        exponential_backoff_delay = 1
-
-        while not self.cluster.connected:
-            try:
-                self._reconnect_attempt()
-            except ConnectionFailedError as e:
-                # a connection failure happened :(
-                logger.warning('Connecting failed due to %s while connecting and initial setup', repr(e))
-                self.cluster.connected = False
-                if self.backend is not None:
-                    self.backend.shutdown()
-                    self.backend = None # good policy to release resources before you sleep
-                time.sleep(exponential_backoff_delay)
-
-                if self.is_terminating and (not self.complete_remaining_upon_termination):
-                    raise _ImOuttaHere()
-
-                exponential_backoff_delay = min(60, exponential_backoff_delay * 2)
-            else:
-                logger.info('Connected to AMQP broker via %s', self.backend)
-                self.cluster.connected = True
-                self.event_queue.put(ConnectionUp(initial=self.first_connect))
-                self.first_connect = False
-
-
-    def perform_order(self):
-        order = self.order_queue.popleft()
-
-        try:
-            if order.cancelled:
-                logger.debug('Order %s was cancelled', order)
-                order._failed(Cancelled())
-                return
-
-            if isinstance(order, SendMessage):
-                self.backend.basic_publish(order.message, order.exchange, order.routing_key)
-            elif isinstance(order, SetQoS):
-                self.qos = order.qos
-                pre_siz, pre_cou, glob = order.qos
-                self.backend.basic_qos(pre_siz, pre_cou, glob)
-            elif isinstance(order, DeclareExchange):
-                self.backend.exchange_declare(order.exchange)
-                self.declared_exchanges[order.exchange.name] = order.exchange
-            elif isinstance(order, DeleteExchange):
-                self.backend.exchange_delete(order.exchange)
-                if order.exchange.name in self.declared_exchanges:
-                    del self.declared_exchanges[order.exchange.name]
-            elif isinstance(order, DeclareQueue):
-                self.backend.queue_declare(order.queue)
-            elif isinstance(order, DeleteQueue):
-                self.backend.queue_delete(order.queue)
-            elif isinstance(order, ConsumeQueue):
-                if order.queue.consumer_tag in self.queues_by_consumer_tags:
-                    order._completed()
-                    return    # already consuming, belay that
-
-                self.backend.queue_declare(order.queue)
-
-                if order.queue.exchange is not None:
-                    self.backend.queue_bind(order.queue, order.queue.exchange)
-
-                self.backend.basic_consume(order.queue, no_ack=order.no_ack)
-                self.queues_by_consumer_tags[order.queue.consumer_tag] = order.queue, order.no_ack
-            elif isinstance(order, CancelQueue):
-                try:
-                    q, no_ack = self.queues_by_consumer_tags.pop(order.queue.consumer_tag)
-                except KeyError:
-                    pass  # wat?
-                else:
-                    self.backend.basic_cancel(order.queue.consumer_tag)
-                    self.event_queue.put(ConsumerCancelled(order.queue, ConsumerCancelled.USER_CANCEL))
-            elif isinstance(order, AcknowledgeMessage):
-                if order.connect_id == self.connect_id:
-                    self.backend.basic_ack(order.delivery_tag)
-            elif isinstance(order, NAcknowledgeMessage):
-                if order.connect_id == self.connect_id:
-                    self.backend.basic_reject(order.delivery_tag)
-        except RemoteAMQPError as e:
-            logger.error('Remote AMQP error: %s', e)
-            order._failed(e)  # we are allowed to go on
-        except ConnectionFailedError as e:
-            logger.error('Connection failed while %s: %s', order, e)
-            self.order_queue.appendleft(order)
-            raise
-        else:
-            order._completed()
-
-    def __run_wrap(self):   # throws _ImOuttaHere
-        # Loop while there are things to do
-        while (not self.is_terminating) or (len(self.order_queue) > 0):
-            try:
-                while len(self.order_queue) > 0:
-                    self.perform_order()
-
-                # just drain shit
-                self.backend.process(max_time=0.05)
-            except ConnectionFailedError as e:
-                logger.warning('Connection to broker lost: %s', e)
-                self.cluster.connected = False
-                self.event_queue.put(ConnectionDown())
-
-                # =========================== remove SendMessagees with discard_on_fail
-                my_orders = []      # because order_queue is used by many threads
-                while len(self.order_queue) > 0:
-                    order = self.order_queue.popleft()
-                    if isinstance(order, SendMessage):
-                        if order.message.discard_on_fail:
-                            order._discard()
-                            continue
-
-                    my_orders.append(order)
-
-                # Ok, we have them in order of execution. Append-left in reverse order
-                # to preserve previous order
-                for order in reversed(my_orders):
-                    my_orders.appendleft(order)
-
-            self._reconnect()
-
-    def run(self):
-        try:
-            self._reconnect()
-            self.__run_wrap()
-        except _ImOuttaHere:
-            pass
-
-        assert self.is_terminating
-        if self.cluster.connected or (self.backend is not None):
-            if self.backend is not None:
-                self.backend.shutdown()
-                self.backend = None
-
-            self.cluster.connected = False
-
-    def terminate(self):
-        """
-        Called by Cluster. Tells to finish all jobs and quit.
-        Unacked messages will not be acked. If this is called, connection may die at any time.
-        """
-        self.is_terminating = True
-
-    ## events called
-    def _on_recvmessage(self, body, exchange_name, routing_key, delivery_tag, properties):
-        """
-        Upon receiving a message
-        """
-        from .messages import ReceivedMessage
-
-        self.event_queue.put(MessageReceived(ReceivedMessage(body, self,
-                                                             self.connect_id,
-                                                             exchange_name,
-                                                             routing_key,
-                                                             properties,
-                                                             delivery_tag=delivery_tag)))
-
-    def _on_consumercancelled(self, consumer_tag):
-        """
-        A consumer has been cancelled
-        """
-        try:
-            queue, no_ack = self.queues_by_consumer_tags.pop(consumer_tag)
-        except KeyError:
-            return  # what?
-
-        self.event_queue.put(ConsumerCancelled(queue, ConsumerCancelled.BROKER_CANCEL))
-
-    ## methods to enqueue something into CHT to execute
-
-    def _do_ackmessage(self, receivedMessage, on_completed=None):
-        """
-        Order acknowledging a message.
-        :param receivedMessage: a ReceivedMessage object to ack
-        :param on_completed: callable/0 to call when acknowledgemenet succeeded
-        :return: an AcknowledgeMess
-        """
-        a = AcknowledgeMessage(receivedMessage.connect_id,
-                                                   receivedMessage.delivery_tag,
-                                                   on_completed=on_completed)
-        self.order_queue.append(a)
-        return a
-
-
-    def _do_nackmessage(self, receivedMessage, on_completed=None):
-        """
-        Order acknowledging a message.
-        :param receivedMessage: a ReceivedMessage object to ack
-        :param on_completed: callable/0 to call when acknowledgemenet succeeded
-        """
-        a = NAcknowledgeMessage(receivedMessage.connect_id,
-                                receivedMessage.delivery_tag,
-                                on_completed=on_completed)
-        self.order_queue.append(a)
-        return a
diff --git a/coolamqp/messages.py b/coolamqp/messages.py
deleted file mode 100644
index 2a2dde2f04b6bcf4f16ddfbf3dacff8ae1fcca25..0000000000000000000000000000000000000000
--- a/coolamqp/messages.py
+++ /dev/null
@@ -1,134 +0,0 @@
-# coding=UTF-8
-import uuid
-import six
-
-
-class Message(object):
-    """AMQP message object"""
-
-    def __init__(self, body, properties=None):
-        """
-        Create a Message object.
-
-        Please take care with passing empty bodies, as py-amqp has some failure on it.
-
-        :param body: stream of octets
-        :type body: str (py2) or bytes (py3)
-        :param properties: AMQP properties to be sent along
-        """
-        if isinstance(body, six.text_type):
-            raise TypeError('body cannot be a text type!')
-        self.body = six.binary_type(body)
-        self.properties = properties or {}
-
-
-class ReceivedMessage(Message):
-    """Message as received from AMQP system"""
-
-    def __init__(self, body, cht, connect_id, exchange_name, routing_key, properties=None, delivery_tag=None):
-        """
-        :param body: message body. A stream of octets.
-        :type body: str (py2) or bytes (py3)
-        :param cht: parent ClusterHandlerThread that emitted this message
-        :param connect_id: connection ID. ClusterHandlerThread will check this in order
-            not to ack messages that were received from a dead connection
-        :param exchange_name: name of exchange this message was submitted to
-        :param routing_key: routing key with which this message was sent
-        :param properties: dictionary. Headers received from AMQP or None for empty dict
-
-        :param delivery_tag: delivery tag assigned by AMQP broker to confirm this message.
-            leave None if auto-ack
-        """
-        Message.__init__(self, body, properties=properties)
-
-        self.cht = cht
-        self.connect_id = connect_id
-        self.delivery_tag = delivery_tag
-        self.exchange_name = exchange_name
-        self.routing_key = routing_key
-
-    def nack(self, on_completed=None):
-        """
-        Negative-acknowledge this message to the broker.
-
-        This internally results in a basic.reject
-
-        :param on_completed: callable/0 to call on acknowledged. Callable will be executed in
-            ClusterHandlerThread's context.
-        :return: an Order, that can ve waited upon for a result
-        """
-        return self.cht._do_nackmessage(self, on_completed=on_completed)
-
-    def ack(self, on_completed=None):
-        """
-        Acknowledge this message to the broker.
-        :param on_completed: callable/0 to call on acknowledged. Callable will be executed in
-            ClusterHandlerThread's context.
-        :return: an Order, that can ve waited upon for a result
-        """
-        return self.cht._do_ackmessage(self, on_completed=on_completed)
-
-
-class Exchange(object):
-    """
-    This represents an Exchange used in AMQP.
-    This is hashable.
-    """
-
-    direct = None   # the direct exchange
-
-    def __init__(self, name='', type='direct', durable=True, auto_delete=False):
-        self.name = name
-        self.type = type
-        self.durable = durable
-        self.auto_delete = auto_delete
-
-    def __hash__(self):
-        return self.name.__hash__()
-
-    def __eq__(self, other):
-        return self.name == other.name
-
-Exchange.direct = Exchange()
-
-
-class Queue(object):
-    """
-    This object represents a Queue that applications consume from or publish to.
-
-    Caveat: Please note the locked_after_reconnect option in constructor
-    """
-
-    def __init__(self, name='', durable=False, exchange=None, exclusive=False, auto_delete=False,
-                 locked_after_reconnect='retry'):
-        """
-        Create a queue definition.
-
-        :param name: name of the queue.
-            Take special care if this is empty. If empty, this will be filled-in by the broker
-            upon declaration. If a disconnect happens, and connection to other node is
-            reestablished, this name will CHANGE AGAIN, and be reflected in this object.
-            This change will be done before CoolAMQP signals reconnection.
-        :param durable: Is the queue durable?
-        :param exchange: Exchange for this queue to bind to. None for no binding.
-        :param exclusive: Is this queue exclusive?
-        :param auto_delete: Is this queue auto_delete ?
-        :param locked_after_reconnect: Behaviour when queue is exclusive and ACCESS_REFUSED/RESOURCE_LOCKED
-            is seen on reconnect. Because broker might not know that we have failed, 'retry' will
-            try again until succeeds (default option). This might block for a long time, until the broker
-            realizes previous connection is dead and deletes the queue.
-            'cancel' will return a ConsumerCancelled to client
-            'defer' will attempt to configure the queue later, but will not block other tasks from progressing.
-        """
-        self.name = name
-        # if name is '', this will be filled in with broker-generated name upon declaration
-        self.durable = durable
-        self.exchange = exchange
-        self.auto_delete = auto_delete
-        self.exclusive = exclusive
-
-        self.anonymous = name == ''  # if this queue is anonymous, it must be regenerated upon reconnect
-
-        self.consumer_tag = name if name != '' else uuid.uuid4().hex    # consumer tag to use in AMQP comms
-        self.locked_after_reconnect = locked_after_reconnect
-        assert locked_after_reconnect in ('retry', 'cancel', 'defer')
\ No newline at end of file
diff --git a/coolamqp/objects.py b/coolamqp/objects.py
new file mode 100644
index 0000000000000000000000000000000000000000..dda8ea5f1033a15b731971ff1120c228b239773f
--- /dev/null
+++ b/coolamqp/objects.py
@@ -0,0 +1,274 @@
+# coding=UTF-8
+"""
+Core objects used in CoolAMQP
+"""
+import threading
+import uuid
+import six
+import logging
+import concurrent.futures
+
+from coolamqp.framing.definitions import BasicContentPropertyList as MessageProperties
+
+__all__ = ('Message', 'ReceivedMessage', 'MessageProperties', 'Queue', 'Exchange', 'Future')
+
+logger = logging.getLogger(__name__)
+
+EMPTY_PROPERTIES = MessageProperties()
+
+
+class Message(object):
+    """
+    An AMQP message. Has a binary body, and some properties.
+
+    Properties is a highly regularized class - see coolamqp.framing.definitions.BasicContentPropertyList
+    for a list of possible properties.
+    """
+
+    Properties = MessageProperties  # an alias for easier use
+
+    def __init__(self, body, properties=None):
+        """
+        Create a Message object.
+
+        Please take care with passing empty bodies, as py-amqp has some failure on it.
+
+        :param body: stream of octets
+        :type body: str (py2) or bytes (py3)
+        :param properties: AMQP properties to be sent along.
+                           default is 'no properties at all'
+                           You can pass a dict - it will be passed to MessageProperties,
+                           but it's slow - don't do that.
+        :type properties: MessageProperties instance, None or a dict
+        """
+        if isinstance(body, six.text_type):
+            raise TypeError('body cannot be a text type!')
+        self.body = six.binary_type(body)
+
+        if isinstance(properties, dict):
+            self.properties = MessageProperties(**properties)
+        elif properties is None:
+            self.properties = EMPTY_PROPERTIES
+        else:
+            self.properties = properties
+
+
+LAMBDA_NONE = lambda: None
+
+class ReceivedMessage(Message):
+    """
+    A message that was received from the AMQP broker.
+
+    It additionally has an exchange name, routing key used, it's delivery tag,
+    and methods for ack() or nack().
+
+    Note that if the consumer that generated this message was no_ack, .ack() and .nack() are no-ops.
+    """
+
+    def __init__(self, body, exchange_name, routing_key,
+                 properties=None,
+                 delivery_tag=None,
+                 ack=None,
+                 nack=None):
+        """
+        :param body: message body. A stream of octets.
+        :type body: str (py2) or bytes (py3)
+        :param cht: parent ClusterHandlerThread that emitted this message
+        :param connect_id: connection ID. ClusterHandlerThread will check this in order
+            not to ack messages that were received from a dead connection
+        :param exchange_name: name of exchange this message was submitted to
+        :param routing_key: routing key with which this message was sent
+        :param properties: a suitable BasicContentPropertyList subinstance
+
+        :param delivery_tag: delivery tag assigned by AMQP broker to confirm this message
+        :param ack: a callable to call when you want to ack (via basic.ack) this message. None if received
+             by the no-ack mechanism
+        :param nack: a callable to call when you want to nack (via basic.reject) this message. None if received
+             by the no-ack mechanism
+        """
+        Message.__init__(self, body, properties=properties)
+
+        self.delivery_tag = delivery_tag
+        self.exchange_name = exchange_name
+        self.routing_key = routing_key
+
+        self.ack = ack or LAMBDA_NONE
+        self.nack = nack or LAMBDA_NONE
+
+
+class Exchange(object):
+    """
+    This represents an Exchange used in AMQP.
+    This is hashable.
+    """
+
+    direct = None   # the direct exchange
+
+    def __init__(self, name='', type='direct', durable=True, auto_delete=False):
+        self.name = name
+        self.type = type
+        self.durable = durable
+        self.auto_delete = auto_delete
+
+    def __hash__(self):
+        return self.name.__hash__()
+
+    def __eq__(self, other):
+        return self.name == other.name
+
+Exchange.direct = Exchange()
+
+
+class Queue(object):
+    """
+    This object represents a Queue that applications consume from or publish to.
+    """
+
+    def __init__(self, name=u'', durable=False, exchange=None, exclusive=False, auto_delete=False):
+        """
+        Create a queue definition.
+
+        :param name: name of the queue.
+            Take special care if this is empty. If empty, this will be filled-in by the broker
+            upon declaration. If a disconnect happens, and connection to other node is
+            reestablished, this name will CHANGE AGAIN, and be reflected in this object.
+            This change will be done before CoolAMQP signals reconnection.
+        :param durable: Is the queue durable?
+        :param exchange: Exchange for this queue to bind to. None for no binding.
+        :param exclusive: Is this queue exclusive?
+        :param auto_delete: Is this queue auto_delete ?
+        """
+        self.name = name.encode('utf8')
+        # if name is '', this will be filled in with broker-generated name upon declaration
+        self.durable = durable
+        self.exchange = exchange
+        self.auto_delete = auto_delete
+        self.exclusive = exclusive
+
+        self.anonymous = name == ''  # if this queue is anonymous, it must be regenerated upon reconnect
+
+        self.consumer_tag = name if name != '' else uuid.uuid4().hex    # consumer tag to use in AMQP comms
+
+    def __eq__(self, other):
+        return self.name == other.name
+
+    def __hash__(self):
+        return hash(self.name)
+
+
+class Future(concurrent.futures.Future):
+    """
+    Future returned by asynchronous CoolAMQP methods.
+
+    A strange future (only one thread may wait for it)
+    """
+    __slots__ = ('lock', 'completed', 'successfully', '_result', 'running', 'callables', 'cancelled')
+
+
+    def __init__(self):
+        self.lock = threading.Lock()
+        self.lock.acquire()
+
+        self.completed = False
+        self.successfully = None
+        self._result = None
+        self.cancelled = False
+        self.running = True
+
+        self.callables = []
+
+    def add_done_callback(self, fn):
+        self.callables.append(fn)
+
+    def result(self, timeout=None):
+        assert timeout is None, u'Non-none timeouts not supported'
+        self.lock.acquire()
+
+        if self.completed:
+            if self.successfully:
+                return self._result
+            else:
+                raise self._result
+        else:
+            if self.cancelled:
+                raise concurrent.futures.CancelledError()
+            else:
+                # it's invalid to release the lock, not do the future if it's not cancelled
+                raise RuntimeError(u'Invalid state!')
+
+    def cancel(self):
+        """
+        When cancelled, future will attempt not to complete (completed=False).
+        :return:
+        """
+        self.cancelled = True
+
+    def __finish(self, result, successful):
+        self.completed = True
+        self.successfully = successful
+        self._result = result
+        self.lock.release()
+
+        for callable in self.callables:
+            try:
+                callable(self)
+            except Exception as e:
+                logger.error('Exception in base order future: %s', repr(e))
+            except BaseException as e:
+                logger.critical('WILD NASAL DEMON APPEARED: %s', repr(e))
+
+    def set_result(self, result=None):
+        self.__finish(result, True)
+
+    def set_exception(self, exception):
+        self.__finish(exception, False)
+
+    def set_cancel(self):
+        """Executor has seen that this is cancelled, and discards it from list of things to do"""
+        assert self.cancelled
+        self.completed = False
+        self.lock.release()
+
+
+class NodeDefinition(object):
+    """
+    Definition of a reachable AMQP node.
+
+    This object is hashable.
+    """
+
+    def __init__(self, *args, **kwargs):
+        """
+        Create a cluster node definition.
+
+            a = ClusterNode(host='192.168.0.1', user='admin', password='password',
+                            virtual_host='vhost')
+
+        or
+
+            a = ClusterNode('192.168.0.1', 'admin', 'password')
+
+        Additional keyword parameters that can be specified:
+            heartbeat - heartbeat interval in seconds
+            port - TCP port to use. Default is 5672
+        """
+
+        self.heartbeat = kwargs.pop('heartbeat', None)
+        self.port = kwargs.pop('port', 5672)
+
+        if len(kwargs) > 0:
+            # Prepare arguments for amqp.connection.Connection
+            self.host = kwargs['host']
+            self.user = kwargs['user']
+            self.password = kwargs['password']
+            self.virtual_host = kwargs.get('virtual_host', '/')
+        elif len(args) == 3:
+            self.host, self.user, self.password = args
+            self.virtual_host = '/'
+        elif len(args) == 4:
+            self.host, self.user, self.password, self.virtual_host = args
+        else:
+            raise NotImplementedError #todo implement this
+
+    def __str__(self):
+        return six.text_type(b'amqp://%s:%s@%s/%s'.encode('utf8') % (self.host, self.port, self.user, self.virtual_host))
\ No newline at end of file
diff --git a/coolamqp/orders.py b/coolamqp/orders.py
deleted file mode 100644
index 2c97858213765d5d217c37277d50ec8c467b7e61..0000000000000000000000000000000000000000
--- a/coolamqp/orders.py
+++ /dev/null
@@ -1,175 +0,0 @@
-# coding=UTF-8
-"""
-Orders that can be dispatched to ClusterHandlerThread
-"""
-from threading import Lock
-import warnings
-
-
-_NOOP_COMP = lambda: None
-_NOOP_FAIL = lambda e: None
-
-
-class Order(object):
-    """Base class for orders dispatched to ClusterHandlerThread"""
-    def __init__(self, on_completed=None, on_failed=None):
-        """
-        Please note that callbacks will be executed BEFORE the lock is released,
-        but after .result is updated, ie. if
-        you have something like
-
-            amqp.send(.., on_completed=hello).result()
-            bye()
-
-        then hello() will be called BEFORE bye().
-        Callbacks are called from CoolAMQP's internal thread.
-
-        If this fails, then property .error_code can be read to get the error code.
-        and .reply_text has the reply of the server or some other reason. These are set before
-        callbacks are called.
-
-        Error code is None, if not available, or AMQP constants describing errors,
-        eg. 502 for syntax error.
-
-        A discarded or cancelled order is considered FAILED
-        """
-        self.on_completed = on_completed or _NOOP_COMP
-        self.on_failed = on_failed or _NOOP_FAIL
-        self._result = None  # None on non-completed
-                            # True on completed OK
-                            # exception instance on failed
-                            # private
-        self.lock = Lock()
-        self.lock.acquire()
-        self.cancelled = False          #: public
-        self.discarded = False          #: public
-        self.error_code = None
-        self.reply_text = None
-
-    def has_finished(self):
-        """Return if this task has either completed or failed"""
-        return self._result is not None
-
-    def cancel(self):
-        """Cancel this order"""
-        self.cancelled = True
-
-    def _completed(self):       # called by handler
-        self._result = True
-        self.on_completed()
-        self.lock.release()
-
-    def _discard(self):     # called by handler
-        from coolamqp.backends.base import Discarded
-        self.discarded = True
-        self.on_failed(Discarded())
-        self.lock.release()
-
-    def _failed(self, e):       # called by handler
-        """
-        :param e: AMQPError instance or Cancelled instance
-        """
-        from coolamqp.backends import Cancelled
-        self._result = e
-        if not isinstance(e, Cancelled):    # a true error
-            self.error_code = e.code
-            self.reply_text = e.reply_text
-
-        self.on_failed(e)
-        self.lock.release()
-
-    def wait(self):
-        """Wait until this is completed and return whether the order succeeded"""
-        self.lock.acquire()
-        return self._result is True
-
-    def has_failed(self):
-        """Return whether the operation failed, ie. completed but with an error code.
-        Cancelled and discarded ops are considered failed.
-        This assumes that this order has been .wait()ed upon"""
-        return self._result is True
-
-    def result(self):
-        """Wait until this is completed and return a response"""
-        warnings.warn('Use .wait() instead', PendingDeprecationWarning)
-        self.lock.acquire()
-        return self._result
-
-    @staticmethod
-    def _discarded(on_completed=None, on_failed=None):   # return order for a discarded message
-        o = Order(on_completed=on_completed, on_failed=on_failed)
-        self.on_completed()
-
-
-class SendMessage(Order):
-    """Send a message"""
-    def __init__(self, message, exchange, routing_key, discard_on_fail=False, on_completed=None, on_failed=None):
-        Order.__init__(self, on_completed=on_completed, on_failed=on_failed)
-        self.message = message
-        self.exchange = exchange
-        self.discard_on_fail = discard_on_fail
-        self.routing_key = routing_key
-
-
-class _Exchange(Order):
-    """Things with exchanges"""
-    def __init__(self, exchange, on_completed=None, on_failed=None):
-        Order.__init__(self, on_completed=on_completed, on_failed=on_failed)
-        self.exchange = exchange
-
-
-class DeclareExchange(_Exchange):
-    """Declare an exchange"""
-
-
-class DeleteExchange(_Exchange):
-    """Delete an exchange"""
-
-
-class _Queue(Order):
-    """Things with queues"""
-    def __init__(self, queue, on_completed=None, on_failed=None):
-        Order.__init__(self, on_completed=on_completed, on_failed=on_failed)
-        self.queue = queue
-
-
-class DeclareQueue(_Queue):
-    """Declare a a queue"""
-
-
-class ConsumeQueue(_Queue):
-    """Declare and consume from a queue"""
-    def __init__(self, queue, no_ack=False, on_completed=None, on_failed=None):
-        _Queue.__init__(self, queue, on_completed=on_completed, on_failed=on_failed)
-        self.no_ack = no_ack
-
-
-class DeleteQueue(_Queue):
-    """Delete a queue"""
-
-
-class CancelQueue(_Queue):
-    """Cancel consuming from a queue"""
-
-
-class SetQoS(Order):
-    """Set QoS"""
-    def __init__(self, prefetch_window, prefetch_count, global_, on_completed=None, on_failed=None):
-        Order.__init__(self, on_completed=on_completed, on_failed=on_failed)
-        self.qos = prefetch_window, prefetch_count, global_
-1
-
-class _AcksAndNacks(Order):
-    """related to acking and nacking"""
-    def __init__(self, connect_id, delivery_tag, on_completed):
-        Order.__init__(self, on_completed=on_completed)
-        self.connect_id = connect_id
-        self.delivery_tag = delivery_tag
-
-
-class AcknowledgeMessage(_AcksAndNacks):
-    """ACK a message"""
-
-
-class NAcknowledgeMessage(_AcksAndNacks):
-    """NACK a message"""
diff --git a/coolamqp/uplink/__init__.py b/coolamqp/uplink/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..cef792f6a97f9813867f7ca9741d5f1783ee03bd
--- /dev/null
+++ b/coolamqp/uplink/__init__.py
@@ -0,0 +1,18 @@
+# coding=UTF-8
+"""
+
+Core object here is Connection. This package:
+    - establishes basic connectivity (up to the point where you can open channels yourself)
+    - takes care of heartbeats
+
+You can wait for a particular frame by setting watches on connections.
+Watches will fire upon an event triggering them.
+
+EVERYTHING HERE IS CALLED BY LISTENER THREAD UNLESS STATED OTHERWISE.
+
+"""
+from __future__ import absolute_import, division, print_function
+
+from coolamqp.uplink.connection import Connection, HeaderOrBodyWatch, MethodWatch, AnyWatch, FailWatch
+from coolamqp.uplink.listener import ListenerThread
+from coolamqp.uplink.handshake import PUBLISHER_CONFIRMS, CONSUMER_CANCEL_NOTIFY
diff --git a/coolamqp/uplink/connection/__init__.py b/coolamqp/uplink/connection/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ec0d01c5042462dcf11aa52740f21c667e1f0ae
--- /dev/null
+++ b/coolamqp/uplink/connection/__init__.py
@@ -0,0 +1,16 @@
+# coding=UTF-8
+"""
+Comprehensive management of a framing connection.
+
+Connection is something that can:
+ - call something when an AMQPFrame is received
+ - send AMQPFrame's
+
+ Pretty much CoolAMQP is about persistent "attaches" that attach to transient connection
+ (they die when down) to do stuff, ie. send messages, consume, etc.
+"""
+from __future__ import absolute_import, division, print_function
+
+from coolamqp.uplink.connection.connection import Connection
+from coolamqp.uplink.connection.watches import FailWatch, Watch, HeaderOrBodyWatch, MethodWatch, AnyWatch
+from coolamqp.uplink.connection.states import ST_OFFLINE, ST_CONNECTING, ST_ONLINE
diff --git a/coolamqp/uplink/connection/connection.py b/coolamqp/uplink/connection/connection.py
new file mode 100644
index 0000000000000000000000000000000000000000..426804257177c6cb953ab37af963d96a4af7848f
--- /dev/null
+++ b/coolamqp/uplink/connection/connection.py
@@ -0,0 +1,301 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+import logging
+import collections
+import time
+import socket
+import six
+
+from coolamqp.uplink.connection.recv_framer import ReceivingFramer
+from coolamqp.uplink.connection.send_framer import SendingFramer
+from coolamqp.framing.frames import AMQPMethodFrame
+from coolamqp.uplink.handshake import Handshaker
+from coolamqp.framing.definitions import ConnectionClose, ConnectionCloseOk
+from coolamqp.uplink.connection.watches import MethodWatch
+from coolamqp.uplink.connection.states import ST_ONLINE, ST_OFFLINE, ST_CONNECTING
+
+
+logger = logging.getLogger(__name__)
+
+
+class Connection(object):
+    """
+    An object that manages a connection in a comprehensive way.
+
+    It allows for sending and registering watches for particular things. Watch will
+    listen for eg. frame on particular channel, frame on any channel, or connection teardown.
+    Watches will also get a callback for connection being non-operational (eg. torn down).
+
+    WARNING: Thread-safety of watch operation hinges on atomicity
+    of .append and .pop.
+
+    Lifecycle of connection is such:
+
+        Connection created  ->  state is ST_CONNECTING
+        .start() called     ->  state is ST_CONNECTING
+        connection.open-ok  ->  state is ST_ONLINE
+    """
+
+    def __init__(self, node_definition, listener_thread):
+        """
+        Create an object that links to an AMQP broker.
+
+        No data will be physically sent until you hit .start()
+
+        :param node_definition: NodeDefinition instance to use
+        :param listener_thread: ListenerThread to use as async engine
+        """
+        self.listener_thread = listener_thread
+        self.node_definition = node_definition
+
+        self.recvf = ReceivingFramer(self.on_frame)
+
+        self.watches = {}    # channel => list of [Watch instance]
+        self.any_watches = []   # list of Watches that should check everything
+
+        self.finalizers = []
+
+
+        self.state = ST_CONNECTING
+
+        self.callables_on_connected = []    # list of callable/0
+
+        # Negotiated connection parameters - handshake will fill this in
+        self.free_channels = [] # attaches can use this for shit.
+                    # WARNING: thread safety of this hinges on atomicity of .pop or .append
+        self.frame_max = None
+        self.heartbeat = None
+        self.extensions = []
+
+    def call_on_connected(self, callable):
+        """
+        Register a callable to be called when this links to the server.
+
+        If you call it while the connection IS up, callable will be called even before this returns.
+
+        You should be optimally an attached attache to receive this.
+
+        :param callable: callable/0 to call
+        """
+        if self.state == ST_ONLINE:
+            callable()
+        else:
+            self.callables_on_connected.append(callable)
+
+    def on_connected(self):
+        """Called by handshaker upon reception of final connection.open-ok"""
+        print(self.free_channels)
+        self.state = ST_ONLINE
+
+        while len(self.callables_on_connected) > 0:
+            self.callables_on_connected.pop()()
+
+    def start(self):
+        """
+        Start processing events for this connect. Create the socket,
+        transmit 'AMQP\x00\x00\x09\x01' and roll.
+
+        Warning: This will block for as long as the TCP connection setup takes.
+        """
+
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+
+        while True:
+            try:
+                sock.connect((self.node_definition.host, self.node_definition.port))
+            except socket.error as e:
+                print(e)
+                time.sleep(0.5) # Connection refused? Very bad things?
+            else:
+                break
+
+        sock.settimeout(0)
+        sock.send('AMQP\x00\x00\x09\x01')
+
+        Handshaker(self, self.node_definition, self.on_connected)
+        self.listener_socket = self.listener_thread.register(sock,
+                                                            on_read=self.recvf.put,
+                                                            on_fail=self.on_fail)
+        self.sendf = SendingFramer(self.listener_socket.send)
+        self.watch_for_method(0, (ConnectionClose, ConnectionCloseOk), self.on_connection_close)
+
+    def add_finalizer(self, callable):
+        """
+        Add a callable to be executed when all watches were failed and we're really going down.
+
+        Finalizers are not used for logic stuff, but for situations like making TCP reconnects.
+        When we are making a reconnect, we need to be sure that all watches fired - so logic is intact.
+
+        DO NOT PUT CALLABLES THAT HAVE TO DO WITH STATE THINGS, ESPECIALLY ATTACHES.
+
+        :param callable: callable/0
+        """
+        self.finalizers.append(callable)
+
+    def on_fail(self):
+        """
+        Called by event loop when the underlying connection is closed.
+
+        This means the connection is dead, cannot be used anymore, and all operations
+        running on it now are aborted, null and void.
+
+        This calls fails all registered watches.
+        Called by ListenerThread.
+
+        WARNING: Note that .on_fail can get called twice - once from .on_connection_close,
+        and second time from ListenerThread when socket is disposed of
+        Therefore we need to make sure callbacks are called EXACTLY once
+        """
+        self.state = ST_OFFLINE # Update state
+
+        watchlists = [self.watches[channel] for channel in self.watches]
+
+        for watchlist in watchlists:   # Run all watches - failed
+            for watch in watchlist:
+                watch.failed()
+
+        for watch in self.any_watches:
+            watch.failed()
+
+        self.watches = {}                       # Clear the watch list
+        self.any_watches = []
+
+        # call finalizers
+        while len(self.finalizers) > 0:
+            self.finalizers.pop()()
+
+    def on_connection_close(self, payload):
+        """
+        Server attempted to close the connection.. or maybe we did?
+
+        Called by ListenerThread.
+        """
+        print('We are GOING DOOOWN')
+        self.on_fail()      # it does not make sense to prolong the agony
+
+        if isinstance(payload, ConnectionClose):
+            print(payload.reply_code, payload.reply_text)
+            self.send([AMQPMethodFrame(0, ConnectionCloseOk())])
+        elif isinstance(payload, ConnectionCloseOk):
+            self.send(None)
+
+    def send(self, frames, priority=False):
+        """
+        Schedule to send some frames.
+
+        Take care: This won't stop you from sending frames larger tham frame_max.
+        Broker will probably close the connection if he sees that.
+
+        :param frames: list of frames or None to close the link
+        :param reason: optional human-readable reason for this action
+        """
+        if frames is not None:
+            self.sendf.send(frames, priority=priority)
+        else:
+            # Listener socket will kill us when time is right
+            self.listener_socket.send(None)
+
+    def on_frame(self, frame):
+        """
+        Called by event loop upon receiving an AMQP frame.
+
+        This will verify all watches on given channel if they were hit,
+        and take appropriate action.
+
+        Unhandled frames will be logged - if they were sent, they probably were important.
+
+        :param frame: AMQPFrame that was received
+        """
+        if isinstance(frame, AMQPMethodFrame):      # temporary, for debugging
+            print('RECEIVED', frame.payload.NAME)
+        else:
+            print('RECEIVED ', frame)
+
+        watch_handled = False   # True if ANY watch handled this
+
+        # ==================== process per-channel watches
+        if frame.channel in self.watches:
+            watches = self.watches[frame.channel]       # a list
+
+            alive_watches = []
+            while len(watches) > 0:
+                watch = watches.pop()
+
+                if watch.cancelled:
+                    continue
+
+                watch_triggered = watch.is_triggered_by(frame)
+                watch_handled |= watch_triggered
+
+                if (not watch_triggered) or (not watch.oneshot):
+                    # Watch remains alive if it was NOT triggered, or it's NOT a oneshot
+                    alive_watches.append(watch)
+
+            for watch in alive_watches:
+                watches.append(watch)
+
+        # ==================== process "any" watches
+        alive_watches = []
+        while len(self.any_watches):
+            watch = self.any_watches.pop()
+            watch_triggered = watch.is_triggered_by(frame)
+            watch_handled |= watch_triggered
+
+            if (not watch_triggered) or (not watch.oneshot):
+                # Watch remains alive if it was NOT triggered, or it's NOT a oneshot
+                alive_watches.append(watch)
+
+        for watch in alive_watches:
+            self.any_watches.append(watch)
+
+        if not watch_handled:
+            logger.critical('Unhandled frame %s', frame)
+
+    def watchdog(self, delay, callback):
+        """
+        Call callback in delay seconds. One-shot.
+
+        Shall the connection die in the meantime, watchdog will not
+        be called, and everything will process according to
+        ListenerThread's on_fail callback.
+        """
+        self.listener_socket.oneshot(delay, callback)
+
+    def unwatch_all(self, channel_id):
+        """
+        Remove all watches from specified channel
+        """
+        self.watches.pop(channel_id, None)
+
+    def watch(self, watch):
+        """
+        Register a watch.
+        :param watch: Watch to register
+        """
+        assert self.state != ST_OFFLINE
+        if watch.channel is None:
+            self.any_watches.append(watch)
+        elif watch.channel not in self.watches:
+            self.watches[watch.channel] = collections.deque([watch])
+        else:
+            self.watches[watch.channel].append(watch)
+
+    def watch_for_method(self, channel, method, callback, on_fail=None):
+        """
+        :param channel: channel to monitor
+        :param method: AMQPMethodPayload class or tuple of AMQPMethodPayload classes
+        :param callback: callable(AMQPMethodPayload instance)
+        """
+        mw = MethodWatch(channel, method, callback, on_end=on_fail)
+        self.watch(mw)
+        return mw
+
+    def method_and_watch(self, channel_id, method_payload, method_or_methods, callback):
+        """
+        A syntactic sugar for
+
+                .watch_for_method(channel_id, method_or_methdods, callback)
+                .send([AMQPMethodFrame(channel_id, method_payload)])
+        """
+        self.watch_for_method(channel_id, method_or_methods, callback)
+        self.send([AMQPMethodFrame(channel_id, method_payload)])
diff --git a/coolamqp/uplink/connection/recv_framer.py b/coolamqp/uplink/connection/recv_framer.py
new file mode 100644
index 0000000000000000000000000000000000000000..1075f52066130fc8c87982e2ef54a0d048546047
--- /dev/null
+++ b/coolamqp/uplink/connection/recv_framer.py
@@ -0,0 +1,138 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+
+import collections
+import io
+import six
+import struct
+
+from coolamqp.framing.frames import AMQPBodyFrame, AMQPHeaderFrame, AMQPHeartbeatFrame, AMQPMethodFrame
+from coolamqp.framing.definitions import FRAME_HEADER, FRAME_HEARTBEAT, FRAME_END, FRAME_METHOD, FRAME_BODY
+
+FRAME_TYPES = {
+    FRAME_HEADER: AMQPHeaderFrame,
+    FRAME_BODY: AMQPBodyFrame,
+    FRAME_METHOD: AMQPMethodFrame
+}
+
+
+class ReceivingFramer(object):
+    """
+    Assembles AMQP framing from received data.
+
+    Just call with .put(data) upon receiving,
+    and on_frame will be called with fresh frames.
+
+    Not thread safe.
+
+    State machine
+        (frame_type is None)  and has_bytes(1)        ->          (frame_type <- bytes(1))
+
+        (frame_type is HEARTBEAT) and has_bytes(AMQPHeartbeatFrame.LENGTH-1)  ->          (output_frame, frame_type <- None)
+        (frame_type is not HEARTBEAT and not None) and has_bytes(6)  ->      (frame_channel <- bytes(2),
+                                                                 frame_size <- bytes(4))
+
+        (frame_size is not None) and has_bytes(frame_size+1)    ->  (output_frame,
+                                                                            frame_type <- None
+                                                                            frame_size < None)
+    """
+    def __init__(self, on_frame=lambda frame: None):
+        self.chunks = collections.deque()   # all received data
+        self.total_data_len = 0
+
+        self.frame_type = None
+        self.frame_channel = None
+        self.frame_size = None
+
+        self.bytes_needed = None    # bytes needed for a new frame
+        self.on_frame = on_frame
+
+    def put(self, data):
+        """
+        Called upon receiving data.
+
+        May result in any number of .on_frame() calls
+        :param data: received data
+        """
+        self.total_data_len += len(data)
+        self.chunks.append(buffer(data))
+
+        while self._statemachine():
+            pass
+
+    def _extract(self, up_to): # return up to up_to bytes from current chunk, switch if necessary
+        assert self.total_data_len >= up_to, 'Tried to extract %s but %s remaining' % (up_to, self.total_data_len)
+        if up_to >= len(self.chunks[0]):
+            q =  self.chunks.popleft()
+        else:
+            q = buffer(self.chunks[0], 0, up_to)
+            self.chunks[0] = buffer(self.chunks[0], up_to)
+
+        self.total_data_len -= len(q)
+        return q
+
+    def _statemachine(self):
+        # state rule 1
+        if self.frame_type is None and self.total_data_len > 0:
+            self.frame_type = ord(self._extract(1)[0])
+
+            if self.frame_type not in (FRAME_HEARTBEAT, FRAME_HEADER, FRAME_METHOD, FRAME_BODY):
+                raise ValueError('Invalid frame')
+
+            return True
+
+        # state rule 2
+        elif (self.frame_type == FRAME_HEARTBEAT) and (self.total_data_len >= AMQPHeartbeatFrame.LENGTH-1):
+            data = b''
+            while len(data) < AMQPHeartbeatFrame.LENGTH-1:
+                data = data + six.binary_type(self._extract(AMQPHeartbeatFrame.LENGTH-1 - len(data)))
+
+            if data != AMQPHeartbeatFrame.DATA[1:]:
+                # Invalid heartbeat frame!
+                raise ValueError('Invalid AMQP heartbeat')
+
+            self.on_frame(AMQPHeartbeatFrame())
+            self.frame_type = None
+
+            return True
+
+        # state rule 3
+        elif (self.frame_type != FRAME_HEARTBEAT) and (self.frame_type is not None) and (self.frame_size is None) and (self.total_data_len > 6):
+            hdr = b''
+            while len(hdr) < 6:
+                hdr = hdr + six.binary_type(self._extract(6 - len(hdr)))
+
+            self.frame_channel, self.frame_size = struct.unpack('!HI', hdr)
+
+            return True
+
+        # state rule 4
+        elif (self.frame_size is not None) and (self.total_data_len >= (self.frame_size+1)):
+
+            if len(self.chunks[0]) >= self.frame_size:
+                # We can subslice it - it's very fast
+                payload = self._extract(self.frame_size)
+            else:
+                # Construct a separate buffer :(
+                payload = io.BytesIO()
+                while payload.tell() < self.frame_size:
+                    payload.write(self._extract(self.frame_size - payload.tell()))
+
+                assert payload.tell() <= self.total_data_len
+
+                payload = buffer(payload.getvalue())
+
+            if ord(self._extract(1)[0]) != FRAME_END:
+                raise ValueError('Invalid frame end')
+
+            try:
+                frame = FRAME_TYPES[self.frame_type].unserialize(self.frame_channel, payload)
+            except ValueError:
+                raise
+
+            self.on_frame(frame)
+            self.frame_type = None
+            self.frame_size = None
+
+            return True
+        return False
\ No newline at end of file
diff --git a/coolamqp/uplink/connection/send_framer.py b/coolamqp/uplink/connection/send_framer.py
new file mode 100644
index 0000000000000000000000000000000000000000..bb07e45552b6fe213454b344fdd68805c250b41a
--- /dev/null
+++ b/coolamqp/uplink/connection/send_framer.py
@@ -0,0 +1,50 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+
+import collections
+import threading
+import io
+import socket
+
+
+class SendingFramer(object):
+    """
+    Assembles AMQP framing from received data and orchestrates their upload via a socket.
+
+    Just call with .put(data) and get framing by iterator .framing().
+
+    Not thread safe.
+
+    State machine
+        (frame_type is None)  and has_bytes(1)        ->          (frame_type <- bytes(1))
+
+        (frame_type is HEARTBEAT) and has_bytes(3)  ->          (output_frame, frame_type <- None)
+        (frame_type is not HEARTBEAT and not None) and has_bytes(6)  ->      (frame_channel <- bytes(2),
+                                                                 frame_size <- bytes(4))
+
+        (frame_size is not None) and has_bytes(frame_size+1)    ->  (output_frame,
+                                                                            frame_type <- None
+                                                                            frame_size < None)
+    """
+    def __init__(self, on_send=lambda data: None):
+        """
+        :param on_send: a callable(data, priority=False) that can be called with some data to send
+            data will always be entire AMQP frames!
+        """
+        self.on_send = on_send
+
+
+    def send(self, frames, priority=False):
+        """
+        Schedule to send some frames.
+        :param frames: list of AMQPFrame instances
+        :param priority: preempty existing frames
+        """
+        length = sum(frame.get_size() for frame in frames)
+        buf = io.BytesIO(bytearray(length))
+
+        for frame in frames:
+            frame.write_to(buf)
+
+        q = buf.getvalue()
+        self.on_send(q, priority)
diff --git a/coolamqp/uplink/connection/states.py b/coolamqp/uplink/connection/states.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f340b26b72a615934208b19042aa7c859899d4f
--- /dev/null
+++ b/coolamqp/uplink/connection/states.py
@@ -0,0 +1,6 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+
+ST_OFFLINE = 0
+ST_CONNECTING = 1
+ST_ONLINE = 2
\ No newline at end of file
diff --git a/coolamqp/uplink/connection/watches.py b/coolamqp/uplink/connection/watches.py
new file mode 100644
index 0000000000000000000000000000000000000000..10026c88df42b88f4a201db3fba7ecb15b924fbd
--- /dev/null
+++ b/coolamqp/uplink/connection/watches.py
@@ -0,0 +1,128 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+
+from coolamqp.framing.frames import AMQPMethodFrame, AMQPHeartbeatFrame, AMQPHeaderFrame, AMQPBodyFrame
+
+
+class Watch(object):
+    """
+    A watch is placed per-channel, to listen for a particular frame.
+    """
+
+    def __init__(self, channel, oneshot):
+        """
+        :param channel: Channel to listen to.
+            all channels if None is passed
+        :param oneshot: Is destroyed after triggering?
+        """
+        self.channel = channel
+        self.oneshot = oneshot
+        self.cancelled = False
+
+    def is_triggered_by(self, frame):
+        """
+        Does frame trigger this watch?
+        Run callable if it does.
+        :param frame: AMQPFrame instance
+        :return: bool
+        """
+        raise Exception('Abstract method')
+
+    def failed(self):
+        """
+        This watch will process things no more, because underlying
+        link has failed
+        """
+
+    def cancel(self):
+        """
+        Called by watch's user. This watch will not receive events anymore
+        (whether about frame or fail), and it will be discarded upon next iteration.
+        """
+        self.cancelled = True
+
+
+class AnyWatch(Watch):
+    """
+    Watch that listens for any frame.
+
+    It does not listen for failures.
+
+    Used because heartbeating is implemented improperly EVERYWHERE
+    (ie. you might not get a heartbeat when connection is so loaded it just can't get it in time,
+    due to loads and loads of message exchanging).
+
+    Eg. RabbitMQ will happily disconnect you if you don't, but it can get lax with heartbeats
+    as it wants.
+    """
+    def __init__(self, callable):
+        super(AnyWatch, self).__init__(None, False)
+        self.callable = callable
+
+    def is_triggered_by(self, frame):
+        self.callable(frame)
+        return True
+
+
+class FailWatch(Watch):
+    """
+    A special kind of watch that fires when connection has died
+    """
+    def __init__(self, callable):
+        super(FailWatch, self).__init__(None, True)
+        self.callable = callable
+
+    def is_triggered_by(self, frame):
+        return False
+
+    def failed(self):
+        """Connection failed!"""
+        self.callable()
+
+
+class HeaderOrBodyWatch(Watch):
+    """
+    A multi-shot watch listening for AMQP header or body frames
+    """
+    def __init__(self, channel, callable):
+        Watch.__init__(self, channel, False)
+        self.callable = callable
+
+    def is_triggered_by(self, frame):
+        if not (isinstance(frame, (AMQPHeaderFrame, AMQPBodyFrame))):
+            return False
+        self.callable(frame)
+        return True
+
+
+class MethodWatch(Watch):
+    """
+    One-shot watch listening for methods.
+    """
+    def __init__(self, channel, method_or_methods, callable, on_end=None):
+        """
+        :param method_or_methods: class, or list of AMQPMethodPayload classes
+        :param callable: callable(AMQPMethodPayload instance)
+        :param on_end: callable/0 on link dying
+        """
+        Watch.__init__(self, channel, True)
+        self.callable = callable
+        if isinstance(method_or_methods, (list, tuple)):
+            self.methods = tuple(method_or_methods)
+        else:
+            self.methods = method_or_methods
+        self.on_end = on_end
+
+    def failed(self):
+        if self.on_end is not None:
+            self.on_end()
+
+    def is_triggered_by(self, frame):
+
+        if not isinstance(frame, AMQPMethodFrame):
+            return False
+
+        if isinstance(frame.payload, self.methods):
+            self.callable(frame.payload)
+            return True
+        return False
diff --git a/coolamqp/uplink/handshake.py b/coolamqp/uplink/handshake.py
new file mode 100644
index 0000000000000000000000000000000000000000..4c8bf1e6fdfdad717af656fe0b6087387dbc4004
--- /dev/null
+++ b/coolamqp/uplink/handshake.py
@@ -0,0 +1,113 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+"""
+Provides reactors that can authenticate an AQMP session
+"""
+import six
+from coolamqp.framing.definitions import ConnectionStart, ConnectionStartOk, \
+    ConnectionTune, ConnectionTuneOk, ConnectionOpen, ConnectionOpenOk, ConnectionClose
+from coolamqp.framing.frames import AMQPMethodFrame
+from coolamqp.uplink.connection.states import ST_ONLINE
+
+
+PUBLISHER_CONFIRMS = b'publisher_confirms'
+CONSUMER_CANCEL_NOTIFY = b'consumer_cancel_notify'
+
+SUPPORTED_EXTENSIONS = [
+    PUBLISHER_CONFIRMS,
+    CONSUMER_CANCEL_NOTIFY
+]
+
+CLIENT_DATA = [
+        # because RabbitMQ is some kind of a fascist and does not allow
+        # these fields to be of type short-string
+        (b'product', (b'CoolAMQP', b'S')),
+        (b'version', (b'develop', b'S')),
+        (b'copyright', (b'Copyright (C) 2016-2017 DMS Serwis', b'S')),
+        (b'information', (b'Licensed under the MIT License.\nSee https://github.com/smok-serwis/coolamqp for details', b'S')),
+        (b'capabilities', ([(capa, (True, b't')) for capa in SUPPORTED_EXTENSIONS], b'F')),
+      ]
+
+WATCHDOG_TIMEOUT = 10
+
+
+class Handshaker(object):
+    """
+    Object that given a connection rolls the handshake.
+    """
+
+    def __init__(self, connection, node_definition, on_success):
+        """
+        :param connection: Connection instance to use
+        :type node_definition: NodeDefinition
+        :param on_success: callable/0, on success
+        """
+        self.connection = connection
+        self.login = node_definition.user.encode('utf8')
+        self.password = node_definition.password.encode('utf8')
+        self.virtual_host = node_definition.virtual_host.encode('utf8')
+        self.heartbeat = node_definition.heartbeat or 0
+        self.connection.watch_for_method(0, ConnectionStart, self.on_connection_start)
+
+        # Callbacks
+        self.on_success = on_success
+
+    # Called by internal setup
+    def on_watchdog(self):
+        """
+        Called WATCHDOG_TIMEOUT seconds after setup begins
+
+        If we are not ST_ONLINE after that much, something is wrong and pwn this connection.
+        """
+        # Not connected in 20 seconds - abort
+        if self.connection.state != ST_ONLINE:
+            # closing the connection this way will get to Connection by channels of ListenerThread
+            self.connection.send(None)
+
+    def on_connection_start(self, payload):
+
+        sasl_mechanisms = payload.mechanisms.split(b' ')
+        locale_supported = payload.locales.split(b' ')
+
+        # Select a mechanism
+        if b'PLAIN' not in sasl_mechanisms:
+            raise ValueError('Server does not support PLAIN')
+
+        # Select capabilities
+        server_props = dict(payload.server_properties)
+        if b'capabilities' in server_props:
+            for label, fv in server_props[b'capabilities'][0]:
+                if label in SUPPORTED_EXTENSIONS:
+                    if fv[0]:
+                        self.connection.extensions.append(label)
+
+        self.connection.watchdog(WATCHDOG_TIMEOUT, self.on_watchdog)
+        self.connection.watch_for_method(0, ConnectionTune, self.on_connection_tune)
+        self.connection.send([
+            AMQPMethodFrame(0,
+                            ConnectionStartOk(CLIENT_DATA, b'PLAIN',
+                                              b'\x00' + self.login + b'\x00' + self.password,
+                                              locale_supported[0]
+                                              ))
+        ])
+
+    def on_connection_tune(self, payload):
+        self.connection.frame_max = payload.frame_max
+        self.connection.heartbeat = min(payload.heartbeat, self.heartbeat)
+        print('Selected', payload.channel_max, 'channels')
+        for channel in six.moves.xrange(1, (65535 if payload.channel_max == 0 else payload.channel_max)+1):
+            self.connection.free_channels.append(channel)
+
+        self.connection.watch_for_method(0, ConnectionOpenOk, self.on_connection_open_ok)
+        self.connection.send([
+            AMQPMethodFrame(0, ConnectionTuneOk(payload.channel_max, payload.frame_max, self.connection.heartbeat)),
+            AMQPMethodFrame(0, ConnectionOpen(self.virtual_host))
+        ])
+
+        # Install heartbeat handlers NOW, if necessary
+        if self.connection.heartbeat > 0:
+            from coolamqp.uplink.heartbeat import Heartbeater
+            Heartbeater(self.connection, self.connection.heartbeat)
+
+    def on_connection_open_ok(self, payload):
+        self.on_success()
diff --git a/coolamqp/uplink/heartbeat.py b/coolamqp/uplink/heartbeat.py
new file mode 100644
index 0000000000000000000000000000000000000000..3f37ba89a3b1fedc33de7f977b8bed40bf8fde58
--- /dev/null
+++ b/coolamqp/uplink/heartbeat.py
@@ -0,0 +1,49 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+import monotonic
+
+from coolamqp.framing.frames import AMQPHeartbeatFrame
+from coolamqp.uplink.connection.watches import AnyWatch
+
+
+class Heartbeater(object):
+    """
+    An object that handles heartbeats
+    """
+
+    def __init__(self, connection, heartbeat_interval=0):
+        self.connection = connection
+        self.heartbeat_interval = heartbeat_interval
+
+        self.last_heartbeat_on = monotonic.monotonic()  # last heartbeat from server
+
+        self.connection.watchdog(self.heartbeat_interval, self.on_timer)
+        self.connection.watch(AnyWatch(self.on_heartbeat))
+
+    def on_heartbeat(self, frame):
+        self.last_heartbeat_on = monotonic.monotonic()
+
+    def on_any_frame(self):
+        """
+        Hehehe, most AMQP servers are not AMQP-compliant.
+        Consider a situation where you just got like a metric shitton of messages,
+        and the TCP connection is bustin' filled with those frames.
+
+        Server should still be able to send a heartbeat frame, but it doesn't, because of the queue, and
+        BANG, dead.
+
+        I know I'm being picky, but at least I implement this behaviour correctly - see priority argument in send.
+
+        Anyway, we should register an all-watch for this.
+        """
+        self.last_heartbeat_on = monotonic.monotonic()
+
+    def on_timer(self):
+        """Timer says we should send a heartbeat"""
+        self.connection.send([AMQPHeartbeatFrame()], priority=True)
+
+        if (monotonic.monotonic() - self.last_heartbeat_on) > 2*self.heartbeat_interval:
+            # closing because of heartbeat
+            self.connection.send(None)
+
+        self.connection.watchdog(self.heartbeat_interval, self.on_timer)
diff --git a/coolamqp/uplink/listener/__init__.py b/coolamqp/uplink/listener/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..6e09bdfd2188ed56b734fcadeee2c4f3879ab522
--- /dev/null
+++ b/coolamqp/uplink/listener/__init__.py
@@ -0,0 +1,18 @@
+# coding=UTF-8
+"""
+A listener is a thread that monitors a bunch of sockets for activity.
+
+Think "asyncio" but I couldn't be bothered to learn Twisted.
+
+It provides both for sending and receiving messages. It is written
+as a package, because the optimal network call, epoll, is not
+available on Windows, and you might just want to use it.
+
+select and poll are not optimal, because if you wanted to send
+something in that small gap where select/poll blocks, you won't
+immediately be able to do so. With epoll, you can.
+"""
+from __future__ import absolute_import, division, print_function
+
+
+from coolamqp.uplink.listener.thread import ListenerThread
diff --git a/coolamqp/uplink/listener/epoll_listener.py b/coolamqp/uplink/listener/epoll_listener.py
new file mode 100644
index 0000000000000000000000000000000000000000..a27095e0e1cbc46535385b1970afd9e1c29c5f58
--- /dev/null
+++ b/coolamqp/uplink/listener/epoll_listener.py
@@ -0,0 +1,151 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+import six
+import logging
+import select
+import monotonic
+import socket
+import collections
+import heapq
+
+from coolamqp.uplink.listener.socket import SocketFailed, BaseSocket
+
+
+logger = logging.getLogger(__name__)
+
+
+RO = select.EPOLLIN | select.EPOLLHUP | select.EPOLLERR
+RW = RO | select.EPOLLOUT
+
+
+class EpollSocket(BaseSocket):
+    """
+    EpollListener substitutes your BaseSockets with this
+    """
+    def __init__(self, sock, on_read, on_fail, listener):
+        BaseSocket.__init__(self, sock, on_read=on_read, on_fail=on_fail)
+        self.listener = listener
+        self.priority_queue = collections.deque()
+
+    def send(self, data, priority=False):
+        """
+        This can actually get called not by ListenerThread.
+        """
+        BaseSocket.send(self, data, priority=priority)
+        try:
+            self.listener.epoll.modify(self, RW)
+        except socket.error:
+            # silence. If there are errors, it's gonna get nuked soon.
+            pass
+
+    def oneshot(self, seconds_after, callable):
+        """
+        Set to fire a callable N seconds after
+        :param seconds_after: seconds after this
+        :param callable: callable/0
+        """
+        self.listener.oneshot(self, seconds_after, callable)
+
+    def noshot(self):
+        """
+        Clear all time-delayed callables.
+
+        This will make no time-delayed callables delivered if ran in listener thread
+        """
+        self.listener.noshot(self)
+
+
+class EpollListener(object):
+    """
+    A listener using epoll.
+    """
+
+    def __init__(self):
+        self.epoll = select.epoll()
+        self.fd_to_sock = {}
+        self.time_events = []
+
+    def wait(self, timeout=1):
+        events = self.epoll.poll(timeout=timeout)
+
+        # Timer events
+        mono = monotonic.monotonic()
+        while len(self.time_events) > 0 and (self.time_events[0][0] < mono):
+            ts, fd, callback = heapq.heappop(self.time_events)
+            callback()
+
+        for fd, event in events:
+            sock = self.fd_to_sock[fd]
+
+            # Errors
+            try:
+                if event & (select.EPOLLERR | select.EPOLLHUP):
+                    raise SocketFailed()
+
+                if event & select.EPOLLIN:
+                    sock.on_read()
+
+                if event & select.EPOLLOUT:
+                    if sock.on_write():
+                        # I'm done with sending for now
+                        self.epoll.modify(sock.fileno(), RW)
+
+            except SocketFailed:
+                self.epoll.unregister(fd)
+                del self.fd_to_sock[fd]
+                sock.on_fail()
+                self.noshot(sock)
+                sock.close()
+
+    def noshot(self, sock):
+        """
+        Clear all one-shots for a socket
+        :param sock: BaseSocket instance
+        """
+        fd = sock.fileno()
+        self.time_events = [q for q in self.time_events if q[1] != fd]
+
+    def shutdown(self):
+        """
+        Forcibly close all sockets that this manages (calling their on_fail's),
+        and close the object.
+
+        This object is unusable after this call.
+        """
+        for sock in six.itervalues(self.fd_to_sock):
+            sock.on_fail()
+            sock.close()
+        self.fd_to_sock = {}
+        self.epoll.close()
+        self.time_events = []
+
+    def oneshot(self, sock, delta, callback):
+        """
+        A socket registers a time callback
+        :param sock: BaseSocket instance
+        :param delta: "this seconds after now"
+        :param callback: callable/0
+        """
+        if sock.fileno() in self.fd_to_sock:
+            heapq.heappush(self.time_events, (monotonic.monotonic() + delta,
+                                              sock.fileno(),
+                                              callback
+                                              ))
+
+    def register(self, sock, on_read=lambda data: None,
+                             on_fail=lambda: None):
+        """
+        Add a socket to be listened for by the loop.
+
+        :param sock: a socket instance (as returned by socket module)
+        :param on_read: callable(data) to be called with received data
+        :param on_fail: callable() to be called when socket fails
+
+        :return: a BaseSocket instance to use instead of this socket
+        """
+        sock = EpollSocket(sock, on_read, on_fail, self)
+        self.fd_to_sock[sock.fileno()] = sock
+
+        self.epoll.register(sock, RW)
+        return sock
+
diff --git a/coolamqp/uplink/listener/socket.py b/coolamqp/uplink/listener/socket.py
new file mode 100644
index 0000000000000000000000000000000000000000..ffd56372ee8eddb44ad45b938c227060f819de42
--- /dev/null
+++ b/coolamqp/uplink/listener/socket.py
@@ -0,0 +1,145 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+import collections
+import socket
+
+
+class SocketFailed(IOError):
+    """Failure during socket operation. It needs to be discarded."""
+
+
+class BaseSocket(object):
+    """
+    Base class for sockets provided to listeners.
+
+    This is based on a standard TCP socket.
+
+    To be instantiated only by Listeners.
+    """
+
+    def __init__(self, sock, on_read=lambda data: None,
+                             on_time=lambda: None,
+                             on_fail=lambda: None):
+        """
+
+        :param sock: socketobject
+        :param on_read: callable(data) to be called when data is read.
+            Listener thread context
+            Raises ValueError on socket should be closed
+        :param on_time: callable() when time provided by socket expires
+        :param on_fail: callable() when socket is dead and to be discarded.
+            Listener thread context.
+            Socket descriptor will be handled by listener.
+            This should not
+        """
+        assert sock is not None
+        self.sock = sock
+        self.data_to_send = collections.deque()
+        self.priority_queue = collections.deque()   # when a piece of data is finished, this queue is checked first
+        self.my_on_read = on_read
+        self._on_fail = on_fail
+        self.on_time = on_time
+        self.is_failed = False
+
+    def on_fail(self):
+        self.is_failed = True
+        self._on_fail()
+
+    def send(self, data, priority=True):
+        """
+        Schedule to send some data.
+
+        :param data: data to send, or None to terminate this socket.
+            Note that data will be sent atomically, ie. without interruptions.
+        :param priority: preempt other datas. Property of sending data atomically will be maintained.
+        """
+        if self.is_failed: return
+
+        if data is None:
+            # THE POPE OF NOPE
+            self.priority_queue = collections.deque()
+            self.data_to_send = collections.deque([None])
+            return
+
+        if priority:
+            self.priority_queue.append(data)
+        else:
+            self.data_to_send.append(data)
+
+    def oneshot(self, seconds_after, callable):
+        """
+        Set to fire a callable N seconds after
+        :param seconds_after: seconds after this
+        :param callable: callable/0
+        """
+        raise Exception('Abstract; listener should override that')
+
+    def noshot(self):
+        """
+        Clear all time-delayed callables.
+
+        This will make no time-delayed callables delivered if ran in listener thread
+        """
+        raise Exception('Abstract; listener should override that')
+
+    def on_read(self):
+        """Socket is readable, called by Listener"""
+        if self.is_failed: return
+        try:
+            data = self.sock.recv(2048)
+        except (IOError, socket.error):
+            raise SocketFailed()
+
+        if len(data) == 0:
+            raise SocketFailed()
+
+        try:
+            self.my_on_read(data)
+        except ValueError:
+            raise SocketFailed()
+
+    def on_write(self):
+        """
+        Socket is writable, called by Listener
+        :raises SocketFailed: on socket error
+        :return: True if I'm done sending shit for now
+        """
+        if self.is_failed: return
+
+        while True:
+            if len(self.data_to_send) == 0:
+                if len(self.priority_queue) == 0:
+                    return True
+                else:
+                    self.data_to_send.appendleft(self.priority_queue.popleft())
+
+            assert len(self.data_to_send) > 0
+
+            if self.data_to_send[0] is None:
+                raise SocketFailed() # We should terminate the connection!
+
+            try:
+                sent = self.sock.send(self.data_to_send[0])
+            except (IOError, socket.error):
+                raise SocketFailed()
+
+            if sent < len(self.data_to_send[0]):
+                # Not everything could be sent
+                self.data_to_send[0] = buffer(self.data_to_send[0], sent)
+                return False
+            else:
+                # Looks like everything has been sent
+                self.data_to_send.popleft()     # mark as sent
+
+                if len(self.priority_queue) > 0:
+                    # We can send a priority pack
+                    print('Deploying priority data')
+                    self.data_to_send.appendleft(self.priority_queue.popleft())
+
+    def fileno(self):
+        """Return descriptor number"""
+        return self.sock.fileno()
+
+    def close(self):
+        """Close this socket"""
+        self.sock.close()
\ No newline at end of file
diff --git a/coolamqp/uplink/listener/thread.py b/coolamqp/uplink/listener/thread.py
new file mode 100644
index 0000000000000000000000000000000000000000..40abd2449db5201e4e5b5b3e3fa26b1db94689b7
--- /dev/null
+++ b/coolamqp/uplink/listener/thread.py
@@ -0,0 +1,40 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+
+import threading
+
+from coolamqp.uplink.listener.epoll_listener import EpollListener
+
+
+class ListenerThread(threading.Thread):
+    """
+    A thread that does the listening.
+
+    It automatically picks the best listener for given platform.
+    """
+
+    def __init__(self):
+        threading.Thread.__init__(self)
+        self.daemon = True
+        self.terminating = False
+        self.listener = EpollListener()
+
+    def terminate(self):
+       self.terminating = True
+
+    def run(self):
+        while not self.terminating:
+            self.listener.wait(timeout=1)
+        self.listener.shutdown()
+
+    def register(self, sock, on_read=lambda data: None, on_fail=lambda: None):
+        """
+        Add a socket to be listened for by the loop.
+
+        :param sock: a socket instance (as returned by socket module)
+        :param on_read: callable(data) to be called with received data
+        :param on_fail: callable() to be called when socket fails
+
+        :return: a BaseSocket instance to use instead of this socket
+        """
+        return self.listener.register(sock, on_read, on_fail)
diff --git a/examples/send_to_myself.py b/examples/send_to_myself.py
deleted file mode 100644
index bc113e54c71fc7be96d3bf035a84a63ee5c5bdb9..0000000000000000000000000000000000000000
--- a/examples/send_to_myself.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# coding=UTF-8
-from __future__ import print_function
-from coolamqp import Cluster, ClusterNode, Queue, Message, ConnectionUp, ConnectionDown, MessageReceived, ConsumerCancelled
-import logging
-import time
-
-QUEUE_NAME = 'f'
-
-logging.basicConfig()
-
-cluster = Cluster([ClusterNode('192.168.224.31:5672', 'smok', 'smok', 'smok', heartbeat=10)]).start()
-
-a_queue = Queue(QUEUE_NAME, auto_delete=True)
-cluster.consume(a_queue)
-cluster.qos(0, 1)
-
-
-q = time.time()
-while True:
-    if time.time() - q > 10:
-        q = time.time()
-        cluster.send(Message('hello world'), routing_key=QUEUE_NAME)
-
-    evt = cluster.drain(2)
-
-    if isinstance(evt, ConnectionUp):
-        print('Connection is up')
-    elif isinstance(evt, ConnectionDown):
-        print('Connection is down')
-    elif isinstance(evt, MessageReceived):
-        print('Message is %s' % (evt.message.body, ))
-        evt.message.ack()
-    elif isinstance(evt, ConsumerCancelled):
-        print('Consumer %s cancelled' % (evt.queue.name, ))
-
-
diff --git a/requirements.txt b/requirements.txt
index 5fecbcdb185943b6108d6983bf76d394d392c227..e791b2aada9c24c75598436e3e21008bda714327 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,4 @@
 amqp
 six
 monotonic
+futures
diff --git a/resources/amqp0-9-1.extended.xml b/resources/amqp0-9-1.extended.xml
new file mode 100644
index 0000000000000000000000000000000000000000..7ba2d60d3bab985e47c48fa25e2b219bcbac1786
--- /dev/null
+++ b/resources/amqp0-9-1.extended.xml
@@ -0,0 +1,3347 @@
+<?xml version = "1.0"?>
+
+<!--
+     WARNING: Modified from the official 0-9-1 specification XML by
+     the addition of:
+     confirm.select and confirm.select-ok,
+     exchange.bind and exchange.bind-ok,
+     exchange.unbind and exchange.unbind-ok,
+     basic.nack,
+     the ability for the Server to send basic.ack, basic.nack and
+      basic.cancel to the client, and
+     the un-deprecation of exchange.declare{auto-delete} and exchange.declare{internal}
+
+     Modifications are (c) 2010-2013 VMware, Inc. and may be distributed under
+     the same BSD license as the stripped spec.
+-->
+
+<!--
+    Copyright Notice
+    ================
+    Copyright (c) 2006-2008 Cisco Systems, Credit Suisse, Deutsche Boerse
+    Systems, Envoy Technologies, Inc., Goldman Sachs, IONA Technologies PLC,
+    iMatix Corporation, JPMorgan Chase Bank Inc. N.A, Novell, Rabbit
+    Technologies Ltd., Red Hat, Inc., TWIST Process Innovations Ltd, WS02
+    Inc. and 29West Inc. All rights reserved.
+
+    License
+    =======
+    Cisco Systems, Credit Suisse, Deutsche Boerse Systems, Envoy Technologies,
+    Inc., Goldman Sachs, IONA Technologies PLC, iMatix Corporation, JPMorgan
+    Chase Bank Inc. N.A, Novell, Rabbit Technologies Ltd., Red Hat, Inc.,
+    TWIST Process Innovations Ltd, WS02, Inc. and 29West Inc. (collectively,
+    the "Authors") each hereby grants to you a worldwide, perpetual,
+    royalty-free, nontransferable, nonexclusive license to (i) copy, display,
+    distribute and implement the Advanced Messaging Queue Protocol ("AMQP")
+    Specification and (ii) the Licensed Claims that are held by the Authors,
+    all for the purpose of implementing the Advanced Messaging Queue Protocol
+    Specification. Your license and any rights under this Agreement will
+    terminate immediately without notice from any Author if you bring any
+    claim, suit, demand, or action related to the Advanced Messaging Queue
+    Protocol Specification against any Author. Upon termination, you shall
+    destroy all copies of the Advanced Messaging Queue Protocol Specification
+    in your possession or control.
+
+    As used hereunder, "Licensed Claims" means those claims of a patent or
+    patent application, throughout the world, excluding design patents and
+    design registrations, owned or controlled, or that can be sublicensed
+    without fee and in compliance with the requirements of this Agreement,
+    by an Author or its affiliates now or at any future time and which would
+    necessarily be infringed by implementation of the Advanced Messaging
+    Queue Protocol Specification. A claim is necessarily infringed hereunder
+    only when it is not possible to avoid infringing it because there is no
+    plausible non-infringing alternative for implementing the required
+    portions of the Advanced Messaging Queue Protocol Specification.
+    Notwithstanding the foregoing, Licensed Claims shall not include any
+    claims other than as set forth above even if contained in the same patent
+    as Licensed Claims; or that read solely on any implementations of any
+    portion of the Advanced Messaging Queue Protocol Specification that are
+    not required by the Advanced Messaging Queue ProtocolSpecification, or
+    that, if licensed, would require a payment of royalties by the licensor
+    to unaffiliated third parties. Moreover, Licensed Claims shall not
+    include (i) any enabling technologies that may be necessary to make or
+    use any Licensed Product but are not themselves expressly set forth in
+    the Advanced Messaging Queue Protocol Specification (e.g., semiconductor
+    manufacturing technology, compiler technology, object oriented
+    technology, networking technology, operating system technology, and the
+    like); or (ii) the implementation of other published standards developed
+    elsewhere and merely referred to in the body of the Advanced Messaging
+    Queue Protocol Specification, or (iii) any Licensed Product and any
+    combinations thereof the purpose or function of which is not required
+    for compliance with the Advanced Messaging Queue Protocol Specification.
+    For purposes of this definition, the Advanced Messaging Queue Protocol
+    Specification shall be deemed to include both architectural and
+    interconnection requirements essential for interoperability and may also
+    include supporting source code artifacts where such architectural,
+    interconnection requirements and source code artifacts are expressly
+    identified as being required or documentation to achieve compliance with
+    the Advanced Messaging Queue Protocol Specification.
+
+    As used hereunder, "Licensed Products" means only those specific portions
+    of products (hardware, software or combinations thereof) that implement
+    and are compliant with all relevant portions of the Advanced Messaging
+    Queue Protocol Specification.
+
+    The following disclaimers, which you hereby also acknowledge as to any
+    use you may make of the Advanced Messaging Queue Protocol Specification:
+
+    THE ADVANCED MESSAGING QUEUE PROTOCOL SPECIFICATION IS PROVIDED "AS IS,"
+    AND THE AUTHORS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+    IMPLIED, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, OR TITLE; THAT THE
+    CONTENTS OF THE ADVANCED MESSAGING QUEUE PROTOCOL SPECIFICATION ARE
+    SUITABLE FOR ANY PURPOSE; NOR THAT THE IMPLEMENTATION OF THE ADVANCED
+    MESSAGING QUEUE PROTOCOL SPECIFICATION WILL NOT INFRINGE ANY THIRD PARTY
+    PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.
+
+    THE AUTHORS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL,
+    INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATING TO ANY
+    USE, IMPLEMENTATION OR DISTRIBUTION OF THE ADVANCED MESSAGING QUEUE
+    PROTOCOL SPECIFICATION.
+
+    The name and trademarks of the Authors may NOT be used in any manner,
+    including advertising or publicity pertaining to the Advanced Messaging
+    Queue Protocol Specification or its contents without specific, written
+    prior permission. Title to copyright in the Advanced Messaging Queue
+    Protocol Specification will at all times remain with the Authors.
+
+    No other rights are granted by implication, estoppel or otherwise.
+
+    Upon termination of your license or rights under this Agreement, you
+    shall destroy all copies of the Advanced Messaging Queue Protocol
+    Specification in your possession or control.
+
+    Trademarks
+    ==========
+    JPMorgan, JPMorgan Chase, Chase, the JPMorgan Chase logo and the
+    Octagon Symbol are trademarks of JPMorgan Chase & Co.
+
+    IMATIX and the iMatix logo are trademarks of iMatix Corporation sprl.
+
+    IONA, IONA Technologies, and the IONA logos are trademarks of IONA
+    Technologies PLC and/or its subsidiaries.
+
+    LINUX is a trademark of Linus Torvalds. RED HAT and JBOSS are registered
+    trademarks of Red Hat, Inc. in the US and other countries.
+
+    Java, all Java-based trademarks and OpenOffice.org are trademarks of
+    Sun Microsystems, Inc. in the United States, other countries, or both.
+
+    Other company, product, or service names may be trademarks or service
+    marks of others.
+
+    Links to full AMQP specification:
+    =================================
+    http://www.amqp.org
+-->
+
+<!--
+    <!DOCTYPE amqp SYSTEM "amqp.dtd">
+-->
+
+<!-- XML Notes
+
+    We use entities to indicate repetition; attributes to indicate properties.
+
+    We use the 'name' attribute as an identifier, usually within the context
+    of the surrounding entities.
+
+    We use spaces to seperate words in names, so that we can print names in
+    their natural form depending on the context - underlines for source code,
+    hyphens for written text, etc.
+
+    We do not enforce any particular validation mechanism but we support all
+    mechanisms.  The protocol definition conforms to a formal grammar that is
+    published seperately in several technologies.
+
+ -->
+
+<amqp major = "0" minor = "9" revision = "1"
+    port = "5672" comment = "AMQ Protocol version 0-9-1">
+  <!--
+      ======================================================
+      ==       CONSTANTS
+      ======================================================
+  -->
+  <!-- Frame types -->
+  <constant name = "frame-method"     value = "1" />
+  <constant name = "frame-header"     value = "2" />
+  <constant name = "frame-body"       value = "3" />
+  <constant name = "frame-heartbeat"  value = "8" />
+
+  <!-- Protocol constants -->
+  <constant name = "frame-min-size"   value = "4096" />
+  <constant name = "frame-end"        value = "206" />
+
+  <!-- Reply codes -->
+  <constant name = "reply-success" value = "200">
+    <doc>
+      Indicates that the method completed successfully. This reply code is
+      reserved for future use - the current protocol design does not use positive
+      confirmation and reply codes are sent only in case of an error.
+    </doc>
+  </constant>
+
+  <constant name = "content-too-large" value = "311" class = "soft-error">
+    <doc>
+      The client attempted to transfer content larger than the server could accept
+      at the present time. The client may retry at a later time.
+    </doc>
+  </constant>
+
+  <constant name = "no-consumers" value = "313" class = "soft-error">
+    <doc>
+      When the exchange cannot deliver to a consumer when the immediate flag is
+      set. As a result of pending data on the queue or the absence of any
+      consumers of the queue.
+    </doc>
+  </constant>
+
+  <constant name = "connection-forced" value = "320" class = "hard-error">
+    <doc>
+      An operator intervened to close the connection for some reason. The client
+      may retry at some later date.
+    </doc>
+  </constant>
+
+  <constant name = "invalid-path" value = "402" class = "hard-error">
+    <doc>
+      The client tried to work with an unknown virtual host.
+    </doc>
+  </constant>
+
+  <constant name = "access-refused" value = "403" class = "soft-error">
+    <doc>
+      The client attempted to work with a server entity to which it has no
+      access due to security settings.
+    </doc>
+  </constant>
+
+  <constant name = "not-found" value = "404" class = "soft-error">
+    <doc>
+      The client attempted to work with a server entity that does not exist.
+    </doc>
+  </constant>
+
+  <constant name = "resource-locked" value = "405" class = "soft-error">
+    <doc>
+      The client attempted to work with a server entity to which it has no
+      access because another client is working with it.
+    </doc>
+  </constant>
+
+  <constant name = "precondition-failed" value = "406" class = "soft-error">
+    <doc>
+      The client requested a method that was not allowed because some precondition
+      failed.
+    </doc>
+  </constant>
+
+  <constant name = "frame-error" value = "501" class = "hard-error">
+    <doc>
+      The sender sent a malformed frame that the recipient could not decode.
+      This strongly implies a programming error in the sending peer.
+    </doc>
+  </constant>
+
+  <constant name = "syntax-error" value = "502" class = "hard-error">
+    <doc>
+      The sender sent a frame that contained illegal values for one or more
+      fields. This strongly implies a programming error in the sending peer.
+    </doc>
+  </constant>
+
+  <constant name = "command-invalid" value = "503" class = "hard-error">
+    <doc>
+      The client sent an invalid sequence of frames, attempting to perform an
+      operation that was considered invalid by the server. This usually implies
+      a programming error in the client.
+    </doc>
+  </constant>
+
+  <constant name = "channel-error" value = "504" class = "hard-error">
+    <doc>
+      The client attempted to work with a channel that had not been correctly
+      opened. This most likely indicates a fault in the client layer.
+    </doc>
+  </constant>
+
+  <constant name = "unexpected-frame" value = "505" class = "hard-error">
+    <doc>
+      The peer sent a frame that was not expected, usually in the context of
+      a content header and body.  This strongly indicates a fault in the peer's
+      content processing.
+    </doc>
+  </constant>
+
+  <constant name = "resource-error" value = "506" class = "hard-error">
+    <doc>
+      The server could not complete the method because it lacked sufficient
+      resources. This may be due to the client creating too many of some type
+      of entity.
+    </doc>
+  </constant>
+
+  <constant name = "not-allowed" value = "530" class = "hard-error">
+    <doc>
+      The client tried to work with some entity in a manner that is prohibited
+      by the server, due to security settings or by some other criteria.
+    </doc>
+  </constant>
+
+  <constant name = "not-implemented" value = "540" class = "hard-error">
+    <doc>
+      The client tried to use functionality that is not implemented in the
+      server.
+    </doc>
+  </constant>
+
+  <constant name = "internal-error" value = "541" class = "hard-error">
+    <doc>
+      The server could not complete the method because of an internal error.
+      The server may require intervention by an operator in order to resume
+      normal operations.
+    </doc>
+  </constant>
+
+  <!--
+      ======================================================
+      ==       DOMAIN TYPES
+      ======================================================
+  -->
+
+  <domain name = "class-id" type = "short" />
+
+  <domain name = "consumer-tag" type = "shortstr" label = "consumer tag">
+    <doc>
+      Identifier for the consumer, valid within the current channel.
+    </doc>
+  </domain>
+
+  <domain name = "delivery-tag" type = "longlong" label = "server-assigned delivery tag">
+    <doc>
+      The server-assigned and channel-specific delivery tag
+    </doc>
+    <rule name = "channel-local">
+      <doc>
+        The delivery tag is valid only within the channel from which the message was
+        received. I.e. a client MUST NOT receive a message on one channel and then
+        acknowledge it on another.
+      </doc>
+    </rule>
+    <rule name = "non-zero">
+      <doc>
+        The server MUST NOT use a zero value for delivery tags. Zero is reserved
+        for client use, meaning "all messages so far received".
+      </doc>
+    </rule>
+  </domain>
+
+  <domain name = "exchange-name" type = "shortstr" label = "exchange name">
+    <doc>
+      The exchange name is a client-selected string that identifies the exchange for
+      publish methods.
+    </doc>
+    <assert check = "length" value = "127" />
+    <assert check = "regexp" value = "^[a-zA-Z0-9-_.:]*$" />
+  </domain>
+
+  <domain name = "method-id" type = "short" />
+
+  <domain name = "no-ack" type = "bit" label = "no acknowledgement needed">
+    <doc>
+      If this field is set the server does not expect acknowledgements for
+      messages. That is, when a message is delivered to the client the server
+      assumes the delivery will succeed and immediately dequeues it. This
+      functionality may increase performance but at the cost of reliability.
+      Messages can get lost if a client dies before they are delivered to the
+      application.
+    </doc>
+  </domain>
+
+  <domain name = "no-local" type = "bit" label = "do not deliver own messages">
+    <doc>
+      If the no-local field is set the server will not send messages to the connection that
+      published them.
+    </doc>
+  </domain>
+
+  <domain name = "no-wait" type = "bit" label = "do not send reply method">
+    <doc>
+      If set, the server will not respond to the method. The client should not wait
+      for a reply method. If the server could not complete the method it will raise a
+      channel or connection exception.
+    </doc>
+  </domain>
+
+  <domain name = "path" type = "shortstr">
+    <doc>
+      Unconstrained.
+    </doc>
+    <assert check = "notnull" />
+    <assert check = "length" value = "127" />
+  </domain>
+
+  <domain name = "peer-properties" type = "table">
+    <doc>
+      This table provides a set of peer properties, used for identification, debugging,
+      and general information.
+    </doc>
+  </domain>
+
+  <domain name = "queue-name" type = "shortstr" label = "queue name">
+    <doc>
+      The queue name identifies the queue within the vhost.  In methods where the queue
+      name may be blank, and that has no specific significance, this refers to the
+      'current' queue for the channel, meaning the last queue that the client declared
+      on the channel.  If the client did not declare a queue, and the method needs a
+      queue name, this will result in a 502 (syntax error) channel exception.
+    </doc>
+    <assert check = "length" value = "127" />
+    <assert check = "regexp" value = "^[a-zA-Z0-9-_.:]*$" />
+  </domain>
+
+  <domain name = "redelivered" type = "bit" label = "message is being redelivered">
+    <doc>
+      This indicates that the message has been previously delivered to this or
+      another client.
+    </doc>
+    <rule name = "implementation">
+      <doc>
+        The server SHOULD try to signal redelivered messages when it can. When
+        redelivering a message that was not successfully acknowledged, the server
+        SHOULD deliver it to the original client if possible.
+      </doc>
+      <doc type = "scenario">
+        Declare a shared queue and publish a message to the queue.  Consume the
+        message using explicit acknowledgements, but do not acknowledge the
+        message.  Close the connection, reconnect, and consume from the queue
+        again.  The message should arrive with the redelivered flag set.
+      </doc>
+    </rule>
+    <rule name = "hinting">
+      <doc>
+        The client MUST NOT rely on the redelivered field but should take it as a
+        hint that the message may already have been processed. A fully robust
+        client must be able to track duplicate received messages on non-transacted,
+        and locally-transacted channels.
+      </doc>
+    </rule>
+  </domain>
+
+  <domain name = "message-count" type = "long" label = "number of messages in queue">
+    <doc>
+      The number of messages in the queue, which will be zero for newly-declared
+      queues. This is the number of messages present in the queue, and committed
+      if the channel on which they were published is transacted, that are not
+      waiting acknowledgement.
+    </doc>
+  </domain>
+
+  <domain name = "reply-code" type = "short" label = "reply code from server">
+    <doc>
+      The reply code. The AMQ reply codes are defined as constants at the start
+      of this formal specification.
+    </doc>
+    <assert check = "notnull" />
+  </domain>
+
+  <domain name = "reply-text" type = "shortstr" label = "localised reply text">
+    <doc>
+      The localised reply text. This text can be logged as an aid to resolving
+      issues.
+    </doc>
+    <assert check = "notnull" />
+  </domain>
+
+  <!-- Elementary domains -->
+  <domain name = "bit"        type = "bit"       label = "single bit" />
+  <domain name = "octet"      type = "octet"     label = "single octet" />
+  <domain name = "short"      type = "short"     label = "16-bit integer" />
+  <domain name = "long"       type = "long"      label = "32-bit integer" />
+  <domain name = "longlong"   type = "longlong"  label = "64-bit integer" />
+  <domain name = "shortstr"   type = "shortstr"  label = "short string" />
+  <domain name = "longstr"    type = "longstr"   label = "long string" />
+  <domain name = "timestamp"  type = "timestamp" label = "64-bit timestamp" />
+  <domain name = "table"      type = "table"     label = "field table" />
+
+  <!-- ==  CONNECTION  ======================================================= -->
+
+  <class name = "connection" handler = "connection" index = "10" label = "work with socket connections">
+    <doc>
+      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.
+    </doc>
+
+    <doc type = "grammar">
+      connection          = open-connection *use-connection close-connection
+      open-connection     = C:protocol-header
+                            S:START C:START-OK
+                            *challenge
+                            S:TUNE C:TUNE-OK
+                            C:OPEN S:OPEN-OK
+      challenge           = S:SECURE C:SECURE-OK
+      use-connection      = *channel
+      close-connection    = C:CLOSE S:CLOSE-OK
+                          / S:CLOSE C:CLOSE-OK
+    </doc>
+
+    <chassis name = "server" implement = "MUST" />
+    <chassis name = "client" implement = "MUST" />
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "start" synchronous = "1" index = "10" label = "start connection negotiation">
+      <doc>
+        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
+        which the client can use for authentication.
+      </doc>
+
+      <rule name = "protocol-name">
+        <doc>
+          If the server cannot support the protocol specified in the protocol header,
+          it MUST respond with a valid protocol header and then close the socket
+          connection.
+        </doc>
+        <doc type = "scenario">
+          The client sends a protocol header containing an invalid protocol name.
+          The server MUST respond by sending a valid protocol header and then closing
+          the connection.
+        </doc>
+      </rule>
+      <rule name = "server-support">
+        <doc>
+          The server MUST provide a protocol version that is lower than or equal to
+          that requested by the client in the protocol header.
+        </doc>
+        <doc type = "scenario">
+          The client requests a protocol version that is higher than any valid
+          implementation, e.g. 2.0.  The server must respond with a protocol header
+          indicating its supported protocol version, e.g. 1.0.
+        </doc>
+      </rule>
+      <rule name = "client-support">
+        <doc>
+          If the client cannot handle the protocol version suggested by the server
+          it MUST close the socket connection without sending any further data.
+        </doc>
+        <doc type = "scenario">
+          The server sends a protocol version that is lower than any valid
+          implementation, e.g. 0.1.  The client must respond by closing the
+          connection without sending any further data.
+        </doc>
+      </rule>
+
+      <chassis name = "client" implement = "MUST" />
+      <response name = "start-ok" />
+
+      <field name = "version-major" domain = "octet" label = "protocol major version">
+        <doc>
+          The major version number can take any value from 0 to 99 as defined in the
+          AMQP specification.
+        </doc>
+      </field>
+
+      <field name = "version-minor" domain = "octet" label = "protocol minor version">
+        <doc>
+          The minor version number can take any value from 0 to 99 as defined in the
+          AMQP specification.
+        </doc>
+      </field>
+
+      <field name = "server-properties" domain = "peer-properties" label = "server properties">
+        <rule name = "required-fields">
+          <doc>
+            The properties SHOULD contain at least these fields: "host", specifying the
+            server host name or address, "product", giving the name of the server product,
+            "version", giving the name of the server version, "platform", giving the name
+            of the operating system, "copyright", if appropriate, and "information", giving
+            other general information.
+          </doc>
+          <doc type = "scenario">
+            Client connects to server and inspects the server properties. It checks for
+            the presence of the required fields.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "mechanisms" domain = "longstr" label = "available security mechanisms">
+        <doc>
+          A list of the security mechanisms that the server supports, delimited by spaces.
+        </doc>
+        <assert check = "notnull" />
+      </field>
+
+      <field name = "locales" domain = "longstr" label = "available message locales">
+        <doc>
+          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.
+        </doc>
+        <rule name = "required-support">
+          <doc>
+            The server MUST support at least the en_US locale.
+          </doc>
+          <doc type = "scenario">
+            Client connects to server and inspects the locales field. It checks for
+            the presence of the required locale(s).
+          </doc>
+        </rule>
+        <assert check = "notnull" />
+      </field>
+    </method>
+
+    <method name = "start-ok" synchronous = "1" index = "11"
+      label = "select security mechanism and locale">
+      <doc>
+        This method selects a SASL security mechanism.
+      </doc>
+
+      <chassis name = "server" implement = "MUST" />
+
+      <field name = "client-properties" domain = "peer-properties" label = "client properties">
+        <rule name = "required-fields">
+          <!-- This rule is not testable from the client side -->
+          <doc>
+            The properties SHOULD contain at least these fields: "product", giving the name
+            of the client product, "version", giving the name of the client version, "platform",
+            giving the name of the operating system, "copyright", if appropriate, and
+            "information", giving other general information.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "mechanism" domain = "shortstr" label = "selected security mechanism">
+        <doc>
+          A single security mechanisms selected by the client, which must be one of those
+          specified by the server.
+        </doc>
+        <rule name = "security">
+          <doc>
+            The client SHOULD authenticate using the highest-level security profile it
+            can handle from the list provided by the server.
+          </doc>
+        </rule>
+        <rule name = "validity">
+          <doc>
+            If the mechanism field does not contain one of the security mechanisms
+            proposed by the server in the Start method, the server MUST close the
+            connection without sending any further data.
+          </doc>
+          <doc type = "scenario">
+            Client connects to server and sends an invalid security mechanism. The
+            server must respond by closing the connection (a socket close, with no
+            connection close negotiation).
+          </doc>
+        </rule>
+        <assert check = "notnull" />
+      </field>
+
+      <field name = "response" domain = "longstr" label = "security response data">
+        <doc>
+          A block of opaque data passed to the security mechanism. The contents of this
+          data are defined by the SASL security mechanism.
+        </doc>
+        <assert check = "notnull" />
+      </field>
+
+      <field name = "locale" domain = "shortstr" label = "selected message locale">
+        <doc>
+          A single message locale selected by the client, which must be one of those
+          specified by the server.
+        </doc>
+        <assert check = "notnull" />
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "secure" synchronous = "1" index = "20" label = "security mechanism challenge">
+      <doc>
+        The SASL protocol works by exchanging challenges and responses until both peers have
+        received sufficient information to authenticate each other. This method challenges
+        the client to provide more information.
+      </doc>
+
+      <chassis name = "client" implement = "MUST" />
+      <response name = "secure-ok" />
+
+      <field name = "challenge" domain = "longstr" label = "security challenge data">
+        <doc>
+          Challenge information, a block of opaque binary data passed to the security
+          mechanism.
+        </doc>
+      </field>
+    </method>
+
+    <method name = "secure-ok" synchronous = "1" index = "21" label = "security mechanism response">
+      <doc>
+        This method attempts to authenticate, passing a block of SASL data for the security
+        mechanism at the server side.
+      </doc>
+
+      <chassis name = "server" implement = "MUST" />
+
+      <field name = "response" domain = "longstr" label = "security response data">
+        <doc>
+          A block of opaque data passed to the security mechanism. The contents of this
+          data are defined by the SASL security mechanism.
+        </doc>
+        <assert check = "notnull" />
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "tune" synchronous = "1" index = "30"
+      label = "propose connection tuning parameters">
+      <doc>
+        This method proposes a set of connection configuration values to the client. The
+        client can accept and/or adjust these.
+      </doc>
+
+      <chassis name = "client" implement = "MUST" />
+
+      <response name = "tune-ok" />
+
+      <field name = "channel-max" domain = "short" label = "proposed maximum channels">
+        <doc>
+          Specifies highest channel number that the server permits.  Usable channel numbers
+          are in the range 1..channel-max.  Zero indicates no specified limit.
+        </doc>
+      </field>
+
+      <field name = "frame-max" domain = "long" label = "proposed maximum frame size">
+        <doc>
+          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
+          that the server does not impose any specific limit but may reject very large
+          frames if it cannot allocate resources for them.
+        </doc>
+        <rule name = "minimum">
+          <doc>
+            Until the frame-max has been negotiated, both peers MUST accept frames of up
+            to frame-min-size octets large, and the minimum negotiated value for frame-max
+            is also frame-min-size.
+          </doc>
+          <doc type = "scenario">
+            Client connects to server and sends a large properties field, creating a frame
+            of frame-min-size octets.  The server must accept this frame.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "heartbeat" domain = "short" label = "desired heartbeat delay">
+        <doc>
+          The delay, in seconds, of the connection heartbeat that the server wants.
+          Zero means the server does not want a heartbeat.
+        </doc>
+      </field>
+    </method>
+
+    <method name = "tune-ok" synchronous = "1" index = "31"
+      label = "negotiate connection tuning parameters">
+      <doc>
+        This method sends the client's connection tuning parameters to the server.
+        Certain fields are negotiated, others provide capability information.
+      </doc>
+
+      <chassis name = "server" implement = "MUST" />
+
+      <field name = "channel-max" domain = "short" label = "negotiated maximum channels">
+        <doc>
+          The maximum total number of channels that the client will use per connection.
+        </doc>
+        <rule name = "upper-limit">
+          <doc>
+            If the client specifies a channel max that is higher than the value provided
+            by the server, the server MUST close the connection without attempting a
+            negotiated close.  The server may report the error in some fashion to assist
+            implementors.
+          </doc>
+        </rule>
+        <assert check = "notnull" />
+        <assert check = "le" method = "tune" field = "channel-max" />
+      </field>
+
+      <field name = "frame-max" domain = "long" label = "negotiated maximum frame size">
+        <doc>
+          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
+          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
+          be broken into frames of arbitrary size.
+        </doc>
+        <rule name = "minimum">
+          <doc>
+            Until the frame-max has been negotiated, both peers MUST accept frames of up
+            to frame-min-size octets large, and the minimum negotiated value for frame-max
+            is also frame-min-size.
+          </doc>
+        </rule>
+        <rule name = "upper-limit">
+          <doc>
+            If the client specifies a frame max that is higher than the value provided
+            by the server, the server MUST close the connection without attempting a
+            negotiated close. The server may report the error in some fashion to assist
+            implementors.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "heartbeat" domain = "short" label = "desired heartbeat delay">
+        <doc>
+          The delay, in seconds, of the connection heartbeat that the client wants. Zero
+          means the client does not want a heartbeat.
+        </doc>
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "open" synchronous = "1" index = "40" label = "open connection to virtual host">
+      <doc>
+        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.
+        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.
+      </doc>
+
+      <chassis name = "server" implement = "MUST" />
+      <response name = "open-ok" />
+
+      <field name = "virtual-host" domain = "path" label = "virtual host name">
+        <doc>
+          The name of the virtual host to work with.
+        </doc>
+        <rule name = "separation">
+          <doc>
+            If the server supports multiple virtual hosts, it MUST enforce a full
+            separation of exchanges, queues, and all associated entities per virtual
+            host. An application, connected to a specific virtual host, MUST NOT be able
+            to access resources of another virtual host.
+          </doc>
+        </rule>
+        <rule name = "security">
+          <doc>
+            The server SHOULD verify that the client has permission to access the
+            specified virtual host.
+          </doc>
+        </rule>
+      </field>
+      <!-- Deprecated: "capabilities", must be zero -->
+      <field name = "reserved-1" type = "shortstr" reserved = "1" />
+      <!-- Deprecated: "insist", must be zero -->
+      <field name = "reserved-2" type = "bit" reserved = "1" />
+    </method>
+
+    <method name = "open-ok" synchronous = "1" index = "41" label = "signal that connection is ready">
+      <doc>
+        This method signals to the client that the connection is ready for use.
+      </doc>
+      <chassis name = "client" implement = "MUST" />
+      <!-- Deprecated: "known-hosts", must be zero -->
+      <field name = "reserved-1" type = "shortstr" reserved = "1" />
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "close" synchronous = "1" index = "50" label = "request a connection close">
+      <doc>
+        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
+        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.
+      </doc>
+      <rule name = "stability">
+        <doc>
+          After sending this method, any received methods except Close and Close-OK MUST
+          be discarded.  The response to receiving a Close after sending Close must be to
+          send Close-Ok.
+        </doc>
+      </rule>
+
+      <chassis name = "client" implement = "MUST" />
+      <chassis name = "server" implement = "MUST" />
+      <response name = "close-ok" />
+
+      <field name = "reply-code" domain = "reply-code" />
+      <field name = "reply-text" domain = "reply-text" />
+
+      <field name = "class-id" domain = "class-id" label = "failing method class">
+        <doc>
+          When the close is provoked by a method exception, this is the class of the
+          method.
+        </doc>
+      </field>
+
+      <field name = "method-id" domain = "method-id" label = "failing method ID">
+        <doc>
+          When the close is provoked by a method exception, this is the ID of the method.
+        </doc>
+      </field>
+    </method>
+
+    <method name = "close-ok" synchronous = "1" index = "51" label = "confirm a connection close">
+      <doc>
+        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.
+      </doc>
+      <rule name = "reporting">
+        <doc>
+          A peer that detects a socket closure without having received a Close-Ok
+          handshake method SHOULD log the error.
+        </doc>
+      </rule>
+      <chassis name = "client" implement = "MUST" />
+      <chassis name = "server" implement = "MUST" />
+    </method>
+
+    <method name = "blocked" index = "60">
+      <doc>
+        This method indicates that a connection has been blocked
+        and does not accept new publishes.
+      </doc>
+      <chassis name = "server" implement = "MUST"/>
+      <chassis name = "client" implement = "MUST"/>
+      <field name = "reason" domain = "shortstr" />
+    </method>
+    <method name = "unblocked" index = "61">
+      <doc>
+        This method indicates that a connection has been unblocked
+        and now accepts publishes.
+      </doc>
+      <chassis name = "server" implement = "MUST"/>
+      <chassis name = "client" implement = "MUST"/>
+    </method>
+  </class>
+
+  <!-- ==  CHANNEL  ========================================================== -->
+
+  <class name = "channel" handler = "channel" index = "20" label = "work with channels">
+    <doc>
+      The channel class provides methods for a client to establish a channel to a
+      server and for both peers to operate the channel thereafter.
+    </doc>
+
+    <doc type = "grammar">
+      channel             = open-channel *use-channel close-channel
+      open-channel        = C:OPEN S:OPEN-OK
+      use-channel         = C:FLOW S:FLOW-OK
+                          / S:FLOW C:FLOW-OK
+                          / functional-class
+      close-channel       = C:CLOSE S:CLOSE-OK
+                          / S:CLOSE C:CLOSE-OK
+    </doc>
+
+    <chassis name = "server" implement = "MUST" />
+    <chassis name = "client" implement = "MUST" />
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "open" synchronous = "1" index = "10" label = "open a channel for use">
+      <doc>
+        This method opens a channel to the server.
+      </doc>
+      <rule name = "state" on-failure = "channel-error">
+        <doc>
+          The client MUST NOT use this method on an already-opened channel.
+        </doc>
+        <doc type = "scenario">
+          Client opens a channel and then reopens the same channel.
+        </doc>
+      </rule>
+      <chassis name = "server" implement = "MUST" />
+      <response name = "open-ok" />
+      <!-- Deprecated: "out-of-band", must be zero -->
+      <field name = "reserved-1" type = "shortstr" reserved = "1" />
+    </method>
+
+    <method name = "open-ok" synchronous = "1" index = "11" label = "signal that the channel is ready">
+      <doc>
+        This method signals to the client that the channel is ready for use.
+      </doc>
+      <chassis name = "client" implement = "MUST" />
+      <!-- Deprecated: "channel-id", must be zero -->
+      <field name = "reserved-1" type = "longstr" reserved = "1" />
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "flow" synchronous = "1" index = "20" label = "enable/disable flow from peer">
+      <doc>
+        This method asks the peer to pause or restart the flow of content data sent by
+        a consumer. This is a simple flow-control mechanism that a peer can use to avoid
+        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
+        not affect contents returned by Basic.Get-Ok methods.
+      </doc>
+
+      <rule name = "initial-state">
+        <doc>
+          When a new channel is opened, it is active (flow is active). Some applications
+          assume that channels are inactive until started. To emulate this behaviour a
+          client MAY open the channel, then pause it.
+        </doc>
+      </rule>
+
+      <rule name = "bidirectional">
+        <doc>
+          When sending content frames, a peer SHOULD monitor the channel for incoming
+          methods and respond to a Channel.Flow as rapidly as possible.
+        </doc>
+      </rule>
+
+      <rule name = "throttling">
+        <doc>
+          A peer MAY use the Channel.Flow method to throttle incoming content data for
+          internal reasons, for example, when exchanging data over a slower connection.
+        </doc>
+      </rule>
+
+      <rule name = "expected-behaviour">
+        <doc>
+          The peer that requests a Channel.Flow method MAY disconnect and/or ban a peer
+          that does not respect the request.  This is to prevent badly-behaved clients
+          from overwhelming a server.
+        </doc>
+      </rule>
+
+      <chassis name = "server" implement = "MUST" />
+      <chassis name = "client" implement = "MUST" />
+
+      <response name = "flow-ok" />
+
+      <field name = "active" domain = "bit" label = "start/stop content frames">
+        <doc>
+          If 1, the peer starts sending content frames. If 0, the peer stops sending
+          content frames.
+        </doc>
+      </field>
+    </method>
+
+    <method name = "flow-ok" index = "21" label = "confirm a flow method">
+      <doc>
+        Confirms to the peer that a flow command was received and processed.
+      </doc>
+      <chassis name = "server" implement = "MUST" />
+      <chassis name = "client" implement = "MUST" />
+      <field name = "active" domain = "bit" label = "current flow setting">
+        <doc>
+          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.
+        </doc>
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "close" synchronous = "1" index = "40" label = "request a channel close">
+      <doc>
+        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
+        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.
+      </doc>
+      <rule name = "stability">
+        <doc>
+          After sending this method, any received methods except Close and Close-OK MUST
+          be discarded.  The response to receiving a Close after sending Close must be to
+          send Close-Ok.
+        </doc>
+      </rule>
+
+      <chassis name = "client" implement = "MUST" />
+      <chassis name = "server" implement = "MUST" />
+      <response name = "close-ok" />
+
+      <field name = "reply-code" domain = "reply-code" />
+      <field name = "reply-text" domain = "reply-text" />
+
+      <field name = "class-id" domain = "class-id" label = "failing method class">
+        <doc>
+          When the close is provoked by a method exception, this is the class of the
+          method.
+        </doc>
+      </field>
+
+      <field name = "method-id" domain = "method-id" label = "failing method ID">
+        <doc>
+          When the close is provoked by a method exception, this is the ID of the method.
+        </doc>
+      </field>
+    </method>
+
+    <method name = "close-ok" synchronous = "1" index = "41" label = "confirm a channel close">
+      <doc>
+        This method confirms a Channel.Close method and tells the recipient that it is safe
+        to release resources for the channel.
+      </doc>
+      <rule name = "reporting">
+        <doc>
+          A peer that detects a socket closure without having received a Channel.Close-Ok
+          handshake method SHOULD log the error.
+        </doc>
+      </rule>
+      <chassis name = "client" implement = "MUST" />
+      <chassis name = "server" implement = "MUST" />
+    </method>
+  </class>
+
+  <!-- ==  EXCHANGE  ========================================================= -->
+
+  <class name = "exchange" handler = "channel" index = "40" label = "work with exchanges">
+    <doc>
+      Exchanges match and distribute messages across queues. Exchanges can be configured in
+      the server or declared at runtime.
+    </doc>
+
+    <doc type = "grammar">
+      exchange            = C:DECLARE  S:DECLARE-OK
+                          / C:DELETE   S:DELETE-OK
+                          / C:BIND     S:BIND-OK
+                          / C:UNBIND   S:UNBIND-OK
+    </doc>
+
+    <chassis name = "server" implement = "MUST" />
+    <chassis name = "client" implement = "MUST" />
+
+    <rule name = "required-types">
+      <doc>
+        The server MUST implement these standard exchange types: fanout, direct.
+      </doc>
+      <doc type = "scenario">
+        Client attempts to declare an exchange with each of these standard types.
+      </doc>
+    </rule>
+    <rule name = "recommended-types">
+      <doc>
+        The server SHOULD implement these standard exchange types: topic, headers.
+      </doc>
+      <doc type = "scenario">
+        Client attempts to declare an exchange with each of these standard types.
+      </doc>
+    </rule>
+    <rule name = "required-instances">
+      <doc>
+        The server MUST, in each virtual host, pre-declare an exchange instance
+        for each standard exchange type that it implements, where the name of the
+        exchange instance, if defined, is "amq." followed by the exchange type name.
+      </doc>
+      <doc>
+        The server MUST, in each virtual host, pre-declare at least two direct
+        exchange instances: one named "amq.direct", the other with no public name
+        that serves as a default  exchange for Publish methods.
+      </doc>
+      <doc type = "scenario">
+        Client declares a temporary queue and attempts to bind to each required
+        exchange instance ("amq.fanout", "amq.direct", "amq.topic", and "amq.headers"
+        if those types are defined).
+      </doc>
+    </rule>
+    <rule name = "default-exchange">
+      <doc>
+        The server MUST pre-declare a direct exchange with no public name to act as
+        the default exchange for content Publish methods and for default queue bindings.
+      </doc>
+      <doc type = "scenario">
+        Client checks that the default exchange is active by specifying a queue
+        binding with no exchange name, and publishing a message with a suitable
+        routing key but without specifying the exchange name, then ensuring that
+        the message arrives in the queue correctly.
+      </doc>
+    </rule>
+    <rule name = "default-access">
+      <doc>
+        The server MUST NOT allow clients to access the default exchange except
+        by specifying an empty exchange name in the Queue.Bind and content Publish
+        methods.
+      </doc>
+    </rule>
+    <rule name = "extensions">
+      <doc>
+        The server MAY implement other exchange types as wanted.
+      </doc>
+    </rule>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "declare" synchronous = "1" index = "10" label = "verify exchange exists, create if needed">
+      <doc>
+        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.
+      </doc>
+      <rule name = "minimum">
+        <doc>
+          The server SHOULD support a minimum of 16 exchanges per virtual host and
+          ideally, impose no limit except as defined by available resources.
+        </doc>
+        <doc type = "scenario">
+          The client declares as many exchanges as it can until the server reports
+          an error; the number of exchanges successfully declared must be at least
+          sixteen.
+        </doc>
+      </rule>
+
+      <chassis name = "server" implement = "MUST" />
+      <response name = "declare-ok" />
+
+      <!-- Deprecated: "ticket", must be zero -->
+      <field name = "reserved-1" type = "short" reserved = "1" />
+
+      <field name = "exchange" domain = "exchange-name">
+        <rule name = "reserved" on-failure = "access-refused">
+          <doc>
+            Exchange names starting with "amq." are reserved for pre-declared and
+            standardised exchanges. The client MAY declare an exchange starting with
+            "amq." if the passive option is set, or the exchange already exists.
+          </doc>
+          <doc type = "scenario">
+            The client attempts to declare a non-existing exchange starting with
+            "amq." and with the passive option set to zero.
+          </doc>
+        </rule>
+        <rule name = "syntax" on-failure = "precondition-failed">
+          <doc>
+            The exchange name consists of a non-empty sequence of these characters:
+            letters, digits, hyphen, underscore, period, or colon.
+          </doc>
+          <doc type = "scenario">
+            The client attempts to declare an exchange with an illegal name.
+          </doc>
+        </rule>
+        <assert check = "notnull" />
+      </field>
+
+      <field name = "type" domain = "shortstr" label = "exchange type">
+        <doc>
+          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
+          messages are routed through it. It is not valid or meaningful to attempt to
+          change the type of an existing exchange.
+        </doc>
+        <rule name = "typed" on-failure = "not-allowed">
+          <doc>
+            Exchanges cannot be redeclared with different types.  The client MUST not
+            attempt to redeclare an existing exchange with a different type than used
+            in the original Exchange.Declare method.
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+        <rule name = "support" on-failure = "command-invalid">
+          <doc>
+            The client MUST NOT attempt to declare an exchange with a type that the
+            server does not support.
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "passive" domain = "bit" label = "do not create exchange">
+        <doc>
+          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
+          use this to check whether an exchange exists without modifying the
+          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.
+          Arguments are compared for semantic equivalence.
+        </doc>
+        <rule name = "not-found">
+          <doc>
+            If set, and the exchange does not already exist, the server MUST
+            raise a channel exception with reply code 404 (not found).
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+        <rule name = "equivalent">
+          <doc>
+            If not set and the exchange exists, the server MUST check that the
+            existing exchange has the same values for type, durable, and arguments
+            fields.  The server MUST respond with Declare-Ok if the requested
+            exchange matches these fields, and MUST raise a channel exception if
+            not.
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "durable" domain = "bit" label = "request a durable exchange">
+        <doc>
+          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
+          (transient exchanges) are purged if/when a server restarts.
+        </doc>
+        <rule name = "support">
+          <doc>
+            The server MUST support both durable and transient exchanges.
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "auto-delete" domain = "bit" label = "auto-delete when unused">
+        <doc>
+          If set, the exchange is deleted when all queues have
+          finished using it.
+        </doc>
+        <rule name = "amq_exchange_02">
+          <doc>
+            The server SHOULD allow for a reasonable delay between the
+            point when it determines that an exchange is not being
+            used (or no longer used), and the point when it deletes
+            the exchange.  At the least it must allow a client to
+            create an exchange and then bind a queue to it, with a
+            small but non-zero delay between these two actions.
+          </doc>
+        </rule>
+        <rule name = "amq_exchange_25">
+          <doc>
+            The server MUST ignore the auto-delete field if the
+            exchange already exists.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "internal" domain = "bit" label = "create internal exchange">
+        <doc>
+          If set, the exchange may not be used directly by publishers,
+          but only when bound to other exchanges. Internal exchanges
+          are used to construct wiring that is not visible to
+          applications.
+        </doc>
+      </field>
+
+      <field name = "no-wait" domain = "no-wait" />
+
+      <field name = "arguments" domain = "table" label = "arguments for declaration">
+        <doc>
+          A set of arguments for the declaration. The syntax and semantics of these
+          arguments depends on the server implementation.
+        </doc>
+      </field>
+    </method>
+
+    <method name = "declare-ok" synchronous = "1" index = "11" label = "confirm exchange declaration">
+      <doc>
+        This method confirms a Declare method and confirms the name of the exchange,
+        essential for automatically-named exchanges.
+      </doc>
+      <chassis name = "client" implement = "MUST" />
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "delete" synchronous = "1" index = "20" label = "delete an exchange">
+      <doc>
+        This method deletes an exchange. When an exchange is deleted all queue bindings on
+        the exchange are cancelled.
+      </doc>
+
+      <chassis name = "server" implement = "MUST" />
+      <response name = "delete-ok" />
+
+      <!-- Deprecated: "ticket", must be zero -->
+      <field name = "reserved-1" type = "short" reserved = "1" />
+
+      <field name = "exchange" domain = "exchange-name">
+        <rule name = "exists" on-failure = "not-found">
+          <doc>
+            The client MUST NOT attempt to delete an exchange that does not exist.
+          </doc>
+        </rule>
+        <assert check = "notnull" />
+      </field>
+
+      <field name = "if-unused" domain = "bit" label = "delete only if unused">
+        <doc>
+          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
+          channel exception instead.
+        </doc>
+        <rule name = "in-use" on-failure = "precondition-failed">
+          <doc>
+            The server MUST NOT delete an exchange that has bindings on it, if the if-unused
+            field is true.
+          </doc>
+          <doc type = "scenario">
+            The client declares an exchange, binds a queue to it, then tries to delete it
+            setting if-unused to true.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "no-wait" domain = "no-wait" />
+    </method>
+
+    <method name = "delete-ok" synchronous = "1" index = "21"
+      label = "confirm deletion of an exchange">
+      <doc>This method confirms the deletion of an exchange.</doc>
+      <chassis name = "client" implement = "MUST" />
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "bind" synchronous = "1" index = "30"
+            label = "bind exchange to an exchange">
+
+      <doc>This method binds an exchange to an exchange.</doc>
+
+      <rule name = "duplicates">
+        <doc>
+          A server MUST allow and ignore duplicate bindings - that is,
+          two or more bind methods for a specific exchanges, with
+          identical arguments - without treating these as an error.
+        </doc>
+        <doc type = "scenario">
+          A client binds an exchange to an exchange. The client then
+          repeats the bind (with identical arguments).
+        </doc>
+      </rule>
+
+      <rule name = "cyclical">
+        <doc>
+          A server MUST allow cycles of exchange bindings to be
+          created including allowing an exchange to be bound to
+          itself.
+        </doc>
+        <doc type = "scenario">
+          A client declares an exchange and binds it to itself.
+        </doc>
+      </rule>
+
+      <rule name = "unique">
+        <doc>
+          A server MUST not deliver the same message more than once to
+          a destination exchange, even if the topology of exchanges
+          and bindings results in multiple (even infinite) routes to
+          that exchange.
+        </doc>
+        <doc type = "scenario">
+          A client declares an exchange and binds it using multiple
+          bindings to the amq.topic exchange. The client then
+          publishes a message to the amq.topic exchange that matches
+          all the bindings.
+        </doc>
+      </rule>
+
+      <chassis name = "server" implement = "MUST"/>
+
+      <response name = "bind-ok"/>
+
+      <!-- Deprecated: "ticket", must be zero -->
+      <field name = "reserved-1" type = "short" reserved = "1"/>
+
+      <field name = "destination" domain = "exchange-name"
+             label = "name of the destination exchange to bind to">
+        <doc>Specifies the name of the destination exchange to bind.</doc>
+        <rule name = "exchange-existence" on-failure = "not-found">
+          <doc>
+            A client MUST NOT be allowed to bind a non-existent
+            destination exchange.
+          </doc>
+          <doc type = "scenario">
+            A client attempts to bind an undeclared exchange to an
+            exchange.
+          </doc>
+        </rule>
+        <rule name = "default-exchange">
+          <doc>
+            The server MUST accept a blank exchange name to mean the
+            default exchange.
+          </doc>
+          <doc type = "scenario">
+            The client declares an exchange and binds a blank exchange
+            name to it.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "source" domain = "exchange-name"
+             label = "name of the source exchange to bind to">
+        <doc>Specifies the name of the source exchange to bind.</doc>
+        <rule name = "exchange-existence" on-failure = "not-found">
+          <doc>
+            A client MUST NOT be allowed to bind a non-existent source
+            exchange.
+          </doc>
+          <doc type = "scenario">
+            A client attempts to bind an exchange to an undeclared
+            exchange.
+          </doc>
+        </rule>
+        <rule name = "default-exchange">
+          <doc>
+            The server MUST accept a blank exchange name to mean the
+            default exchange.
+          </doc>
+          <doc type = "scenario">
+            The client declares an exchange and binds it to a blank
+            exchange name.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "routing-key" domain = "shortstr"
+             label = "message routing key">
+        <doc>
+          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 routing key - refer
+          to the specific exchange documentation.
+        </doc>
+      </field>
+
+      <field name = "no-wait" domain = "no-wait"/>
+
+      <field name = "arguments" domain = "table"
+             label = "arguments for binding">
+        <doc>
+          A set of arguments for the binding. The syntax and semantics
+          of these arguments depends on the exchange class.
+        </doc>
+      </field>
+    </method>
+
+    <method name="bind-ok" synchronous="1" index="31"
+            label = "confirm bind successful">
+      <doc>This method confirms that the bind was successful.</doc>
+
+      <chassis name="client" implement="MUST"/>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "unbind" synchronous = "1" index = "40"
+            label = "unbind an exchange from an exchange">
+      <doc>This method unbinds an exchange from an exchange.</doc>
+      <rule name = "01">
+        <doc>If a unbind fails, the server MUST raise a connection exception.</doc>
+      </rule>
+      <chassis name = "server" implement = "MUST"/>
+      <response name = "unbind-ok"/>
+
+      <!-- Deprecated: "ticket", must be zero -->
+      <field name = "reserved-1" type = "short" reserved = "1"/>
+
+      <field name = "destination" domain = "exchange-name">
+        <doc>Specifies the name of the destination exchange to unbind.</doc>
+        <rule name = "must-exist" on-failure = "not-found">
+          <doc>
+            The client MUST NOT attempt to unbind an exchange that
+            does not exist from an exchange.
+          </doc>
+          <doc type = "scenario">
+            The client attempts to unbind a non-existent exchange from
+            an exchange.
+          </doc>
+        </rule>
+        <rule name = "default-exchange">
+          <doc>
+            The server MUST accept a blank exchange name to mean the
+            default exchange.
+          </doc>
+          <doc type = "scenario">
+            The client declares an exchange, binds a blank exchange
+            name to it, and then unbinds a blank exchange name from
+            it.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "source" domain = "exchange-name">
+        <doc>Specifies the name of the source exchange to unbind.</doc>
+        <rule name = "must-exist" on-failure = "not-found">
+          <doc>
+            The client MUST NOT attempt to unbind an exchange from an
+            exchange that does not exist.
+          </doc>
+          <doc type = "scenario">
+            The client attempts to unbind an exchange from a
+            non-existent exchange.
+          </doc>
+        </rule>
+        <rule name = "default-exchange">
+          <doc>
+            The server MUST accept a blank exchange name to mean the
+            default exchange.
+          </doc>
+          <doc type = "scenario">
+            The client declares an exchange, binds an exchange to a
+            blank exchange name, and then unbinds an exchange from a
+            black exchange name.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "routing-key" domain = "shortstr"
+             label = "routing key of binding">
+        <doc>Specifies the routing key of the binding to unbind.</doc>
+      </field>
+
+      <field name = "no-wait" domain = "no-wait"/>
+
+      <field name = "arguments" domain = "table"
+             label = "arguments of binding">
+        <doc>Specifies the arguments of the binding to unbind.</doc>
+      </field>
+    </method>
+
+    <method name = "unbind-ok" synchronous = "1" index = "51"
+            label = "confirm unbind successful">
+      <doc>This method confirms that the unbind was successful.</doc>
+      <chassis name = "client" implement = "MUST"/>
+    </method>
+
+  </class>
+
+  <!-- ==  QUEUE  ============================================================ -->
+
+  <class name = "queue" handler = "channel" index = "50" label = "work with queues">
+    <doc>
+      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
+      from publishers.
+    </doc>
+
+    <doc type = "grammar">
+      queue               = C:DECLARE  S:DECLARE-OK
+                          / C:BIND     S:BIND-OK
+                          / C:UNBIND   S:UNBIND-OK
+                          / C:PURGE    S:PURGE-OK
+                          / C:DELETE   S:DELETE-OK
+    </doc>
+
+    <chassis name = "server" implement = "MUST" />
+    <chassis name = "client" implement = "MUST" />
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "declare" synchronous = "1" index = "10" label = "declare queue, create if needed">
+      <doc>
+        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
+        contents, and the level of sharing for the queue.
+      </doc>
+
+      <rule name = "default-binding">
+        <doc>
+          The server MUST create a default binding for a newly-declared queue to the
+          default exchange, which is an exchange of type 'direct' and use the queue
+          name as the routing key.
+        </doc>
+        <doc type = "scenario">
+          Client declares a new queue, and then without explicitly binding it to an
+          exchange, attempts to send a message through the default exchange binding,
+          i.e. publish a message to the empty exchange, with the queue name as routing
+          key.
+        </doc>
+      </rule>
+
+      <rule name = "minimum-queues">
+        <doc>
+          The server SHOULD support a minimum of 256 queues per virtual host and ideally,
+          impose no limit except as defined by available resources.
+        </doc>
+        <doc type = "scenario">
+          Client attempts to declare as many queues as it can until the server reports
+          an error.  The resulting count must at least be 256.
+        </doc>
+      </rule>
+
+      <chassis name = "server" implement = "MUST" />
+      <response name = "declare-ok" />
+
+      <!-- Deprecated: "ticket", must be zero -->
+      <field name = "reserved-1" type = "short" reserved = "1" />
+
+      <field name = "queue" domain = "queue-name">
+        <rule name = "default-name">
+          <doc>
+            The queue name MAY be empty, in which case the server MUST create a new
+            queue with a unique generated name and return this to the client in the
+            Declare-Ok method.
+          </doc>
+          <doc type = "scenario">
+            Client attempts to declare several queues with an empty name. The client then
+            verifies that the server-assigned names are unique and different.
+          </doc>
+        </rule>
+        <rule name = "reserved" on-failure = "access-refused">
+          <doc>
+            Queue names starting with "amq." are reserved for pre-declared and
+            standardised queues. The client MAY declare a queue starting with
+            "amq." if the passive option is set, or the queue already exists.
+          </doc>
+          <doc type = "scenario">
+            The client attempts to declare a non-existing queue starting with
+            "amq." and with the passive option set to zero.
+          </doc>
+        </rule>
+        <rule name = "syntax" on-failure = "precondition-failed">
+          <doc>
+            The queue name can be empty, or a sequence of these characters:
+            letters, digits, hyphen, underscore, period, or colon.
+          </doc>
+          <doc type = "scenario">
+            The client attempts to declare a queue with an illegal name.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "passive" domain = "bit" label = "do not create queue">
+        <doc>
+          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
+          use this to check whether a queue exists without modifying the
+          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.
+          Arguments are compared for semantic equivalence.
+        </doc>
+        <rule name = "passive" on-failure = "not-found">
+          <doc>
+            The client MAY ask the server to assert that a queue exists without
+            creating the queue if not.  If the queue does not exist, the server
+            treats this as a failure.
+          </doc>
+          <doc type = "scenario">
+            Client declares an existing queue with the passive option and expects
+            the server to respond with a declare-ok. Client then attempts to declare
+            a non-existent queue with the passive option, and the server must close
+            the channel with the correct reply-code.
+          </doc>
+        </rule>
+        <rule name = "equivalent">
+          <doc>
+            If not set and the queue exists, the server MUST check that the
+            existing queue has the same values for durable, exclusive, auto-delete,
+            and arguments fields.  The server MUST respond with Declare-Ok if the
+            requested queue matches these fields, and MUST raise a channel exception
+            if not.
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "durable" domain = "bit" label = "request a durable queue">
+        <doc>
+          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) 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
+          persistent messages to a transient queue.
+        </doc>
+
+        <rule name = "persistence">
+          <doc>The server MUST recreate the durable queue after a restart.</doc>
+
+          <doc type = "scenario">
+            Client declares a durable queue. The server is then restarted. The client
+            then attempts to send a message to the queue. The message should be successfully
+            delivered.
+          </doc>
+        </rule>
+
+        <rule name = "types">
+          <doc>The server MUST support both durable and transient queues.</doc>
+          <doc type = "scenario">
+            A client declares two named queues, one durable and one transient.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "exclusive" domain = "bit" label = "request an exclusive queue">
+        <doc>
+          Exclusive queues may only be accessed by the current connection, and are
+          deleted when that connection closes.  Passive declaration of an exclusive
+          queue by other connections are not allowed.
+        </doc>
+
+        <rule name = "types">
+          <doc>
+            The server MUST support both exclusive (private) and non-exclusive (shared)
+            queues.
+          </doc>
+          <doc type = "scenario">
+            A client declares two named queues, one exclusive and one non-exclusive.
+          </doc>
+        </rule>
+
+        <rule name = "exclusive" on-failure = "resource-locked">
+          <doc>
+            The client MAY NOT attempt to use a queue that was declared as exclusive
+            by another still-open connection.
+          </doc>
+          <doc type = "scenario">
+            One client declares an exclusive queue. A second client on a different
+            connection attempts to declare, bind, consume, purge, delete, or declare
+            a queue of the same name.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "auto-delete" domain = "bit" label = "auto-delete queue when unused">
+        <doc>
+          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
+          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.
+        </doc>
+
+        <rule name = "pre-existence">
+          <doc>
+            The server MUST ignore the auto-delete field if the queue already exists.
+          </doc>
+          <doc type = "scenario">
+            Client declares two named queues, one as auto-delete and one explicit-delete.
+            Client then attempts to declare the two queues using the same names again,
+            but reversing the value of the auto-delete field in each case. Verify that the
+            queues still exist with the original auto-delete flag values.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "no-wait" domain = "no-wait" />
+
+      <field name = "arguments" domain = "table" label = "arguments for declaration">
+        <doc>
+          A set of arguments for the declaration. The syntax and semantics of these
+          arguments depends on the server implementation.
+        </doc>
+      </field>
+    </method>
+
+    <method name = "declare-ok" synchronous = "1" index = "11" label = "confirms a queue definition">
+      <doc>
+        This method confirms a Declare method and confirms the name of the queue, essential
+        for automatically-named queues.
+      </doc>
+
+      <chassis name = "client" implement = "MUST" />
+
+      <field name = "queue" domain = "queue-name">
+        <doc>
+          Reports the name of the queue. If the server generated a queue name, this field
+          contains that name.
+        </doc>
+        <assert check = "notnull" />
+      </field>
+
+      <field name = "message-count" domain = "message-count" />
+
+      <field name = "consumer-count" domain = "long" label = "number of consumers">
+        <doc>
+          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.
+        </doc>
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "bind" synchronous = "1" index = "20" label = "bind queue to an exchange">
+      <doc>
+        This method binds a queue to an exchange. Until a queue is bound it will not
+        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
+        exchange.
+      </doc>
+
+      <rule name = "duplicates">
+        <doc>
+          A server MUST allow ignore duplicate bindings - that is, two or more bind
+          methods for a specific queue, with identical arguments - without treating these
+          as an error.
+        </doc>
+        <doc type = "scenario">
+          A client binds a named queue to an exchange. The client then repeats the bind
+          (with identical arguments).
+        </doc>
+      </rule>
+
+      <rule name = "unique">
+        <doc>
+          A server MUST not deliver the same message more than once to a queue, even if
+          the queue has multiple bindings that match the message.
+        </doc>
+        <doc type = "scenario">
+          A client declares a named queue and binds it using multiple bindings to the
+          amq.topic exchange. The client then publishes a message that matches all its
+          bindings.
+        </doc>
+      </rule>
+
+      <rule name = "transient-exchange">
+        <doc>
+          The server MUST allow a durable queue to bind to a transient exchange.
+        </doc>
+        <doc type = "scenario">
+          A client declares a transient exchange. The client then declares a named durable
+          queue and then attempts to bind the transient exchange to the durable queue.
+        </doc>
+      </rule>
+
+      <rule name = "durable-exchange">
+        <doc>
+          Bindings of durable queues to durable exchanges are automatically durable
+          and the server MUST restore such bindings after a server restart.
+        </doc>
+        <doc type = "scenario">
+          A server declares a named durable queue and binds it to a durable exchange. The
+          server is restarted. The client then attempts to use the queue/exchange combination.
+        </doc>
+      </rule>
+
+      <rule name = "binding-count">
+        <doc>
+          The server SHOULD support at least 4 bindings per queue, and ideally, impose no
+          limit except as defined by available resources.
+        </doc>
+        <doc type = "scenario">
+          A client declares a named queue and attempts to bind it to 4 different
+          exchanges.
+        </doc>
+      </rule>
+
+      <chassis name = "server" implement = "MUST" />
+
+      <response name = "bind-ok" />
+
+      <!-- Deprecated: "ticket", must be zero -->
+      <field name = "reserved-1" type = "short" reserved = "1" />
+
+      <field name = "queue" domain = "queue-name">
+        <doc>Specifies the name of the queue to bind.</doc>
+        <rule name = "queue-known" on-failure = "not-found">
+          <doc>
+            The client MUST either specify a queue name or have previously declared a
+            queue on the same channel
+          </doc>
+          <doc type = "scenario">
+            The client opens a channel and attempts to bind an unnamed queue.
+          </doc>
+        </rule>
+        <rule name = "must-exist" on-failure = "not-found">
+          <doc>
+            The client MUST NOT attempt to bind a queue that does not exist.
+          </doc>
+          <doc type = "scenario">
+            The client attempts to bind a non-existent queue.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "exchange" domain = "exchange-name" label = "name of the exchange to bind to">
+        <rule name = "exchange-existence" on-failure = "not-found">
+          <doc>
+            A client MUST NOT be allowed to bind a queue to a non-existent exchange.
+          </doc>
+          <doc type = "scenario">
+            A client attempts to bind an named queue to a undeclared exchange.
+          </doc>
+        </rule>
+        <rule name = "default-exchange">
+          <doc>
+            The server MUST accept a blank exchange name to mean the default exchange.
+          </doc>
+          <doc type = "scenario">
+            The client declares a queue and binds it to a blank exchange name.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "routing-key" domain = "shortstr" label = "message routing key">
+        <doc>
+          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
+          routing key - refer to the specific exchange documentation.  If the queue name
+          is empty, the server uses the last queue declared on the channel.  If the
+          routing key is also empty, the server uses this queue name for the routing
+          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
+          routing keys depends on the exchange implementation.
+        </doc>
+        <rule name = "direct-exchange-key-matching">
+          <doc>
+            If a message queue binds to a direct exchange using routing key K and a
+            publisher sends the exchange a message with routing key R, then the message
+            MUST be passed to the message queue if K = R.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "no-wait" domain = "no-wait" />
+
+      <field name = "arguments" domain = "table" label = "arguments for binding">
+        <doc>
+          A set of arguments for the binding. The syntax and semantics of these arguments
+          depends on the exchange class.
+        </doc>
+      </field>
+    </method>
+
+    <method name = "bind-ok" synchronous = "1" index = "21" label = "confirm bind successful">
+      <doc>This method confirms that the bind was successful.</doc>
+
+      <chassis name = "client" implement = "MUST" />
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "unbind" synchronous = "1" index = "50" label = "unbind a queue from an exchange">
+      <doc>This method unbinds a queue from an exchange.</doc>
+      <rule name = "01">
+        <doc>If a unbind fails, the server MUST raise a connection exception.</doc>
+      </rule>
+      <chassis name="server" implement="MUST"/>
+      <response name="unbind-ok"/>
+
+      <!-- Deprecated: "ticket", must be zero -->
+      <field name = "reserved-1" type = "short" reserved = "1" />
+
+      <field name = "queue" domain = "queue-name">
+        <doc>Specifies the name of the queue to unbind.</doc>
+        <rule name = "queue-known" on-failure = "not-found">
+          <doc>
+            The client MUST either specify a queue name or have previously declared a
+            queue on the same channel
+          </doc>
+          <doc type = "scenario">
+            The client opens a channel and attempts to unbind an unnamed queue.
+          </doc>
+        </rule>
+        <rule name = "must-exist" on-failure = "not-found">
+          <doc>
+            The client MUST NOT attempt to unbind a queue that does not exist.
+          </doc>
+          <doc type = "scenario">
+            The client attempts to unbind a non-existent queue.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "exchange" domain = "exchange-name">
+        <doc>The name of the exchange to unbind from.</doc>
+        <rule name = "must-exist" on-failure = "not-found">
+          <doc>
+            The client MUST NOT attempt to unbind a queue from an exchange that
+            does not exist.
+          </doc>
+          <doc type = "scenario">
+            The client attempts to unbind a queue from a non-existent exchange.
+          </doc>
+        </rule>
+        <rule name = "default-exchange">
+          <doc>
+            The server MUST accept a blank exchange name to mean the default exchange.
+          </doc>
+          <doc type = "scenario">
+            The client declares a queue and binds it to a blank exchange name.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "routing-key" domain = "shortstr" label = "routing key of binding">
+        <doc>Specifies the routing key of the binding to unbind.</doc>
+      </field>
+
+      <field name = "arguments" domain = "table" label = "arguments of binding">
+        <doc>Specifies the arguments of the binding to unbind.</doc>
+      </field>
+    </method>
+
+    <method name = "unbind-ok" synchronous = "1" index = "51" label = "confirm unbind successful">
+      <doc>This method confirms that the unbind was successful.</doc>
+      <chassis name = "client" implement = "MUST"/>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "purge" synchronous = "1" index = "30" label = "purge a queue">
+      <doc>
+        This method removes all messages from a queue which are not awaiting
+        acknowledgment.
+      </doc>
+
+      <rule name = "02">
+        <doc>
+          The server MUST NOT purge messages that have already been sent to a client
+          but not yet acknowledged.
+        </doc>
+      </rule>
+
+      <rule name = "03">
+        <doc>
+          The server MAY implement a purge queue or log that allows system administrators
+          to recover accidentally-purged messages. The server SHOULD NOT keep purged
+          messages in the same storage spaces as the live messages since the volumes of
+          purged messages may get very large.
+        </doc>
+      </rule>
+
+      <chassis name = "server" implement = "MUST" />
+
+      <response name = "purge-ok" />
+
+      <!-- Deprecated: "ticket", must be zero -->
+      <field name = "reserved-1" type = "short" reserved = "1" />
+
+      <field name = "queue" domain = "queue-name">
+        <doc>Specifies the name of the queue to purge.</doc>
+        <rule name = "queue-known" on-failure = "not-found">
+          <doc>
+            The client MUST either specify a queue name or have previously declared a
+            queue on the same channel
+          </doc>
+          <doc type = "scenario">
+            The client opens a channel and attempts to purge an unnamed queue.
+          </doc>
+        </rule>
+        <rule name = "must-exist" on-failure = "not-found">
+          <doc>
+            The client MUST NOT attempt to purge a queue that does not exist.
+          </doc>
+          <doc type = "scenario">
+            The client attempts to purge a non-existent queue.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "no-wait" domain = "no-wait" />
+    </method>
+
+    <method name = "purge-ok" synchronous = "1" index = "31" label = "confirms a queue purge">
+      <doc>This method confirms the purge of a queue.</doc>
+
+      <chassis name = "client" implement = "MUST" />
+
+      <field name = "message-count" domain = "message-count">
+        <doc>
+          Reports the number of messages purged.
+        </doc>
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "delete" synchronous = "1" index = "40" label = "delete a queue">
+      <doc>
+        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
+        consumers on the queue are cancelled.
+      </doc>
+
+      <rule name = "01">
+        <doc>
+          The server SHOULD use a dead-letter queue to hold messages that were pending on
+          a deleted queue, and MAY provide facilities for a system administrator to move
+          these messages back to an active queue.
+        </doc>
+      </rule>
+
+      <chassis name = "server" implement = "MUST" />
+
+      <response name = "delete-ok" />
+
+      <!-- Deprecated: "ticket", must be zero -->
+      <field name = "reserved-1" type = "short" reserved = "1" />
+
+      <field name = "queue" domain = "queue-name">
+        <doc>Specifies the name of the queue to delete.</doc>
+        <rule name = "queue-known" on-failure = "not-found">
+          <doc>
+            The client MUST either specify a queue name or have previously declared a
+            queue on the same channel
+          </doc>
+          <doc type = "scenario">
+            The client opens a channel and attempts to delete an unnamed queue.
+          </doc>
+        </rule>
+        <rule name = "must-exist" on-failure = "not-found">
+          <doc>
+            The client MUST NOT attempt to delete a queue that does not exist.
+          </doc>
+          <doc type = "scenario">
+            The client attempts to delete a non-existent queue.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "if-unused" domain = "bit" label = "delete only if unused">
+        <doc>
+          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
+          exception instead.
+        </doc>
+        <rule name = "in-use" on-failure = "precondition-failed">
+          <doc>
+            The server MUST NOT delete a queue that has consumers on it, if the if-unused
+            field is true.
+          </doc>
+          <doc type = "scenario">
+            The client declares a queue, and consumes from it, then tries to delete it
+            setting if-unused to true.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "if-empty" domain = "bit" label = "delete only if empty">
+        <doc>
+          If set, the server will only delete the queue if it has no messages.
+        </doc>
+        <rule name = "not-empty" on-failure = "precondition-failed">
+          <doc>
+            The server MUST NOT delete a queue that has messages on it, if the
+            if-empty field is true.
+          </doc>
+          <doc type = "scenario">
+            The client declares a queue, binds it and publishes some messages into it,
+            then tries to delete it setting if-empty to true.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "no-wait" domain = "no-wait" />
+    </method>
+
+    <method name = "delete-ok" synchronous = "1" index = "41" label = "confirm deletion of a queue">
+      <doc>This method confirms the deletion of a queue.</doc>
+
+      <chassis name = "client" implement = "MUST" />
+
+      <field name = "message-count" domain = "message-count">
+        <doc>Reports the number of messages deleted.</doc>
+      </field>
+    </method>
+  </class>
+
+  <!-- ==  BASIC  ============================================================ -->
+
+  <class name = "basic" handler = "channel" index = "60" label = "work with basic content">
+    <doc>
+      The Basic class provides methods that support an industry-standard messaging model.
+    </doc>
+
+    <doc type = "grammar">
+      basic               = C:QOS S:QOS-OK
+                          / C:CONSUME S:CONSUME-OK
+                          / C:CANCEL S:CANCEL-OK
+                          / C:PUBLISH content
+                          / S:RETURN content
+                          / S:DELIVER content
+                          / C:GET ( S:GET-OK content / S:GET-EMPTY )
+                          / C:ACK
+                          / S:ACK
+                          / C:REJECT
+                          / C:NACK
+                          / S:NACK
+                          / C:RECOVER-ASYNC
+                          / C:RECOVER S:RECOVER-OK
+    </doc>
+
+    <chassis name = "server" implement = "MUST" />
+    <chassis name = "client" implement = "MAY" />
+
+    <rule name = "01">
+      <doc>
+        The server SHOULD respect the persistent property of basic messages and
+        SHOULD make a best-effort to hold persistent basic messages on a reliable
+        storage mechanism.
+      </doc>
+      <doc type = "scenario">
+        Send a persistent message to queue, stop server, restart server and then
+        verify whether message is still present.  Assumes that queues are durable.
+        Persistence without durable queues makes no sense.
+      </doc>
+    </rule>
+
+    <rule name = "02">
+      <doc>
+        The server MUST NOT discard a persistent basic message in case of a queue
+        overflow.
+      </doc>
+      <doc type = "scenario">
+        Declare a queue overflow situation with persistent messages and verify that
+        messages do not get lost (presumably the server will write them to disk).
+      </doc>
+    </rule>
+
+    <rule name = "03">
+      <doc>
+        The server MAY use the Channel.Flow method to slow or stop a basic message
+        publisher when necessary.
+      </doc>
+      <doc type = "scenario">
+        Declare a queue overflow situation with non-persistent messages and verify
+        whether the server responds with Channel.Flow or not. Repeat with persistent
+        messages.
+      </doc>
+    </rule>
+
+    <rule name = "04">
+      <doc>
+        The server MAY overflow non-persistent basic messages to persistent
+        storage.
+      </doc>
+      <!-- Test scenario: untestable -->
+    </rule>
+
+    <rule name = "05">
+      <doc>
+        The server MAY discard or dead-letter non-persistent basic messages on a
+        priority basis if the queue size exceeds some configured limit.
+      </doc>
+      <!-- Test scenario: untestable -->
+    </rule>
+
+    <rule name = "06">
+      <doc>
+        The server MUST implement at least 2 priority levels for basic messages,
+        where priorities 0-4 and 5-9 are treated as two distinct levels.
+      </doc>
+      <doc type = "scenario">
+        Send a number of priority 0 messages to a queue. Send one priority 9
+        message.  Consume messages from the queue and verify that the first message
+        received was priority 9.
+      </doc>
+    </rule>
+
+    <rule name = "07">
+      <doc>
+        The server MAY implement up to 10 priority levels.
+      </doc>
+      <doc type = "scenario">
+        Send a number of messages with mixed priorities to a queue, so that all
+        priority values from 0 to 9 are exercised. A good scenario would be ten
+        messages in low-to-high priority.  Consume from queue and verify how many
+        priority levels emerge.
+      </doc>
+    </rule>
+
+    <rule name = "08">
+      <doc>
+        The server MUST deliver messages of the same priority in order irrespective of
+        their individual persistence.
+      </doc>
+      <doc type = "scenario">
+        Send a set of messages with the same priority but different persistence
+        settings to a queue.  Consume and verify that messages arrive in same order
+        as originally published.
+      </doc>
+    </rule>
+
+    <rule name = "09">
+      <doc>
+        The server MUST support un-acknowledged delivery of Basic content, i.e.
+        consumers with the no-ack field set to TRUE.
+      </doc>
+    </rule>
+
+    <rule name = "10">
+      <doc>
+        The server MUST support explicitly acknowledged delivery of Basic content,
+        i.e. consumers with the no-ack field set to FALSE.
+      </doc>
+      <doc type = "scenario">
+        Declare a queue and a consumer using explicit acknowledgements.  Publish a
+        set of messages to the queue.  Consume the messages but acknowledge only
+        half of them.  Disconnect and reconnect, and consume from the queue.
+        Verify that the remaining messages are received.
+      </doc>
+    </rule>
+
+    <!--  These are the properties for a Basic content  -->
+
+    <!--  MIME typing -->
+    <field name = "content-type"    domain = "shortstr"   label = "MIME content type" />
+    <!--  MIME typing -->
+    <field name = "content-encoding" domain = "shortstr"  label = "MIME content encoding" />
+    <!--  For applications, and for header exchange routing -->
+    <field name = "headers"         domain = "table"      label = "message header field table" />
+    <!--  For queues that implement persistence -->
+    <field name = "delivery-mode"   domain = "octet"      label = "non-persistent (1) or persistent (2)" />
+    <!--  For queues that implement priorities -->
+    <field name = "priority"        domain = "octet"      label = "message priority, 0 to 9" />
+    <!--  For application use, no formal behaviour -->
+    <field name = "correlation-id"  domain = "shortstr"   label = "application correlation identifier" />
+    <!--  For application use, no formal behaviour but may hold the
+          name of a private response queue, when used in request messages -->
+    <field name = "reply-to"        domain = "shortstr"   label = "address to reply to" />
+    <!--  For implementation use, no formal behaviour -->
+    <field name = "expiration"      domain = "shortstr"   label = "message expiration specification" />
+    <!--  For application use, no formal behaviour -->
+    <field name = "message-id"      domain = "shortstr"   label = "application message identifier" />
+    <!--  For application use, no formal behaviour -->
+    <field name = "timestamp"       domain = "timestamp"  label = "message timestamp" />
+    <!--  For application use, no formal behaviour -->
+    <field name = "type"            domain = "shortstr"   label = "message type name" />
+    <!--  For application use, no formal behaviour -->
+    <field name = "user-id"         domain = "shortstr"   label = "creating user id" />
+    <!--  For application use, no formal behaviour -->
+    <field name = "app-id"          domain = "shortstr"   label = "creating application id" />
+    <!--  Deprecated, was old cluster-id property -->
+    <field name = "reserved"        domain = "shortstr"   label = "reserved, must be empty" />
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "qos" synchronous = "1" index = "10" label = "specify quality of service">
+      <doc>
+        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
+        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
+        for the server.
+      </doc>
+
+      <chassis name = "server" implement = "MUST" />
+      <response name = "qos-ok" />
+
+      <field name = "prefetch-size" domain = "long" label = "prefetch window in octets">
+        <doc>
+          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,
+          rather than needing to be sent down the channel. Prefetching gives a performance
+          improvement. This field specifies the prefetch window size in octets. The server
+          will send a message in advance if it is equal to or smaller in size than the
+          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
+          apply. The prefetch-size is ignored if the no-ack option is set.
+        </doc>
+        <rule name = "01">
+          <doc>
+            The server MUST ignore this setting when the client is not processing any
+            messages - i.e. the prefetch size does not limit the transfer of single
+            messages to a client, only the sending in advance of more messages while
+            the client still has one or more unacknowledged messages.
+          </doc>
+          <doc type = "scenario">
+            Define a QoS prefetch-size limit and send a single message that exceeds
+            that limit.  Verify that the message arrives correctly.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "prefetch-count" domain = "short" label = "prefetch window in messages">
+        <doc>
+          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
+          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.
+        </doc>
+        <rule name = "01">
+          <doc>
+            The server may send less data in advance than allowed by the client's
+            specified prefetch windows but it MUST NOT send more.
+          </doc>
+          <doc type = "scenario">
+            Define a QoS prefetch-size limit and a prefetch-count limit greater than
+            one.  Send multiple messages that exceed the prefetch size.  Verify that
+            no more than one message arrives at once.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "global" domain = "bit" label = "apply to entire connection">
+        <doc>
+          RabbitMQ has reinterpreted this field. The original
+          specification said: "By default the QoS settings apply to
+          the current channel only. If this field is set, they are
+          applied to the entire connection." Instead, RabbitMQ takes
+          global=false to mean that the QoS settings should apply
+          per-consumer (for new consumers on the channel; existing
+          ones being unaffected) and global=true to mean that the QoS
+          settings should apply per-channel.
+        </doc>
+      </field>
+    </method>
+
+    <method name = "qos-ok" synchronous = "1" index = "11" label = "confirm the requested qos">
+      <doc>
+        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
+        defined.
+      </doc>
+      <chassis name = "client" implement = "MUST" />
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "consume" synchronous = "1" index = "20" label = "start a queue consumer">
+      <doc>
+        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
+        declared on, or until the client cancels them.
+      </doc>
+
+      <rule name = "01">
+        <doc>
+          The server SHOULD support at least 16 consumers per queue, and ideally, impose
+          no limit except as defined by available resources.
+        </doc>
+        <doc type = "scenario">
+          Declare a queue and create consumers on that queue until the server closes the
+          connection. Verify that the number of consumers created was at least sixteen
+          and report the total number.
+        </doc>
+      </rule>
+
+      <chassis name = "server" implement = "MUST" />
+      <response name = "consume-ok" />
+
+      <!-- Deprecated: "ticket", must be zero -->
+      <field name = "reserved-1" type = "short" reserved = "1" />
+
+      <field name = "queue" domain = "queue-name">
+        <doc>Specifies the name of the queue to consume from.</doc>
+      </field>
+
+      <field name = "consumer-tag" domain = "consumer-tag">
+        <doc>
+          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
+          empty the server will generate a unique tag.
+        </doc>
+        <rule name = "01" on-failure = "not-allowed">
+          <doc>
+            The client MUST NOT specify a tag that refers to an existing consumer.
+          </doc>
+          <doc type = "scenario">
+            Attempt to create two consumers with the same non-empty tag, on the
+            same channel.
+          </doc>
+        </rule>
+        <rule name = "02" on-failure = "not-allowed">
+          <doc>
+            The consumer tag is valid only within the channel from which the
+            consumer was created. I.e. a client MUST NOT create a consumer in one
+            channel and then use it in another.
+          </doc>
+          <doc type = "scenario">
+            Attempt to create a consumer in one channel, then use in another channel,
+            in which consumers have also been created (to test that the server uses
+            unique consumer tags).
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "no-local" domain = "no-local" />
+
+      <field name = "no-ack" domain = "no-ack" />
+
+      <field name = "exclusive" domain = "bit" label = "request exclusive access">
+        <doc>
+          Request exclusive consumer access, meaning only this consumer can access the
+          queue.
+        </doc>
+
+        <rule name = "01" on-failure = "access-refused">
+          <doc>
+            The client MAY NOT gain exclusive access to a queue that already has
+            active consumers.
+          </doc>
+          <doc type = "scenario">
+            Open two connections to a server, and in one connection declare a shared
+            (non-exclusive) queue and then consume from the queue.  In the second
+            connection attempt to consume from the same queue using the exclusive
+            option.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "no-wait" domain = "no-wait" />
+
+      <field name = "arguments" domain = "table" label = "arguments for declaration">
+        <doc>
+          A set of arguments for the consume. The syntax and semantics of these
+          arguments depends on the server implementation.
+        </doc>
+      </field>
+    </method>
+
+    <method name = "consume-ok" synchronous = "1" index = "21" label = "confirm a new consumer">
+      <doc>
+        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.
+      </doc>
+      <chassis name = "client" implement = "MUST" />
+      <field name = "consumer-tag" domain = "consumer-tag">
+        <doc>
+          Holds the consumer tag specified by the client or provided by the server.
+        </doc>
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "cancel" synchronous = "1" index = "30" label = "end a queue consumer">
+      <doc>
+        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
+        that consumer. The client may receive an arbitrary number of messages in
+        between sending the cancel method and receiving the cancel-ok reply.
+
+        It may also be sent from the server to the client in the event
+        of the consumer being unexpectedly cancelled (i.e. cancelled
+        for any reason other than the server receiving the
+        corresponding basic.cancel from the client). This allows
+        clients to be notified of the loss of consumers due to events
+        such as queue deletion. Note that as it is not a MUST for
+        clients to accept this method from the server, it is advisable
+        for the broker to be able to identify those clients that are
+        capable of accepting the method, through some means of
+        capability negotiation.
+      </doc>
+
+      <rule name = "01">
+        <doc>
+          If the queue does not exist the server MUST ignore the cancel method, so
+          long as the consumer tag is valid for that channel.
+        </doc>
+        <doc type = "scenario">
+          TODO.
+        </doc>
+      </rule>
+
+      <chassis name = "server" implement = "MUST" />
+      <chassis name = "client" implement = "SHOULD" />
+      <response name = "cancel-ok" />
+
+      <field name = "consumer-tag" domain = "consumer-tag" />
+      <field name = "no-wait" domain = "no-wait" />
+    </method>
+
+    <method name = "cancel-ok" synchronous = "1" index = "31" label = "confirm a cancelled consumer">
+      <doc>
+        This method confirms that the cancellation was completed.
+      </doc>
+      <chassis name = "client" implement = "MUST" />
+      <chassis name = "server" implement = "MAY" />
+      <field name = "consumer-tag" domain = "consumer-tag" />
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "publish" content = "1" index = "40" label = "publish a message">
+      <doc>
+        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
+        consumers when the transaction, if any, is committed.
+      </doc>
+
+      <chassis name = "server" implement = "MUST" />
+
+      <!-- Deprecated: "ticket", must be zero -->
+      <field name = "reserved-1" type = "short" reserved = "1" />
+
+      <field name = "exchange" domain = "exchange-name">
+        <doc>
+          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
+          exchange does not exist, the server will raise a channel exception.
+        </doc>
+
+        <rule name = "must-exist" on-failure = "not-found">
+          <doc>
+            The client MUST NOT attempt to publish a content to an exchange that
+            does not exist.
+          </doc>
+          <doc type = "scenario">
+            The client attempts to publish a content to a non-existent exchange.
+          </doc>
+        </rule>
+        <rule name = "default-exchange">
+          <doc>
+            The server MUST accept a blank exchange name to mean the default exchange.
+          </doc>
+          <doc type = "scenario">
+            The client declares a queue and binds it to a blank exchange name.
+          </doc>
+        </rule>
+        <rule name = "02">
+          <doc>
+            If the exchange was declared as an internal exchange, the server MUST raise
+            a channel exception with a reply code 403 (access refused).
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+
+        <rule name = "03">
+          <doc>
+            The exchange MAY refuse basic content in which case it MUST raise a channel
+            exception with reply code 540 (not implemented).
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "routing-key" domain = "shortstr" label = "Message routing key">
+        <doc>
+          Specifies the routing key for the message. The routing key is used for routing
+          messages depending on the exchange configuration.
+        </doc>
+      </field>
+
+      <field name = "mandatory" domain = "bit" label = "indicate mandatory routing">
+        <doc>
+          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
+          Return method. If this flag is zero, the server silently drops the message.
+        </doc>
+
+        <rule name = "01">
+          <doc>
+            The server SHOULD implement the mandatory flag.
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "immediate" domain = "bit" label = "request immediate delivery">
+        <doc>
+          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
+          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.
+        </doc>
+
+        <rule name = "01">
+          <doc>
+            The server SHOULD implement the immediate flag.
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+      </field>
+    </method>
+
+    <method name = "return" content = "1" index = "50" label = "return a failed message">
+      <doc>
+        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
+        reply code and text provide information about the reason that the message was
+        undeliverable.
+      </doc>
+
+      <chassis name = "client" implement = "MUST" />
+
+      <field name = "reply-code" domain = "reply-code" />
+      <field name = "reply-text" domain = "reply-text" />
+
+      <field name = "exchange" domain = "exchange-name">
+        <doc>
+          Specifies the name of the exchange that the message was originally published
+          to.  May be empty, meaning the default exchange.
+        </doc>
+      </field>
+
+      <field name = "routing-key" domain = "shortstr" label = "Message routing key">
+        <doc>
+          Specifies the routing key name specified when the message was published.
+        </doc>
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "deliver" content = "1" index = "60"
+      label = "notify the client of a consumer message">
+      <doc>
+        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
+        the server responds with Deliver methods as and when messages arrive for that
+        consumer.
+      </doc>
+
+      <rule name = "01">
+        <doc>
+          The server SHOULD track the number of times a message has been delivered to
+          clients and when a message is redelivered a certain number of times - e.g. 5
+          times - without being acknowledged, the server SHOULD consider the message to be
+          unprocessable (possibly causing client applications to abort), and move the
+          message to a dead letter queue.
+        </doc>
+        <doc type = "scenario">
+          TODO.
+        </doc>
+      </rule>
+
+      <chassis name = "client" implement = "MUST" />
+
+      <field name = "consumer-tag" domain = "consumer-tag" />
+      <field name = "delivery-tag" domain = "delivery-tag" />
+      <field name = "redelivered" domain = "redelivered" />
+
+      <field name = "exchange" domain = "exchange-name">
+        <doc>
+          Specifies the name of the exchange that the message was originally published to.
+          May be empty, indicating the default exchange.
+        </doc>
+      </field>
+
+      <field name = "routing-key" domain = "shortstr" label = "Message routing key">
+        <doc>Specifies the routing key name specified when the message was published.</doc>
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "get" synchronous = "1" index = "70" label = "direct access to a queue">
+      <doc>
+        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
+        functionality is more important than performance.
+      </doc>
+
+      <response name = "get-ok" />
+      <response name = "get-empty" />
+      <chassis name = "server" implement = "MUST" />
+
+      <!-- Deprecated: "ticket", must be zero -->
+      <field name = "reserved-1" type = "short" reserved = "1" />
+
+      <field name = "queue" domain = "queue-name">
+        <doc>Specifies the name of the queue to get a message from.</doc>
+      </field>
+      <field name = "no-ack" domain = "no-ack" />
+    </method>
+
+    <method name = "get-ok" synchronous = "1" content = "1" index = "71"
+      label = "provide client with a message">
+      <doc>
+        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
+        get method.
+      </doc>
+
+      <chassis name = "client" implement = "MAY" />
+
+      <field name = "delivery-tag" domain = "delivery-tag" />
+      <field name = "redelivered" domain = "redelivered" />
+      <field name = "exchange" domain = "exchange-name">
+        <doc>
+          Specifies the name of the exchange that the message was originally published to.
+          If empty, the message was published to the default exchange.
+        </doc>
+      </field>
+
+      <field name = "routing-key" domain = "shortstr" label = "Message routing key">
+        <doc>Specifies the routing key name specified when the message was published.</doc>
+      </field>
+
+      <field name = "message-count" domain = "message-count" />
+    </method>
+
+    <method name = "get-empty" synchronous = "1" index = "72"
+      label = "indicate no messages available">
+      <doc>
+        This method tells the client that the queue has no messages available for the
+        client.
+      </doc>
+      <chassis name = "client" implement = "MAY" />
+      <!-- Deprecated: "cluster-id", must be empty -->
+      <field name = "reserved-1" type = "shortstr" reserved = "1" />
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "ack" index = "80" label = "acknowledge one or more messages">
+      <doc>
+        When sent by the client, this method acknowledges one or more
+        messages delivered via the Deliver or Get-Ok methods.
+
+        When sent by server, this method acknowledges one or more
+        messages published with the Publish method on a channel in
+        confirm mode.
+
+        The acknowledgement can be for a single message or a set of
+        messages up to and including a specific message.
+      </doc>
+
+      <chassis name = "server" implement = "MUST" />
+      <chassis name ="client" implement = "MUST"/>
+
+      <field name = "delivery-tag" domain = "delivery-tag" />
+      <field name = "multiple" domain = "bit" label = "acknowledge multiple messages">
+        <doc>
+          If set to 1, the delivery tag is treated as "up to and
+          including", so that multiple messages can be acknowledged
+          with a single method. If set to zero, the delivery tag
+          refers to a single message. If the multiple field is 1, and
+          the delivery tag is zero, this indicates acknowledgement of
+          all outstanding messages.
+        </doc>
+        <rule name = "exists" on-failure = "precondition-failed">
+          <doc>
+            A message MUST not be acknowledged more than once.  The
+            receiving peer MUST validate that a non-zero delivery-tag
+            refers to a delivered message, and raise a channel
+            exception if this is not the case. On a transacted
+            channel, this check MUST be done immediately and not
+            delayed until a Tx.Commit.
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "reject" index = "90" label = "reject an incoming message">
+      <doc>
+        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
+        queue.
+      </doc>
+
+      <rule name = "01">
+        <doc>
+          The server SHOULD be capable of accepting and process the Reject method while
+          sending message content with a Deliver or Get-Ok method. I.e. the server should
+          read and process incoming methods while sending output frames. To cancel a
+          partially-send content, the server sends a content body frame of size 1 (i.e.
+          with no data except the frame-end octet).
+        </doc>
+      </rule>
+
+      <rule name = "02">
+        <doc>
+          The server SHOULD interpret this method as meaning that the client is unable to
+          process the message at this time.
+        </doc>
+        <doc type = "scenario">
+          TODO.
+        </doc>
+      </rule>
+
+      <rule name = "03">
+        <doc>
+          The client MUST NOT use this method as a means of selecting messages to process.
+        </doc>
+        <doc type = "scenario">
+          TODO.
+        </doc>
+      </rule>
+
+      <chassis name = "server" implement = "MUST" />
+
+      <field name = "delivery-tag" domain = "delivery-tag" />
+
+      <field name = "requeue" domain = "bit" label = "requeue the message">
+        <doc>
+          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.
+        </doc>
+
+        <rule name = "01">
+          <doc>
+            The server MUST NOT deliver the message to the same client within the
+            context of the current channel. The recommended strategy is to attempt to
+            deliver the message to an alternative consumer, and if that is not possible,
+            to move the message to a dead-letter queue. The server MAY use more
+            sophisticated tracking to hold the message on the queue and redeliver it to
+            the same client at a later stage.
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "recover-async" index = "100" label = "redeliver unacknowledged messages"
+        deprecated = "1">
+      <doc>
+        This method asks the server to redeliver all unacknowledged messages on a
+        specified channel. Zero or more messages may be redelivered.  This method
+        is deprecated in favour of the synchronous Recover/Recover-Ok.
+      </doc>
+      <rule name = "01">
+        <doc>
+          The server MUST set the redelivered flag on all messages that are resent.
+        </doc>
+        <doc type = "scenario">
+          TODO.
+        </doc>
+      </rule>
+      <chassis name = "server" implement = "MAY" />
+      <field name = "requeue" domain = "bit" label = "requeue the message">
+        <doc>
+          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,
+          potentially then delivering it to an alternative subscriber.
+        </doc>
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "recover" index = "110" label = "redeliver unacknowledged messages">
+      <doc>
+        This method asks the server to redeliver all unacknowledged messages on a
+        specified channel. Zero or more messages may be redelivered.  This method
+        replaces the asynchronous Recover.
+      </doc>
+      <rule name = "01">
+        <doc>
+          The server MUST set the redelivered flag on all messages that are resent.
+        </doc>
+        <doc type = "scenario">
+          TODO.
+        </doc>
+      </rule>
+      <chassis name = "server" implement = "MUST" />
+      <field name = "requeue" domain = "bit" label = "requeue the message">
+        <doc>
+          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,
+          potentially then delivering it to an alternative subscriber.
+        </doc>
+      </field>
+    </method>
+
+    <method name = "recover-ok" synchronous = "1" index = "111" label = "confirm recovery">
+      <doc>
+        This method acknowledges a Basic.Recover method.
+      </doc>
+      <chassis name = "client" implement = "MUST" />
+    </method>
+
+    <method name = "nack" index = "120" label = "reject one or more incoming messages">
+      <doc>
+        This method allows a client to reject one or more incoming messages. It can be
+        used to interrupt and cancel large incoming messages, or return untreatable
+        messages to their original queue.
+
+        This method is also used by the server to inform publishers on channels in
+        confirm mode of unhandled messages.  If a publisher receives this method, it
+        probably needs to republish the offending messages.
+      </doc>
+
+      <rule name = "01">
+        <doc>
+          The server SHOULD be capable of accepting and processing the Nack method while
+          sending message content with a Deliver or Get-Ok method. I.e. the server should
+          read and process incoming methods while sending output frames. To cancel a
+          partially-send content, the server sends a content body frame of size 1 (i.e.
+          with no data except the frame-end octet).
+        </doc>
+      </rule>
+
+      <rule name = "02">
+        <doc>
+          The server SHOULD interpret this method as meaning that the client is unable to
+          process the message at this time.
+        </doc>
+        <doc type = "scenario">
+          TODO.
+        </doc>
+      </rule>
+
+      <rule name = "03">
+        <doc>
+          The client MUST NOT use this method as a means of selecting messages to process.
+        </doc>
+        <doc type = "scenario">
+          TODO.
+        </doc>
+      </rule>
+
+      <rule name = "04">
+        <doc>
+          A client publishing messages to a channel in confirm mode SHOULD be capable of accepting
+          and somehow handling the Nack method.
+        </doc>
+        <doc type = "scenario">
+          TODO
+        </doc>
+      </rule>
+
+      <chassis name = "server" implement = "MUST" />
+      <chassis name = "client" implement = "MUST" />
+
+      <field name = "delivery-tag" domain = "delivery-tag" />
+
+      <field name = "multiple" domain = "bit" label = "reject multiple messages">
+        <doc>
+          If set to 1, the delivery tag is treated as "up to and
+          including", so that multiple messages can be rejected
+          with a single method. If set to zero, the delivery tag
+          refers to a single message. If the multiple field is 1, and
+          the delivery tag is zero, this indicates rejection of
+          all outstanding messages.
+        </doc>
+        <rule name = "exists" on-failure = "precondition-failed">
+          <doc>
+            A message MUST not be rejected more than once.  The
+            receiving peer MUST validate that a non-zero delivery-tag
+            refers to an unacknowledged, delivered message, and
+            raise a channel exception if this is not the case.
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "requeue" domain = "bit" label = "requeue the message">
+        <doc>
+          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.
+          Clients receiving the Nack methods should ignore this flag.
+        </doc>
+
+        <rule name = "01">
+          <doc>
+            The server MUST NOT deliver the message to the same client within the
+            context of the current channel. The recommended strategy is to attempt to
+            deliver the message to an alternative consumer, and if that is not possible,
+            to move the message to a dead-letter queue. The server MAY use more
+            sophisticated tracking to hold the message on the queue and redeliver it to
+            the same client at a later stage.
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+      </field>
+    </method>
+
+  </class>
+
+  <!-- ==  TX  =============================================================== -->
+
+  <class name = "tx" handler = "channel" index = "90" label = "work with transactions">
+    <doc>
+      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
+      within a transaction will complete successfully or none of them will.
+      Servers SHOULD implement atomic transactions at least where all publish
+      or ack requests affect a single queue.  Transactions that cover multiple
+      queues may be non-atomic, given that queues can be created and destroyed
+      asynchronously, and such events do not form part of any transaction.
+      Further, the behaviour of transactions with respect to the immediate and
+      mandatory flags on Basic.Publish methods is not defined.
+    </doc>
+
+    <rule name = "not multiple queues">
+      <doc>
+      Applications MUST NOT rely on the atomicity of transactions that
+      affect more than one queue.
+      </doc>
+    </rule>
+    <rule name = "not immediate">
+      <doc>
+      Applications MUST NOT rely on the behaviour of transactions that
+      include messages published with the immediate option.
+      </doc>
+    </rule>
+    <rule name = "not mandatory">
+      <doc>
+      Applications MUST NOT rely on the behaviour of transactions that
+      include messages published with the mandatory option.
+      </doc>
+    </rule>
+
+    <doc type = "grammar">
+      tx                  = C:SELECT S:SELECT-OK
+                          / C:COMMIT S:COMMIT-OK
+                          / C:ROLLBACK S:ROLLBACK-OK
+    </doc>
+
+    <chassis name = "server" implement = "SHOULD" />
+    <chassis name = "client" implement = "MAY" />
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "select" synchronous = "1" index = "10" label = "select standard transaction mode">
+      <doc>
+        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.
+      </doc>
+      <chassis name = "server" implement = "MUST" />
+      <response name = "select-ok" />
+    </method>
+
+    <method name = "select-ok" synchronous = "1" index = "11" label = "confirm transaction mode">
+      <doc>
+        This method confirms to the client that the channel was successfully set to use
+        standard transactions.
+      </doc>
+      <chassis name = "client" implement = "MUST" />
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "commit" synchronous = "1" index = "20" label = "commit the current transaction">
+      <doc>
+        This method commits all message publications and acknowledgments performed in
+        the current transaction.  A new transaction starts immediately after a commit.
+      </doc>
+      <chassis name = "server" implement = "MUST" />
+      <response name = "commit-ok" />
+
+      <rule name = "transacted" on-failure = "precondition-failed">
+        <doc>
+          The client MUST NOT use the Commit method on non-transacted channels.
+        </doc>
+        <doc type = "scenario">
+          The client opens a channel and then uses Tx.Commit.
+        </doc>
+      </rule>
+    </method>
+
+    <method name = "commit-ok" synchronous = "1" index = "21" label = "confirm a successful commit">
+      <doc>
+        This method confirms to the client that the commit succeeded. Note that if a commit
+        fails, the server raises a channel exception.
+      </doc>
+      <chassis name = "client" implement = "MUST" />
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "rollback" synchronous = "1" index = "30"
+      label = "abandon the current transaction">
+      <doc>
+        This method abandons all message publications and acknowledgments performed in
+        the current transaction. A new transaction starts immediately after a rollback.
+        Note that unacked messages will not be automatically redelivered by rollback;
+        if that is required an explicit recover call should be issued.
+      </doc>
+      <chassis name = "server" implement = "MUST" />
+      <response name = "rollback-ok" />
+
+      <rule name = "transacted" on-failure = "precondition-failed">
+        <doc>
+          The client MUST NOT use the Rollback method on non-transacted channels.
+        </doc>
+        <doc type = "scenario">
+          The client opens a channel and then uses Tx.Rollback.
+        </doc>
+      </rule>
+    </method>
+
+    <method name = "rollback-ok" synchronous = "1" index = "31" label = "confirm successful rollback">
+      <doc>
+        This method confirms to the client that the rollback succeeded. Note that if an
+        rollback fails, the server raises a channel exception.
+      </doc>
+      <chassis name = "client" implement = "MUST" />
+    </method>
+  </class>
+
+  <!-- ==  CONFIRM  ========================================================== -->
+
+  <class name = "confirm" handler = "channel" index = "85" label = "work with confirms">
+    <doc>
+      The Confirm class allows publishers to put the channel in
+      confirm mode and subsequently be notified when messages have been
+      handled by the broker.  The intention is that all messages
+      published on a channel in confirm mode will be acknowledged at
+      some point.  By acknowledging a message the broker assumes
+      responsibility for it and indicates that it has done something
+      it deems reasonable with it.
+
+      Unroutable mandatory or immediate messages are acknowledged
+      right after the Basic.Return method. Messages are acknowledged
+      when all queues to which the message has been routed
+      have either delivered the message and received an
+      acknowledgement (if required), or enqueued the message (and
+      persisted it if required).
+
+      Published messages are assigned ascending sequence numbers,
+      starting at 1 with the first Confirm.Select method. The server
+      confirms messages by sending Basic.Ack methods referring to these
+      sequence numbers.
+    </doc>
+
+    <rule name = "all messages acknowledged">
+      <doc>
+        The server MUST acknowledge all messages received after the
+        channel was put into confirm mode.
+      </doc>
+    </rule>
+
+    <rule name = "all queues">
+      <doc>
+        The server MUST acknowledge a message only after it was
+        properly handled by all the queues it was delivered to.
+      </doc>
+    </rule>
+
+    <rule name = "unroutable messages">
+      <doc>
+        The server MUST acknowledge an unroutable mandatory or
+        immediate message only after it sends the Basic.Return.
+      </doc>
+    </rule>
+
+    <rule name = "time guarantees">
+      <doc>
+        No guarantees are made as to how soon a message is
+        acknowledged.  Applications SHOULD NOT make assumptions about
+        this.
+      </doc>
+    </rule>
+
+    <doc type = "grammar">
+      confirm            = C:SELECT S:SELECT-OK
+    </doc>
+
+    <chassis name = "server" implement = "SHOULD" />
+    <chassis name = "client" implement = "MAY" />
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name="select" synchronous="1" index="10">
+      select confirm mode (i.e. enable publisher acknowledgements)
+      <doc>
+        This method sets the channel to use publisher acknowledgements.
+        The client can only use this method on a non-transactional
+        channel.
+      </doc>
+      <chassis name="server" implement="MUST"/>
+      <response name="select-ok"/>
+      <field name = "nowait" type = "bit">
+        do not send a reply method
+        <doc>
+          If set, the server will not respond to the method. The client should
+          not wait for a reply method.  If the server could not complete the
+          method it will raise a channel or connection exception.
+        </doc>
+      </field>
+    </method>
+
+    <method name="select-ok" synchronous="1" index="11">
+      acknowledge confirm mode
+      <doc>
+        This method confirms to the client that the channel was successfully
+        set to use publisher acknowledgements.
+      </doc>
+      <chassis name="client" implement="MUST"/>
+    </method>
+  </class>
+
+</amqp>
diff --git a/resources/amqp0-9-1.xml b/resources/amqp0-9-1.xml
new file mode 100644
index 0000000000000000000000000000000000000000..da785eb3bed370d7c11852fce8ef7fe7f366f2f6
--- /dev/null
+++ b/resources/amqp0-9-1.xml
@@ -0,0 +1,2843 @@
+<?xml version = "1.0"?>
+<!--
+    Copyright Notice
+    ================
+    Copyright (c) 2006-2008 Cisco Systems, Credit Suisse, Deutsche Boerse
+    Systems, Envoy Technologies, Inc., Goldman Sachs, IONA Technologies PLC,
+    iMatix Corporation, JPMorgan Chase Bank Inc. N.A, Novell, Rabbit
+    Technologies Ltd., Red Hat, Inc., TWIST Process Innovations Ltd, WS02
+    Inc. and 29West Inc. All rights reserved.
+
+    License
+    =======
+    Cisco Systems, Credit Suisse, Deutsche Boerse Systems, Envoy Technologies,
+    Inc., Goldman Sachs, IONA Technologies PLC, iMatix Corporation, JPMorgan
+    Chase Bank Inc. N.A, Novell, Rabbit Technologies Ltd., Red Hat, Inc.,
+    TWIST Process Innovations Ltd, WS02, Inc. and 29West Inc. (collectively,
+    the "Authors") each hereby grants to you a worldwide, perpetual,
+    royalty-free, nontransferable, nonexclusive license to (i) copy, display,
+    distribute and implement the Advanced Messaging Queue Protocol ("AMQP")
+    Specification and (ii) the Licensed Claims that are held by the Authors,
+    all for the purpose of implementing the Advanced Messaging Queue Protocol
+    Specification. Your license and any rights under this Agreement will
+    terminate immediately without notice from any Author if you bring any
+    claim, suit, demand, or action related to the Advanced Messaging Queue
+    Protocol Specification against any Author. Upon termination, you shall
+    destroy all copies of the Advanced Messaging Queue Protocol Specification
+    in your possession or control.
+
+    As used hereunder, "Licensed Claims" means those claims of a patent or
+    patent application, throughout the world, excluding design patents and
+    design registrations, owned or controlled, or that can be sublicensed
+    without fee and in compliance with the requirements of this Agreement,
+    by an Author or its affiliates now or at any future time and which would
+    necessarily be infringed by implementation of the Advanced Messaging
+    Queue Protocol Specification. A claim is necessarily infringed hereunder
+    only when it is not possible to avoid infringing it because there is no
+    plausible non-infringing alternative for implementing the required
+    portions of the Advanced Messaging Queue Protocol Specification.
+    Notwithstanding the foregoing, Licensed Claims shall not include any
+    claims other than as set forth above even if contained in the same patent
+    as Licensed Claims; or that read solely on any implementations of any
+    portion of the Advanced Messaging Queue Protocol Specification that are
+    not required by the Advanced Messaging Queue ProtocolSpecification, or
+    that, if licensed, would require a payment of royalties by the licensor
+    to unaffiliated third parties. Moreover, Licensed Claims shall not
+    include (i) any enabling technologies that may be necessary to make or
+    use any Licensed Product but are not themselves expressly set forth in
+    the Advanced Messaging Queue Protocol Specification (e.g., semiconductor
+    manufacturing technology, compiler technology, object oriented
+    technology, networking technology, operating system technology, and the
+    like); or (ii) the implementation of other published standards developed
+    elsewhere and merely referred to in the body of the Advanced Messaging
+    Queue Protocol Specification, or (iii) any Licensed Product and any
+    combinations thereof the purpose or function of which is not required
+    for compliance with the Advanced Messaging Queue Protocol Specification.
+    For purposes of this definition, the Advanced Messaging Queue Protocol
+    Specification shall be deemed to include both architectural and
+    interconnection requirements essential for interoperability and may also
+    include supporting source code artifacts where such architectural,
+    interconnection requirements and source code artifacts are expressly
+    identified as being required or documentation to achieve compliance with
+    the Advanced Messaging Queue Protocol Specification.
+
+    As used hereunder, "Licensed Products" means only those specific portions
+    of products (hardware, software or combinations thereof) that implement
+    and are compliant with all relevant portions of the Advanced Messaging
+    Queue Protocol Specification.
+
+    The following disclaimers, which you hereby also acknowledge as to any
+    use you may make of the Advanced Messaging Queue Protocol Specification:
+
+    THE ADVANCED MESSAGING QUEUE PROTOCOL SPECIFICATION IS PROVIDED "AS IS,"
+    AND THE AUTHORS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+    IMPLIED, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, OR TITLE; THAT THE
+    CONTENTS OF THE ADVANCED MESSAGING QUEUE PROTOCOL SPECIFICATION ARE
+    SUITABLE FOR ANY PURPOSE; NOR THAT THE IMPLEMENTATION OF THE ADVANCED
+    MESSAGING QUEUE PROTOCOL SPECIFICATION WILL NOT INFRINGE ANY THIRD PARTY
+    PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.
+
+    THE AUTHORS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL,
+    INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATING TO ANY
+    USE, IMPLEMENTATION OR DISTRIBUTION OF THE ADVANCED MESSAGING QUEUE
+    PROTOCOL SPECIFICATION.
+
+    The name and trademarks of the Authors may NOT be used in any manner,
+    including advertising or publicity pertaining to the Advanced Messaging
+    Queue Protocol Specification or its contents without specific, written
+    prior permission. Title to copyright in the Advanced Messaging Queue
+    Protocol Specification will at all times remain with the Authors.
+
+    No other rights are granted by implication, estoppel or otherwise.
+
+    Upon termination of your license or rights under this Agreement, you
+    shall destroy all copies of the Advanced Messaging Queue Protocol
+    Specification in your possession or control.
+
+    Trademarks
+    ==========
+    JPMorgan, JPMorgan Chase, Chase, the JPMorgan Chase logo and the
+    Octagon Symbol are trademarks of JPMorgan Chase & Co.
+
+    IMATIX and the iMatix logo are trademarks of iMatix Corporation sprl.
+
+    IONA, IONA Technologies, and the IONA logos are trademarks of IONA
+    Technologies PLC and/or its subsidiaries.
+
+    LINUX is a trademark of Linus Torvalds. RED HAT and JBOSS are registered
+    trademarks of Red Hat, Inc. in the US and other countries.
+
+    Java, all Java-based trademarks and OpenOffice.org are trademarks of
+    Sun Microsystems, Inc. in the United States, other countries, or both.
+
+    Other company, product, or service names may be trademarks or service
+    marks of others.
+
+    Links to full AMQP specification:
+    =================================
+    http://www.amqp.org
+-->
+
+<!--
+    <!DOCTYPE amqp SYSTEM "amqp.dtd">
+-->
+
+<!-- XML Notes
+
+    We use entities to indicate repetition; attributes to indicate properties.
+
+    We use the 'name' attribute as an identifier, usually within the context
+    of the surrounding entities.
+
+    We use spaces to seperate words in names, so that we can print names in
+    their natural form depending on the context - underlines for source code,
+    hyphens for written text, etc.
+
+    We do not enforce any particular validation mechanism but we support all
+    mechanisms.  The protocol definition conforms to a formal grammar that is
+    published seperately in several technologies.
+
+ -->
+
+<amqp major = "0" minor = "9" revision = "1"
+    port = "5672" comment = "AMQ Protocol version 0-9-1">
+  <!--
+      ======================================================
+      ==       CONSTANTS
+      ======================================================
+  -->
+  <!-- Frame types -->
+  <constant name = "frame-method"     value = "1" />
+  <constant name = "frame-header"     value = "2" />
+  <constant name = "frame-body"       value = "3" />
+  <constant name = "frame-heartbeat"  value = "8" />
+
+  <!-- Protocol constants -->
+  <constant name = "frame-min-size"   value = "4096" />
+  <constant name = "frame-end"        value = "206" />
+
+  <!-- Reply codes -->
+  <constant name = "reply-success" value = "200">
+    <doc>
+      Indicates that the method completed successfully. This reply code is
+      reserved for future use - the current protocol design does not use positive
+      confirmation and reply codes are sent only in case of an error.
+    </doc>
+  </constant>
+
+  <constant name = "content-too-large" value = "311" class = "soft-error">
+    <doc>
+      The client attempted to transfer content larger than the server could accept
+      at the present time. The client may retry at a later time.
+    </doc>
+  </constant>
+
+  <constant name = "no-consumers" value = "313" class = "soft-error">
+    <doc>
+      When the exchange cannot deliver to a consumer when the immediate flag is
+      set. As a result of pending data on the queue or the absence of any
+      consumers of the queue.
+    </doc>
+  </constant>
+
+  <constant name = "connection-forced" value = "320" class = "hard-error">
+    <doc>
+      An operator intervened to close the connection for some reason. The client
+      may retry at some later date.
+    </doc>
+  </constant>
+
+  <constant name = "invalid-path" value = "402" class = "hard-error">
+    <doc>
+      The client tried to work with an unknown virtual host.
+    </doc>
+  </constant>
+
+  <constant name = "access-refused" value = "403" class = "soft-error">
+    <doc>
+      The client attempted to work with a server entity to which it has no
+      access due to security settings.
+    </doc>
+  </constant>
+
+  <constant name = "not-found" value = "404" class = "soft-error">
+    <doc>
+      The client attempted to work with a server entity that does not exist.
+    </doc>
+  </constant>
+
+  <constant name = "resource-locked" value = "405" class = "soft-error">
+    <doc>
+      The client attempted to work with a server entity to which it has no
+      access because another client is working with it.
+    </doc>
+  </constant>
+
+  <constant name = "precondition-failed" value = "406" class = "soft-error">
+    <doc>
+      The client requested a method that was not allowed because some precondition
+      failed.
+    </doc>
+  </constant>
+
+  <constant name = "frame-error" value = "501" class = "hard-error">
+    <doc>
+      The sender sent a malformed frame that the recipient could not decode.
+      This strongly implies a programming error in the sending peer.
+    </doc>
+  </constant>
+
+  <constant name = "syntax-error" value = "502" class = "hard-error">
+    <doc>
+      The sender sent a frame that contained illegal values for one or more
+      fields. This strongly implies a programming error in the sending peer.
+    </doc>
+  </constant>
+
+  <constant name = "command-invalid" value = "503" class = "hard-error">
+    <doc>
+      The client sent an invalid sequence of frames, attempting to perform an
+      operation that was considered invalid by the server. This usually implies
+      a programming error in the client.
+    </doc>
+  </constant>
+
+  <constant name = "channel-error" value = "504" class = "hard-error">
+    <doc>
+      The client attempted to work with a channel that had not been correctly
+      opened. This most likely indicates a fault in the client layer.
+    </doc>
+  </constant>
+
+  <constant name = "unexpected-frame" value = "505" class = "hard-error">
+    <doc>
+      The peer sent a frame that was not expected, usually in the context of
+      a content header and body.  This strongly indicates a fault in the peer's
+      content processing.
+    </doc>
+  </constant>
+
+  <constant name = "resource-error" value = "506" class = "hard-error">
+    <doc>
+      The server could not complete the method because it lacked sufficient
+      resources. This may be due to the client creating too many of some type
+      of entity.
+    </doc>
+  </constant>
+
+  <constant name = "not-allowed" value = "530" class = "hard-error">
+    <doc>
+      The client tried to work with some entity in a manner that is prohibited
+      by the server, due to security settings or by some other criteria.
+    </doc>
+  </constant>
+
+  <constant name = "not-implemented" value = "540" class = "hard-error">
+    <doc>
+      The client tried to use functionality that is not implemented in the
+      server.
+    </doc>
+  </constant>
+
+  <constant name = "internal-error" value = "541" class = "hard-error">
+    <doc>
+      The server could not complete the method because of an internal error.
+      The server may require intervention by an operator in order to resume
+      normal operations.
+    </doc>
+  </constant>
+
+  <!--
+      ======================================================
+      ==       DOMAIN TYPES
+      ======================================================
+  -->
+
+  <domain name = "class-id" type = "short" />
+
+  <domain name = "consumer-tag" type = "shortstr" label = "consumer tag">
+    <doc>
+      Identifier for the consumer, valid within the current channel.
+    </doc>
+  </domain>
+
+  <domain name = "delivery-tag" type = "longlong" label = "server-assigned delivery tag">
+    <doc>
+      The server-assigned and channel-specific delivery tag
+    </doc>
+    <rule name = "channel-local">
+      <doc>
+        The delivery tag is valid only within the channel from which the message was
+        received. I.e. a client MUST NOT receive a message on one channel and then
+        acknowledge it on another.
+      </doc>
+    </rule>
+    <rule name = "non-zero">
+      <doc>
+        The server MUST NOT use a zero value for delivery tags. Zero is reserved
+        for client use, meaning "all messages so far received".
+      </doc>
+    </rule>
+  </domain>
+
+  <domain name = "exchange-name" type = "shortstr" label = "exchange name">
+    <doc>
+      The exchange name is a client-selected string that identifies the exchange for
+      publish methods.
+    </doc>
+    <assert check = "length" value = "127" />
+    <assert check = "regexp" value = "^[a-zA-Z0-9-_.:]*$" />
+  </domain>
+
+  <domain name = "method-id" type = "short" />
+
+  <domain name = "no-ack" type = "bit" label = "no acknowledgement needed">
+    <doc>
+      If this field is set the server does not expect acknowledgements for
+      messages. That is, when a message is delivered to the client the server
+      assumes the delivery will succeed and immediately dequeues it. This
+      functionality may increase performance but at the cost of reliability.
+      Messages can get lost if a client dies before they are delivered to the
+      application.
+    </doc>
+  </domain>
+
+  <domain name = "no-local" type = "bit" label = "do not deliver own messages">
+    <doc>
+      If the no-local field is set the server will not send messages to the connection that
+      published them.
+    </doc>
+  </domain>
+
+  <domain name = "no-wait" type = "bit" label = "do not send reply method">
+    <doc>
+      If set, the server will not respond to the method. The client should not wait
+      for a reply method. If the server could not complete the method it will raise a
+      channel or connection exception.
+    </doc>
+  </domain>
+
+  <domain name = "path" type = "shortstr">
+    <doc>
+      Unconstrained.
+    </doc>
+    <assert check = "notnull" />
+    <assert check = "length" value = "127" />
+  </domain>
+
+  <domain name = "peer-properties" type = "table">
+    <doc>
+      This table provides a set of peer properties, used for identification, debugging,
+      and general information.
+    </doc>
+  </domain>
+
+  <domain name = "queue-name" type = "shortstr" label = "queue name">
+    <doc>
+      The queue name identifies the queue within the vhost.  In methods where the queue
+      name may be blank, and that has no specific significance, this refers to the
+      'current' queue for the channel, meaning the last queue that the client declared
+      on the channel.  If the client did not declare a queue, and the method needs a
+      queue name, this will result in a 502 (syntax error) channel exception.
+    </doc>
+    <assert check = "length" value = "127" />
+    <assert check = "regexp" value = "^[a-zA-Z0-9-_.:]*$" />
+  </domain>
+
+  <domain name = "redelivered" type = "bit" label = "message is being redelivered">
+    <doc>
+      This indicates that the message has been previously delivered to this or
+      another client.
+    </doc>
+    <rule name = "implementation">
+      <doc>
+        The server SHOULD try to signal redelivered messages when it can. When
+        redelivering a message that was not successfully acknowledged, the server
+        SHOULD deliver it to the original client if possible.
+      </doc>
+      <doc type = "scenario">
+        Declare a shared queue and publish a message to the queue.  Consume the
+        message using explicit acknowledgements, but do not acknowledge the
+        message.  Close the connection, reconnect, and consume from the queue
+        again.  The message should arrive with the redelivered flag set.
+      </doc>
+    </rule>
+    <rule name = "hinting">
+      <doc>
+        The client MUST NOT rely on the redelivered field but should take it as a
+        hint that the message may already have been processed. A fully robust
+        client must be able to track duplicate received messages on non-transacted,
+        and locally-transacted channels.
+      </doc>
+    </rule>
+  </domain>
+
+  <domain name = "message-count" type = "long" label = "number of messages in queue">
+    <doc>
+      The number of messages in the queue, which will be zero for newly-declared
+      queues. This is the number of messages present in the queue, and committed
+      if the channel on which they were published is transacted, that are not
+      waiting acknowledgement.
+    </doc>
+  </domain>
+
+  <domain name = "reply-code" type = "short" label = "reply code from server">
+    <doc>
+      The reply code. The AMQ reply codes are defined as constants at the start
+      of this formal specification.
+    </doc>
+    <assert check = "notnull" />
+  </domain>
+
+  <domain name = "reply-text" type = "shortstr" label = "localised reply text">
+    <doc>
+      The localised reply text. This text can be logged as an aid to resolving
+      issues.
+    </doc>
+    <assert check = "notnull" />
+  </domain>
+
+  <!-- Elementary domains -->
+  <domain name = "bit"        type = "bit"       label = "single bit" />
+  <domain name = "octet"      type = "octet"     label = "single octet" />
+  <domain name = "short"      type = "short"     label = "16-bit integer" />
+  <domain name = "long"       type = "long"      label = "32-bit integer" />
+  <domain name = "longlong"   type = "longlong"  label = "64-bit integer" />
+  <domain name = "shortstr"   type = "shortstr"  label = "short string" />
+  <domain name = "longstr"    type = "longstr"   label = "long string" />
+  <domain name = "timestamp"  type = "timestamp" label = "64-bit timestamp" />
+  <domain name = "table"      type = "table"     label = "field table" />
+
+  <!-- ==  CONNECTION  ======================================================= -->
+
+  <class name = "connection" handler = "connection" index = "10" label = "work with socket connections">
+    <doc>
+      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.
+    </doc>
+
+    <doc type = "grammar">
+      connection          = open-connection *use-connection close-connection
+      open-connection     = C:protocol-header
+                            S:START C:START-OK
+                            *challenge
+                            S:TUNE C:TUNE-OK
+                            C:OPEN S:OPEN-OK
+      challenge           = S:SECURE C:SECURE-OK
+      use-connection      = *channel
+      close-connection    = C:CLOSE S:CLOSE-OK
+                          / S:CLOSE C:CLOSE-OK
+    </doc>
+
+    <chassis name = "server" implement = "MUST" />
+    <chassis name = "client" implement = "MUST" />
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "start" synchronous = "1" index = "10" label = "start connection negotiation">
+      <doc>
+        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
+        which the client can use for authentication.
+      </doc>
+
+      <rule name = "protocol-name">
+        <doc>
+          If the server cannot support the protocol specified in the protocol header,
+          it MUST respond with a valid protocol header and then close the socket
+          connection.
+        </doc>
+        <doc type = "scenario">
+          The client sends a protocol header containing an invalid protocol name.
+          The server MUST respond by sending a valid protocol header and then closing
+          the connection.
+        </doc>
+      </rule>
+      <rule name = "server-support">
+        <doc>
+          The server MUST provide a protocol version that is lower than or equal to
+          that requested by the client in the protocol header.
+        </doc>
+        <doc type = "scenario">
+          The client requests a protocol version that is higher than any valid
+          implementation, e.g. 2.0.  The server must respond with a protocol header
+          indicating its supported protocol version, e.g. 1.0.
+        </doc>
+      </rule>
+      <rule name = "client-support">
+        <doc>
+          If the client cannot handle the protocol version suggested by the server
+          it MUST close the socket connection without sending any further data.
+        </doc>
+        <doc type = "scenario">
+          The server sends a protocol version that is lower than any valid
+          implementation, e.g. 0.1.  The client must respond by closing the
+          connection without sending any further data.
+        </doc>
+      </rule>
+
+      <chassis name = "client" implement = "MUST" />
+      <response name = "start-ok" />
+
+      <field name = "version-major" domain = "octet" label = "protocol major version">
+        <doc>
+          The major version number can take any value from 0 to 99 as defined in the
+          AMQP specification.
+        </doc>
+      </field>
+
+      <field name = "version-minor" domain = "octet" label = "protocol minor version">
+        <doc>
+          The minor version number can take any value from 0 to 99 as defined in the
+          AMQP specification.
+        </doc>
+      </field>
+
+      <field name = "server-properties" domain = "peer-properties" label = "server properties">
+        <rule name = "required-fields">
+          <doc>
+            The properties SHOULD contain at least these fields: "host", specifying the
+            server host name or address, "product", giving the name of the server product,
+            "version", giving the name of the server version, "platform", giving the name
+            of the operating system, "copyright", if appropriate, and "information", giving
+            other general information.
+          </doc>
+          <doc type = "scenario">
+            Client connects to server and inspects the server properties. It checks for
+            the presence of the required fields.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "mechanisms" domain = "longstr" label = "available security mechanisms">
+        <doc>
+          A list of the security mechanisms that the server supports, delimited by spaces.
+        </doc>
+        <assert check = "notnull" />
+      </field>
+
+      <field name = "locales" domain = "longstr" label = "available message locales">
+        <doc>
+          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.
+        </doc>
+        <rule name = "required-support">
+          <doc>
+            The server MUST support at least the en_US locale.
+          </doc>
+          <doc type = "scenario">
+            Client connects to server and inspects the locales field. It checks for
+            the presence of the required locale(s).
+          </doc>
+        </rule>
+        <assert check = "notnull" />
+      </field>
+    </method>
+
+    <method name = "start-ok" synchronous = "1" index = "11"
+      label = "select security mechanism and locale">
+      <doc>
+        This method selects a SASL security mechanism.
+      </doc>
+
+      <chassis name = "server" implement = "MUST" />
+
+      <field name = "client-properties" domain = "peer-properties" label = "client properties">
+        <rule name = "required-fields">
+          <!-- This rule is not testable from the client side -->
+          <doc>
+            The properties SHOULD contain at least these fields: "product", giving the name
+            of the client product, "version", giving the name of the client version, "platform",
+            giving the name of the operating system, "copyright", if appropriate, and
+            "information", giving other general information.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "mechanism" domain = "shortstr" label = "selected security mechanism">
+        <doc>
+          A single security mechanisms selected by the client, which must be one of those
+          specified by the server.
+        </doc>
+        <rule name = "security">
+          <doc>
+            The client SHOULD authenticate using the highest-level security profile it
+            can handle from the list provided by the server.
+          </doc>
+        </rule>
+        <rule name = "validity">
+          <doc>
+            If the mechanism field does not contain one of the security mechanisms
+            proposed by the server in the Start method, the server MUST close the
+            connection without sending any further data.
+          </doc>
+          <doc type = "scenario">
+            Client connects to server and sends an invalid security mechanism. The
+            server must respond by closing the connection (a socket close, with no
+            connection close negotiation).
+          </doc>
+        </rule>
+        <assert check = "notnull" />
+      </field>
+
+      <field name = "response" domain = "longstr" label = "security response data">
+        <doc>
+          A block of opaque data passed to the security mechanism. The contents of this
+          data are defined by the SASL security mechanism.
+        </doc>
+        <assert check = "notnull" />
+      </field>
+
+      <field name = "locale" domain = "shortstr" label = "selected message locale">
+        <doc>
+          A single message locale selected by the client, which must be one of those
+          specified by the server.
+        </doc>
+        <assert check = "notnull" />
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "secure" synchronous = "1" index = "20" label = "security mechanism challenge">
+      <doc>
+        The SASL protocol works by exchanging challenges and responses until both peers have
+        received sufficient information to authenticate each other. This method challenges
+        the client to provide more information.
+      </doc>
+
+      <chassis name = "client" implement = "MUST" />
+      <response name = "secure-ok" />
+
+      <field name = "challenge" domain = "longstr" label = "security challenge data">
+        <doc>
+          Challenge information, a block of opaque binary data passed to the security
+          mechanism.
+        </doc>
+      </field>
+    </method>
+
+    <method name = "secure-ok" synchronous = "1" index = "21" label = "security mechanism response">
+      <doc>
+        This method attempts to authenticate, passing a block of SASL data for the security
+        mechanism at the server side.
+      </doc>
+
+      <chassis name = "server" implement = "MUST" />
+
+      <field name = "response" domain = "longstr" label = "security response data">
+        <doc>
+          A block of opaque data passed to the security mechanism. The contents of this
+          data are defined by the SASL security mechanism.
+        </doc>
+        <assert check = "notnull" />
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "tune" synchronous = "1" index = "30"
+      label = "propose connection tuning parameters">
+      <doc>
+        This method proposes a set of connection configuration values to the client. The
+        client can accept and/or adjust these.
+      </doc>
+
+      <chassis name = "client" implement = "MUST" />
+
+      <response name = "tune-ok" />
+
+      <field name = "channel-max" domain = "short" label = "proposed maximum channels">
+        <doc>
+          Specifies highest channel number that the server permits.  Usable channel numbers
+          are in the range 1..channel-max.  Zero indicates no specified limit.
+        </doc>
+      </field>
+
+      <field name = "frame-max" domain = "long" label = "proposed maximum frame size">
+        <doc>
+          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
+          that the server does not impose any specific limit but may reject very large
+          frames if it cannot allocate resources for them.
+        </doc>
+        <rule name = "minimum">
+          <doc>
+            Until the frame-max has been negotiated, both peers MUST accept frames of up
+            to frame-min-size octets large, and the minimum negotiated value for frame-max
+            is also frame-min-size.
+          </doc>
+          <doc type = "scenario">
+            Client connects to server and sends a large properties field, creating a frame
+            of frame-min-size octets.  The server must accept this frame.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "heartbeat" domain = "short" label = "desired heartbeat delay">
+        <doc>
+          The delay, in seconds, of the connection heartbeat that the server wants.
+          Zero means the server does not want a heartbeat.
+        </doc>
+      </field>
+    </method>
+
+    <method name = "tune-ok" synchronous = "1" index = "31"
+      label = "negotiate connection tuning parameters">
+      <doc>
+        This method sends the client's connection tuning parameters to the server.
+        Certain fields are negotiated, others provide capability information.
+      </doc>
+
+      <chassis name = "server" implement = "MUST" />
+
+      <field name = "channel-max" domain = "short" label = "negotiated maximum channels">
+        <doc>
+          The maximum total number of channels that the client will use per connection.
+        </doc>
+        <rule name = "upper-limit">
+          <doc>
+            If the client specifies a channel max that is higher than the value provided
+            by the server, the server MUST close the connection without attempting a
+            negotiated close.  The server may report the error in some fashion to assist
+            implementors.
+          </doc>
+        </rule>
+        <assert check = "notnull" />
+        <assert check = "le" method = "tune" field = "channel-max" />
+      </field>
+
+      <field name = "frame-max" domain = "long" label = "negotiated maximum frame size">
+        <doc>
+          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
+          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
+          be broken into frames of arbitrary size.
+        </doc>
+        <rule name = "minimum">
+          <doc>
+            Until the frame-max has been negotiated, both peers MUST accept frames of up
+            to frame-min-size octets large, and the minimum negotiated value for frame-max
+            is also frame-min-size.
+          </doc>
+        </rule>
+        <rule name = "upper-limit">
+          <doc>
+            If the client specifies a frame max that is higher than the value provided
+            by the server, the server MUST close the connection without attempting a
+            negotiated close. The server may report the error in some fashion to assist
+            implementors.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "heartbeat" domain = "short" label = "desired heartbeat delay">
+        <doc>
+          The delay, in seconds, of the connection heartbeat that the client wants. Zero
+          means the client does not want a heartbeat.
+        </doc>
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "open" synchronous = "1" index = "40" label = "open connection to virtual host">
+      <doc>
+        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.
+        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.
+      </doc>
+
+      <chassis name = "server" implement = "MUST" />
+      <response name = "open-ok" />
+
+      <field name = "virtual-host" domain = "path" label = "virtual host name">
+        <doc>
+          The name of the virtual host to work with.
+        </doc>
+        <rule name = "separation">
+          <doc>
+            If the server supports multiple virtual hosts, it MUST enforce a full
+            separation of exchanges, queues, and all associated entities per virtual
+            host. An application, connected to a specific virtual host, MUST NOT be able
+            to access resources of another virtual host.
+          </doc>
+        </rule>
+        <rule name = "security">
+          <doc>
+            The server SHOULD verify that the client has permission to access the
+            specified virtual host.
+          </doc>
+        </rule>
+      </field>
+      <!-- Deprecated: "capabilities", must be zero -->
+      <field name = "reserved-1" type = "shortstr" reserved = "1" />
+      <!-- Deprecated: "insist", must be zero -->
+      <field name = "reserved-2" type = "bit" reserved = "1" />
+    </method>
+
+    <method name = "open-ok" synchronous = "1" index = "41" label = "signal that connection is ready">
+      <doc>
+        This method signals to the client that the connection is ready for use.
+      </doc>
+      <chassis name = "client" implement = "MUST" />
+      <!-- Deprecated: "known-hosts", must be zero -->
+      <field name = "reserved-1" type = "shortstr" reserved = "1" />
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "close" synchronous = "1" index = "50" label = "request a connection close">
+      <doc>
+        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
+        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.
+      </doc>
+      <rule name = "stability">
+        <doc>
+          After sending this method, any received methods except Close and Close-OK MUST
+          be discarded.  The response to receiving a Close after sending Close must be to
+          send Close-Ok.
+        </doc>
+      </rule>
+
+      <chassis name = "client" implement = "MUST" />
+      <chassis name = "server" implement = "MUST" />
+      <response name = "close-ok" />
+
+      <field name = "reply-code" domain = "reply-code" />
+      <field name = "reply-text" domain = "reply-text" />
+
+      <field name = "class-id" domain = "class-id" label = "failing method class">
+        <doc>
+          When the close is provoked by a method exception, this is the class of the
+          method.
+        </doc>
+      </field>
+
+      <field name = "method-id" domain = "method-id" label = "failing method ID">
+        <doc>
+          When the close is provoked by a method exception, this is the ID of the method.
+        </doc>
+      </field>
+    </method>
+
+    <method name = "close-ok" synchronous = "1" index = "51" label = "confirm a connection close">
+      <doc>
+        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.
+      </doc>
+      <rule name = "reporting">
+        <doc>
+          A peer that detects a socket closure without having received a Close-Ok
+          handshake method SHOULD log the error.
+        </doc>
+      </rule>
+      <chassis name = "client" implement = "MUST" />
+      <chassis name = "server" implement = "MUST" />
+    </method>
+  </class>
+
+  <!-- ==  CHANNEL  ========================================================== -->
+
+  <class name = "channel" handler = "channel" index = "20" label = "work with channels">
+    <doc>
+      The channel class provides methods for a client to establish a channel to a
+      server and for both peers to operate the channel thereafter.
+    </doc>
+
+    <doc type = "grammar">
+      channel             = open-channel *use-channel close-channel
+      open-channel        = C:OPEN S:OPEN-OK
+      use-channel         = C:FLOW S:FLOW-OK
+                          / S:FLOW C:FLOW-OK
+                          / functional-class
+      close-channel       = C:CLOSE S:CLOSE-OK
+                          / S:CLOSE C:CLOSE-OK
+    </doc>
+
+    <chassis name = "server" implement = "MUST" />
+    <chassis name = "client" implement = "MUST" />
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "open" synchronous = "1" index = "10" label = "open a channel for use">
+      <doc>
+        This method opens a channel to the server.
+      </doc>
+      <rule name = "state" on-failure = "channel-error">
+        <doc>
+          The client MUST NOT use this method on an already-opened channel.
+        </doc>
+        <doc type = "scenario">
+          Client opens a channel and then reopens the same channel.
+        </doc>
+      </rule>
+      <chassis name = "server" implement = "MUST" />
+      <response name = "open-ok" />
+      <!-- Deprecated: "out-of-band", must be zero -->
+      <field name = "reserved-1" type = "shortstr" reserved = "1" />
+    </method>
+
+    <method name = "open-ok" synchronous = "1" index = "11" label = "signal that the channel is ready">
+      <doc>
+        This method signals to the client that the channel is ready for use.
+      </doc>
+      <chassis name = "client" implement = "MUST" />
+      <!-- Deprecated: "channel-id", must be zero -->
+      <field name = "reserved-1" type = "longstr" reserved = "1" />
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "flow" synchronous = "1" index = "20" label = "enable/disable flow from peer">
+      <doc>
+        This method asks the peer to pause or restart the flow of content data sent by
+        a consumer. This is a simple flow-control mechanism that a peer can use to avoid
+        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
+        not affect contents returned by Basic.Get-Ok methods.
+      </doc>
+
+      <rule name = "initial-state">
+        <doc>
+          When a new channel is opened, it is active (flow is active). Some applications
+          assume that channels are inactive until started. To emulate this behaviour a
+          client MAY open the channel, then pause it.
+        </doc>
+      </rule>
+
+      <rule name = "bidirectional">
+        <doc>
+          When sending content frames, a peer SHOULD monitor the channel for incoming
+          methods and respond to a Channel.Flow as rapidly as possible.
+        </doc>
+      </rule>
+
+      <rule name = "throttling">
+        <doc>
+          A peer MAY use the Channel.Flow method to throttle incoming content data for
+          internal reasons, for example, when exchanging data over a slower connection.
+        </doc>
+      </rule>
+
+      <rule name = "expected-behaviour">
+        <doc>
+          The peer that requests a Channel.Flow method MAY disconnect and/or ban a peer
+          that does not respect the request.  This is to prevent badly-behaved clients
+          from overwhelming a server.
+        </doc>
+      </rule>
+
+      <chassis name = "server" implement = "MUST" />
+      <chassis name = "client" implement = "MUST" />
+
+      <response name = "flow-ok" />
+
+      <field name = "active" domain = "bit" label = "start/stop content frames">
+        <doc>
+          If 1, the peer starts sending content frames. If 0, the peer stops sending
+          content frames.
+        </doc>
+      </field>
+    </method>
+
+    <method name = "flow-ok" index = "21" label = "confirm a flow method">
+      <doc>
+        Confirms to the peer that a flow command was received and processed.
+      </doc>
+      <chassis name = "server" implement = "MUST" />
+      <chassis name = "client" implement = "MUST" />
+      <field name = "active" domain = "bit" label = "current flow setting">
+        <doc>
+          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.
+        </doc>
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "close" synchronous = "1" index = "40" label = "request a channel close">
+      <doc>
+        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
+        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.
+      </doc>
+      <rule name = "stability">
+        <doc>
+          After sending this method, any received methods except Close and Close-OK MUST
+          be discarded.  The response to receiving a Close after sending Close must be to
+          send Close-Ok.
+        </doc>
+      </rule>
+
+      <chassis name = "client" implement = "MUST" />
+      <chassis name = "server" implement = "MUST" />
+      <response name = "close-ok" />
+
+      <field name = "reply-code" domain = "reply-code" />
+      <field name = "reply-text" domain = "reply-text" />
+
+      <field name = "class-id" domain = "class-id" label = "failing method class">
+        <doc>
+          When the close is provoked by a method exception, this is the class of the
+          method.
+        </doc>
+      </field>
+
+      <field name = "method-id" domain = "method-id" label = "failing method ID">
+        <doc>
+          When the close is provoked by a method exception, this is the ID of the method.
+        </doc>
+      </field>
+    </method>
+
+    <method name = "close-ok" synchronous = "1" index = "41" label = "confirm a channel close">
+      <doc>
+        This method confirms a Channel.Close method and tells the recipient that it is safe
+        to release resources for the channel.
+      </doc>
+      <rule name = "reporting">
+        <doc>
+          A peer that detects a socket closure without having received a Channel.Close-Ok
+          handshake method SHOULD log the error.
+        </doc>
+      </rule>
+      <chassis name = "client" implement = "MUST" />
+      <chassis name = "server" implement = "MUST" />
+    </method>
+  </class>
+
+  <!-- ==  EXCHANGE  ========================================================= -->
+
+  <class name = "exchange" handler = "channel" index = "40" label = "work with exchanges">
+    <doc>
+      Exchanges match and distribute messages across queues. Exchanges can be configured in
+      the server or declared at runtime.
+    </doc>
+
+    <doc type = "grammar">
+      exchange            = C:DECLARE  S:DECLARE-OK
+                          / C:DELETE   S:DELETE-OK
+    </doc>
+
+    <chassis name = "server" implement = "MUST" />
+    <chassis name = "client" implement = "MUST" />
+
+    <rule name = "required-types">
+      <doc>
+        The server MUST implement these standard exchange types: fanout, direct.
+      </doc>
+      <doc type = "scenario">
+        Client attempts to declare an exchange with each of these standard types.
+      </doc>
+    </rule>
+    <rule name = "recommended-types">
+      <doc>
+        The server SHOULD implement these standard exchange types: topic, headers.
+      </doc>
+      <doc type = "scenario">
+        Client attempts to declare an exchange with each of these standard types.
+      </doc>
+    </rule>
+    <rule name = "required-instances">
+      <doc>
+        The server MUST, in each virtual host, pre-declare an exchange instance
+        for each standard exchange type that it implements, where the name of the
+        exchange instance, if defined, is "amq." followed by the exchange type name.
+      </doc>
+      <doc>
+        The server MUST, in each virtual host, pre-declare at least two direct
+        exchange instances: one named "amq.direct", the other with no public name
+        that serves as a default  exchange for Publish methods.
+      </doc>
+      <doc type = "scenario">
+        Client declares a temporary queue and attempts to bind to each required
+        exchange instance ("amq.fanout", "amq.direct", "amq.topic", and "amq.headers"
+        if those types are defined).
+      </doc>
+    </rule>
+    <rule name = "default-exchange">
+      <doc>
+        The server MUST pre-declare a direct exchange with no public name to act as
+        the default exchange for content Publish methods and for default queue bindings.
+      </doc>
+      <doc type = "scenario">
+        Client checks that the default exchange is active by specifying a queue
+        binding with no exchange name, and publishing a message with a suitable
+        routing key but without specifying the exchange name, then ensuring that
+        the message arrives in the queue correctly.
+      </doc>
+    </rule>
+    <rule name = "default-access">
+      <doc>
+        The server MUST NOT allow clients to access the default exchange except
+        by specifying an empty exchange name in the Queue.Bind and content Publish
+        methods.
+      </doc>
+    </rule>
+    <rule name = "extensions">
+      <doc>
+        The server MAY implement other exchange types as wanted.
+      </doc>
+    </rule>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "declare" synchronous = "1" index = "10" label = "verify exchange exists, create if needed">
+      <doc>
+        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.
+      </doc>
+      <rule name = "minimum">
+        <doc>
+          The server SHOULD support a minimum of 16 exchanges per virtual host and
+          ideally, impose no limit except as defined by available resources.
+        </doc>
+        <doc type = "scenario">
+          The client declares as many exchanges as it can until the server reports
+          an error; the number of exchanges successfully declared must be at least
+          sixteen.
+        </doc>
+      </rule>
+
+      <chassis name = "server" implement = "MUST" />
+      <response name = "declare-ok" />
+
+      <!-- Deprecated: "ticket", must be zero -->
+      <field name = "reserved-1" type = "short" reserved = "1" />
+
+      <field name = "exchange" domain = "exchange-name">
+        <rule name = "reserved" on-failure = "access-refused">
+          <doc>
+            Exchange names starting with "amq." are reserved for pre-declared and
+            standardised exchanges. The client MAY declare an exchange starting with
+            "amq." if the passive option is set, or the exchange already exists.
+          </doc>
+          <doc type = "scenario">
+            The client attempts to declare a non-existing exchange starting with
+            "amq." and with the passive option set to zero.
+          </doc>
+        </rule>
+        <rule name = "syntax" on-failure = "precondition-failed">
+          <doc>
+            The exchange name consists of a non-empty sequence of these characters:
+            letters, digits, hyphen, underscore, period, or colon.
+          </doc>
+          <doc type = "scenario">
+            The client attempts to declare an exchange with an illegal name.
+          </doc>
+        </rule>
+        <assert check = "notnull" />
+      </field>
+
+      <field name = "type" domain = "shortstr" label = "exchange type">
+        <doc>
+          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
+          messages are routed through it. It is not valid or meaningful to attempt to
+          change the type of an existing exchange.
+        </doc>
+        <rule name = "typed" on-failure = "not-allowed">
+          <doc>
+            Exchanges cannot be redeclared with different types.  The client MUST not
+            attempt to redeclare an existing exchange with a different type than used
+            in the original Exchange.Declare method.
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+        <rule name = "support" on-failure = "command-invalid">
+          <doc>
+            The client MUST NOT attempt to declare an exchange with a type that the
+            server does not support.
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "passive" domain = "bit" label = "do not create exchange">
+        <doc>
+          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
+          use this to check whether an exchange exists without modifying the
+          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.
+          Arguments are compared for semantic equivalence.
+        </doc>
+        <rule name = "not-found">
+          <doc>
+            If set, and the exchange does not already exist, the server MUST
+            raise a channel exception with reply code 404 (not found).
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+        <rule name = "equivalent">
+          <doc>
+            If not set and the exchange exists, the server MUST check that the
+            existing exchange has the same values for type, durable, and arguments
+            fields.  The server MUST respond with Declare-Ok if the requested
+            exchange matches these fields, and MUST raise a channel exception if
+            not.
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "durable" domain = "bit" label = "request a durable exchange">
+        <doc>
+          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
+          (transient exchanges) are purged if/when a server restarts.
+        </doc>
+        <rule name = "support">
+          <doc>
+            The server MUST support both durable and transient exchanges.
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+      </field>
+
+      <!-- Deprecated: "auto-delete", must be zero -->
+      <field name = "reserved-2" type = "bit" reserved = "1" />
+      <!-- Deprecated: "internal", must be zero -->
+      <field name = "reserved-3" type = "bit" reserved = "1" />
+      <field name = "no-wait" domain = "no-wait" />
+
+      <field name = "arguments" domain = "table" label = "arguments for declaration">
+        <doc>
+          A set of arguments for the declaration. The syntax and semantics of these
+          arguments depends on the server implementation.
+        </doc>
+      </field>
+    </method>
+
+    <method name = "declare-ok" synchronous = "1" index = "11" label = "confirm exchange declaration">
+      <doc>
+        This method confirms a Declare method and confirms the name of the exchange,
+        essential for automatically-named exchanges.
+      </doc>
+      <chassis name = "client" implement = "MUST" />
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "delete" synchronous = "1" index = "20" label = "delete an exchange">
+      <doc>
+        This method deletes an exchange. When an exchange is deleted all queue bindings on
+        the exchange are cancelled.
+      </doc>
+
+      <chassis name = "server" implement = "MUST" />
+      <response name = "delete-ok" />
+
+      <!-- Deprecated: "ticket", must be zero -->
+      <field name = "reserved-1" type = "short" reserved = "1" />
+
+      <field name = "exchange" domain = "exchange-name">
+        <rule name = "exists" on-failure = "not-found">
+          <doc>
+            The client MUST NOT attempt to delete an exchange that does not exist.
+          </doc>
+        </rule>
+        <assert check = "notnull" />
+      </field>
+
+      <field name = "if-unused" domain = "bit" label = "delete only if unused">
+        <doc>
+          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
+          channel exception instead.
+        </doc>
+        <rule name = "in-use" on-failure = "precondition-failed">
+          <doc>
+            The server MUST NOT delete an exchange that has bindings on it, if the if-unused
+            field is true.
+          </doc>
+          <doc type = "scenario">
+            The client declares an exchange, binds a queue to it, then tries to delete it
+            setting if-unused to true.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "no-wait" domain = "no-wait" />
+    </method>
+
+    <method name = "delete-ok" synchronous = "1" index = "21"
+      label = "confirm deletion of an exchange">
+      <doc>This method confirms the deletion of an exchange.</doc>
+      <chassis name = "client" implement = "MUST" />
+    </method>
+  </class>
+
+  <!-- ==  QUEUE  ============================================================ -->
+
+  <class name = "queue" handler = "channel" index = "50" label = "work with queues">
+    <doc>
+      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
+      from publishers.
+    </doc>
+
+    <doc type = "grammar">
+      queue               = C:DECLARE  S:DECLARE-OK
+                          / C:BIND     S:BIND-OK
+                          / C:UNBIND   S:UNBIND-OK
+                          / C:PURGE    S:PURGE-OK
+                          / C:DELETE   S:DELETE-OK
+    </doc>
+
+    <chassis name = "server" implement = "MUST" />
+    <chassis name = "client" implement = "MUST" />
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "declare" synchronous = "1" index = "10" label = "declare queue, create if needed">
+      <doc>
+        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
+        contents, and the level of sharing for the queue.
+      </doc>
+
+      <rule name = "default-binding">
+        <doc>
+          The server MUST create a default binding for a newly-declared queue to the
+          default exchange, which is an exchange of type 'direct' and use the queue
+          name as the routing key.
+        </doc>
+        <doc type = "scenario">
+          Client declares a new queue, and then without explicitly binding it to an
+          exchange, attempts to send a message through the default exchange binding,
+          i.e. publish a message to the empty exchange, with the queue name as routing
+          key.
+        </doc>
+      </rule>
+
+      <rule name = "minimum-queues">
+        <doc>
+          The server SHOULD support a minimum of 256 queues per virtual host and ideally,
+          impose no limit except as defined by available resources.
+        </doc>
+        <doc type = "scenario">
+          Client attempts to declare as many queues as it can until the server reports
+          an error.  The resulting count must at least be 256.
+        </doc>
+      </rule>
+
+      <chassis name = "server" implement = "MUST" />
+      <response name = "declare-ok" />
+
+      <!-- Deprecated: "ticket", must be zero -->
+      <field name = "reserved-1" type = "short" reserved = "1" />
+
+      <field name = "queue" domain = "queue-name">
+        <rule name = "default-name">
+          <doc>
+            The queue name MAY be empty, in which case the server MUST create a new
+            queue with a unique generated name and return this to the client in the
+            Declare-Ok method.
+          </doc>
+          <doc type = "scenario">
+            Client attempts to declare several queues with an empty name. The client then
+            verifies that the server-assigned names are unique and different.
+          </doc>
+        </rule>
+        <rule name = "reserved" on-failure = "access-refused">
+          <doc>
+            Queue names starting with "amq." are reserved for pre-declared and
+            standardised queues. The client MAY declare a queue starting with
+            "amq." if the passive option is set, or the queue already exists.
+          </doc>
+          <doc type = "scenario">
+            The client attempts to declare a non-existing queue starting with
+            "amq." and with the passive option set to zero.
+          </doc>
+        </rule>
+        <rule name = "syntax" on-failure = "precondition-failed">
+          <doc>
+            The queue name can be empty, or a sequence of these characters:
+            letters, digits, hyphen, underscore, period, or colon.
+          </doc>
+          <doc type = "scenario">
+            The client attempts to declare a queue with an illegal name.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "passive" domain = "bit" label = "do not create queue">
+        <doc>
+          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
+          use this to check whether a queue exists without modifying the
+          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.
+          Arguments are compared for semantic equivalence.
+        </doc>
+        <rule name = "passive" on-failure = "not-found">
+          <doc>
+            The client MAY ask the server to assert that a queue exists without
+            creating the queue if not.  If the queue does not exist, the server
+            treats this as a failure.
+          </doc>
+          <doc type = "scenario">
+            Client declares an existing queue with the passive option and expects
+            the server to respond with a declare-ok. Client then attempts to declare
+            a non-existent queue with the passive option, and the server must close
+            the channel with the correct reply-code.
+          </doc>
+        </rule>
+        <rule name = "equivalent">
+          <doc>
+            If not set and the queue exists, the server MUST check that the
+            existing queue has the same values for durable, exclusive, auto-delete,
+            and arguments fields.  The server MUST respond with Declare-Ok if the
+            requested queue matches these fields, and MUST raise a channel exception
+            if not.
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "durable" domain = "bit" label = "request a durable queue">
+        <doc>
+          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) 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
+          persistent messages to a transient queue.
+        </doc>
+
+        <rule name = "persistence">
+          <doc>The server MUST recreate the durable queue after a restart.</doc>
+
+          <doc type = "scenario">
+            Client declares a durable queue. The server is then restarted. The client
+            then attempts to send a message to the queue. The message should be successfully
+            delivered.
+          </doc>
+        </rule>
+
+        <rule name = "types">
+          <doc>The server MUST support both durable and transient queues.</doc>
+          <doc type = "scenario">
+            A client declares two named queues, one durable and one transient.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "exclusive" domain = "bit" label = "request an exclusive queue">
+        <doc>
+          Exclusive queues may only be accessed by the current connection, and are
+          deleted when that connection closes.  Passive declaration of an exclusive
+          queue by other connections are not allowed.
+        </doc>
+
+        <rule name = "types">
+          <doc>
+            The server MUST support both exclusive (private) and non-exclusive (shared)
+            queues.
+          </doc>
+          <doc type = "scenario">
+            A client declares two named queues, one exclusive and one non-exclusive.
+          </doc>
+        </rule>
+
+        <rule name = "exclusive" on-failure = "resource-locked">
+          <doc>
+            The client MAY NOT attempt to use a queue that was declared as exclusive
+            by another still-open connection.
+          </doc>
+          <doc type = "scenario">
+            One client declares an exclusive queue. A second client on a different
+            connection attempts to declare, bind, consume, purge, delete, or declare
+            a queue of the same name.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "auto-delete" domain = "bit" label = "auto-delete queue when unused">
+        <doc>
+          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
+          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.
+        </doc>
+
+        <rule name = "pre-existence">
+          <doc>
+            The server MUST ignore the auto-delete field if the queue already exists.
+          </doc>
+          <doc type = "scenario">
+            Client declares two named queues, one as auto-delete and one explicit-delete.
+            Client then attempts to declare the two queues using the same names again,
+            but reversing the value of the auto-delete field in each case. Verify that the
+            queues still exist with the original auto-delete flag values.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "no-wait" domain = "no-wait" />
+
+      <field name = "arguments" domain = "table" label = "arguments for declaration">
+        <doc>
+          A set of arguments for the declaration. The syntax and semantics of these
+          arguments depends on the server implementation.
+        </doc>
+      </field>
+    </method>
+
+    <method name = "declare-ok" synchronous = "1" index = "11" label = "confirms a queue definition">
+      <doc>
+        This method confirms a Declare method and confirms the name of the queue, essential
+        for automatically-named queues.
+      </doc>
+
+      <chassis name = "client" implement = "MUST" />
+
+      <field name = "queue" domain = "queue-name">
+        <doc>
+          Reports the name of the queue. If the server generated a queue name, this field
+          contains that name.
+        </doc>
+        <assert check = "notnull" />
+      </field>
+
+      <field name = "message-count" domain = "message-count" />
+
+      <field name = "consumer-count" domain = "long" label = "number of consumers">
+        <doc>
+          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.
+        </doc>
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "bind" synchronous = "1" index = "20" label = "bind queue to an exchange">
+      <doc>
+        This method binds a queue to an exchange. Until a queue is bound it will not
+        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
+        exchange.
+      </doc>
+
+      <rule name = "duplicates">
+        <doc>
+          A server MUST allow ignore duplicate bindings - that is, two or more bind
+          methods for a specific queue, with identical arguments - without treating these
+          as an error.
+        </doc>
+        <doc type = "scenario">
+          A client binds a named queue to an exchange. The client then repeats the bind
+          (with identical arguments).
+        </doc>
+      </rule>
+
+      <rule name = "unique">
+        <doc>
+          A server MUST not deliver the same message more than once to a queue, even if
+          the queue has multiple bindings that match the message.
+        </doc>
+        <doc type = "scenario">
+          A client declares a named queue and binds it using multiple bindings to the
+          amq.topic exchange. The client then publishes a message that matches all its
+          bindings.
+        </doc>
+      </rule>
+
+      <rule name = "transient-exchange">
+        <doc>
+          The server MUST allow a durable queue to bind to a transient exchange.
+        </doc>
+        <doc type = "scenario">
+          A client declares a transient exchange. The client then declares a named durable
+          queue and then attempts to bind the transient exchange to the durable queue.
+        </doc>
+      </rule>
+
+      <rule name = "durable-exchange">
+        <doc>
+          Bindings of durable queues to durable exchanges are automatically durable
+          and the server MUST restore such bindings after a server restart.
+        </doc>
+        <doc type = "scenario">
+          A server declares a named durable queue and binds it to a durable exchange. The
+          server is restarted. The client then attempts to use the queue/exchange combination.
+        </doc>
+      </rule>
+
+      <rule name = "binding-count">
+        <doc>
+          The server SHOULD support at least 4 bindings per queue, and ideally, impose no
+          limit except as defined by available resources.
+        </doc>
+        <doc type = "scenario">
+          A client declares a named queue and attempts to bind it to 4 different
+          exchanges.
+        </doc>
+      </rule>
+
+      <chassis name = "server" implement = "MUST" />
+
+      <response name = "bind-ok" />
+
+      <!-- Deprecated: "ticket", must be zero -->
+      <field name = "reserved-1" type = "short" reserved = "1" />
+
+      <field name = "queue" domain = "queue-name">
+        <doc>Specifies the name of the queue to bind.</doc>
+        <rule name = "queue-known" on-failure = "not-found">
+          <doc>
+            The client MUST either specify a queue name or have previously declared a
+            queue on the same channel
+          </doc>
+          <doc type = "scenario">
+            The client opens a channel and attempts to bind an unnamed queue.
+          </doc>
+        </rule>
+        <rule name = "must-exist" on-failure = "not-found">
+          <doc>
+            The client MUST NOT attempt to bind a queue that does not exist.
+          </doc>
+          <doc type = "scenario">
+            The client attempts to bind a non-existent queue.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "exchange" domain = "exchange-name" label = "name of the exchange to bind to">
+        <rule name = "exchange-existence" on-failure = "not-found">
+          <doc>
+            A client MUST NOT be allowed to bind a queue to a non-existent exchange.
+          </doc>
+          <doc type = "scenario">
+            A client attempts to bind an named queue to a undeclared exchange.
+          </doc>
+        </rule>
+        <rule name = "default-exchange">
+          <doc>
+            The server MUST accept a blank exchange name to mean the default exchange.
+          </doc>
+          <doc type = "scenario">
+            The client declares a queue and binds it to a blank exchange name.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "routing-key" domain = "shortstr" label = "message routing key">
+        <doc>
+          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
+          routing key - refer to the specific exchange documentation.  If the queue name
+          is empty, the server uses the last queue declared on the channel.  If the
+          routing key is also empty, the server uses this queue name for the routing
+          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
+          routing keys depends on the exchange implementation.
+        </doc>
+        <rule name = "direct-exchange-key-matching">
+          <doc>
+            If a message queue binds to a direct exchange using routing key K and a
+            publisher sends the exchange a message with routing key R, then the message
+            MUST be passed to the message queue if K = R.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "no-wait" domain = "no-wait" />
+
+      <field name = "arguments" domain = "table" label = "arguments for binding">
+        <doc>
+          A set of arguments for the binding. The syntax and semantics of these arguments
+          depends on the exchange class.
+        </doc>
+      </field>
+    </method>
+
+    <method name = "bind-ok" synchronous = "1" index = "21" label = "confirm bind successful">
+      <doc>This method confirms that the bind was successful.</doc>
+
+      <chassis name = "client" implement = "MUST" />
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "unbind" synchronous = "1" index = "50" label = "unbind a queue from an exchange">
+      <doc>This method unbinds a queue from an exchange.</doc>
+      <rule name = "01">
+        <doc>If a unbind fails, the server MUST raise a connection exception.</doc>
+      </rule>
+      <chassis name="server" implement="MUST"/>
+      <response name="unbind-ok"/>
+
+      <!-- Deprecated: "ticket", must be zero -->
+      <field name = "reserved-1" type = "short" reserved = "1" />
+
+      <field name = "queue" domain = "queue-name">
+        <doc>Specifies the name of the queue to unbind.</doc>
+        <rule name = "queue-known" on-failure = "not-found">
+          <doc>
+            The client MUST either specify a queue name or have previously declared a
+            queue on the same channel
+          </doc>
+          <doc type = "scenario">
+            The client opens a channel and attempts to unbind an unnamed queue.
+          </doc>
+        </rule>
+        <rule name = "must-exist" on-failure = "not-found">
+          <doc>
+            The client MUST NOT attempt to unbind a queue that does not exist.
+          </doc>
+          <doc type = "scenario">
+            The client attempts to unbind a non-existent queue.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "exchange" domain = "exchange-name">
+        <doc>The name of the exchange to unbind from.</doc>
+        <rule name = "must-exist" on-failure = "not-found">
+          <doc>
+            The client MUST NOT attempt to unbind a queue from an exchange that
+            does not exist.
+          </doc>
+          <doc type = "scenario">
+            The client attempts to unbind a queue from a non-existent exchange.
+          </doc>
+        </rule>
+        <rule name = "default-exchange">
+          <doc>
+            The server MUST accept a blank exchange name to mean the default exchange.
+          </doc>
+          <doc type = "scenario">
+            The client declares a queue and binds it to a blank exchange name.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "routing-key" domain = "shortstr" label = "routing key of binding">
+        <doc>Specifies the routing key of the binding to unbind.</doc>
+      </field>
+
+      <field name = "arguments" domain = "table" label = "arguments of binding">
+        <doc>Specifies the arguments of the binding to unbind.</doc>
+      </field>
+    </method>
+
+    <method name = "unbind-ok" synchronous = "1" index = "51" label = "confirm unbind successful">
+      <doc>This method confirms that the unbind was successful.</doc>
+      <chassis name = "client" implement = "MUST"/>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "purge" synchronous = "1" index = "30" label = "purge a queue">
+      <doc>
+        This method removes all messages from a queue which are not awaiting
+        acknowledgment.
+      </doc>
+
+      <rule name = "02">
+        <doc>
+          The server MUST NOT purge messages that have already been sent to a client
+          but not yet acknowledged.
+        </doc>
+      </rule>
+
+      <rule name = "03">
+        <doc>
+          The server MAY implement a purge queue or log that allows system administrators
+          to recover accidentally-purged messages. The server SHOULD NOT keep purged
+          messages in the same storage spaces as the live messages since the volumes of
+          purged messages may get very large.
+        </doc>
+      </rule>
+
+      <chassis name = "server" implement = "MUST" />
+
+      <response name = "purge-ok" />
+
+      <!-- Deprecated: "ticket", must be zero -->
+      <field name = "reserved-1" type = "short" reserved = "1" />
+
+      <field name = "queue" domain = "queue-name">
+        <doc>Specifies the name of the queue to purge.</doc>
+        <rule name = "queue-known" on-failure = "not-found">
+          <doc>
+            The client MUST either specify a queue name or have previously declared a
+            queue on the same channel
+          </doc>
+          <doc type = "scenario">
+            The client opens a channel and attempts to purge an unnamed queue.
+          </doc>
+        </rule>
+        <rule name = "must-exist" on-failure = "not-found">
+          <doc>
+            The client MUST NOT attempt to purge a queue that does not exist.
+          </doc>
+          <doc type = "scenario">
+            The client attempts to purge a non-existent queue.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "no-wait" domain = "no-wait" />
+    </method>
+
+    <method name = "purge-ok" synchronous = "1" index = "31" label = "confirms a queue purge">
+      <doc>This method confirms the purge of a queue.</doc>
+
+      <chassis name = "client" implement = "MUST" />
+
+      <field name = "message-count" domain = "message-count">
+        <doc>
+          Reports the number of messages purged.
+        </doc>
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "delete" synchronous = "1" index = "40" label = "delete a queue">
+      <doc>
+        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
+        consumers on the queue are cancelled.
+      </doc>
+
+      <rule name = "01">
+        <doc>
+          The server SHOULD use a dead-letter queue to hold messages that were pending on
+          a deleted queue, and MAY provide facilities for a system administrator to move
+          these messages back to an active queue.
+        </doc>
+      </rule>
+
+      <chassis name = "server" implement = "MUST" />
+
+      <response name = "delete-ok" />
+
+      <!-- Deprecated: "ticket", must be zero -->
+      <field name = "reserved-1" type = "short" reserved = "1" />
+
+      <field name = "queue" domain = "queue-name">
+        <doc>Specifies the name of the queue to delete.</doc>
+        <rule name = "queue-known" on-failure = "not-found">
+          <doc>
+            The client MUST either specify a queue name or have previously declared a
+            queue on the same channel
+          </doc>
+          <doc type = "scenario">
+            The client opens a channel and attempts to delete an unnamed queue.
+          </doc>
+        </rule>
+        <rule name = "must-exist" on-failure = "not-found">
+          <doc>
+            The client MUST NOT attempt to delete a queue that does not exist.
+          </doc>
+          <doc type = "scenario">
+            The client attempts to delete a non-existent queue.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "if-unused" domain = "bit" label = "delete only if unused">
+        <doc>
+          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
+          exception instead.
+        </doc>
+        <rule name = "in-use" on-failure = "precondition-failed">
+          <doc>
+            The server MUST NOT delete a queue that has consumers on it, if the if-unused
+            field is true.
+          </doc>
+          <doc type = "scenario">
+            The client declares a queue, and consumes from it, then tries to delete it
+            setting if-unused to true.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "if-empty" domain = "bit" label = "delete only if empty">
+        <doc>
+          If set, the server will only delete the queue if it has no messages.
+        </doc>
+        <rule name = "not-empty" on-failure = "precondition-failed">
+          <doc>
+            The server MUST NOT delete a queue that has messages on it, if the
+            if-empty field is true.
+          </doc>
+          <doc type = "scenario">
+            The client declares a queue, binds it and publishes some messages into it,
+            then tries to delete it setting if-empty to true.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "no-wait" domain = "no-wait" />
+    </method>
+
+    <method name = "delete-ok" synchronous = "1" index = "41" label = "confirm deletion of a queue">
+      <doc>This method confirms the deletion of a queue.</doc>
+
+      <chassis name = "client" implement = "MUST" />
+
+      <field name = "message-count" domain = "message-count">
+        <doc>Reports the number of messages deleted.</doc>
+      </field>
+    </method>
+  </class>
+
+  <!-- ==  BASIC  ============================================================ -->
+
+  <class name = "basic" handler = "channel" index = "60" label = "work with basic content">
+    <doc>
+      The Basic class provides methods that support an industry-standard messaging model.
+    </doc>
+
+    <doc type = "grammar">
+      basic               = C:QOS S:QOS-OK
+                          / C:CONSUME S:CONSUME-OK
+                          / C:CANCEL S:CANCEL-OK
+                          / C:PUBLISH content
+                          / S:RETURN content
+                          / S:DELIVER content
+                          / C:GET ( S:GET-OK content / S:GET-EMPTY )
+                          / C:ACK
+                          / C:REJECT
+                          / C:RECOVER-ASYNC
+                          / C:RECOVER S:RECOVER-OK
+    </doc>
+
+    <chassis name = "server" implement = "MUST" />
+    <chassis name = "client" implement = "MAY" />
+
+    <rule name = "01">
+      <doc>
+        The server SHOULD respect the persistent property of basic messages and
+        SHOULD make a best-effort to hold persistent basic messages on a reliable
+        storage mechanism.
+      </doc>
+      <doc type = "scenario">
+        Send a persistent message to queue, stop server, restart server and then
+        verify whether message is still present.  Assumes that queues are durable.
+        Persistence without durable queues makes no sense.
+      </doc>
+    </rule>
+
+    <rule name = "02">
+      <doc>
+        The server MUST NOT discard a persistent basic message in case of a queue
+        overflow.
+      </doc>
+      <doc type = "scenario">
+        Declare a queue overflow situation with persistent messages and verify that
+        messages do not get lost (presumably the server will write them to disk).
+      </doc>
+    </rule>
+
+    <rule name = "03">
+      <doc>
+        The server MAY use the Channel.Flow method to slow or stop a basic message
+        publisher when necessary.
+      </doc>
+      <doc type = "scenario">
+        Declare a queue overflow situation with non-persistent messages and verify
+        whether the server responds with Channel.Flow or not. Repeat with persistent
+        messages.
+      </doc>
+    </rule>
+
+    <rule name = "04">
+      <doc>
+        The server MAY overflow non-persistent basic messages to persistent
+        storage.
+      </doc>
+      <!-- Test scenario: untestable -->
+    </rule>
+
+    <rule name = "05">
+      <doc>
+        The server MAY discard or dead-letter non-persistent basic messages on a
+        priority basis if the queue size exceeds some configured limit.
+      </doc>
+      <!-- Test scenario: untestable -->
+    </rule>
+
+    <rule name = "06">
+      <doc>
+        The server MUST implement at least 2 priority levels for basic messages,
+        where priorities 0-4 and 5-9 are treated as two distinct levels.
+      </doc>
+      <doc type = "scenario">
+        Send a number of priority 0 messages to a queue. Send one priority 9
+        message.  Consume messages from the queue and verify that the first message
+        received was priority 9.
+      </doc>
+    </rule>
+
+    <rule name = "07">
+      <doc>
+        The server MAY implement up to 10 priority levels.
+      </doc>
+      <doc type = "scenario">
+        Send a number of messages with mixed priorities to a queue, so that all
+        priority values from 0 to 9 are exercised. A good scenario would be ten
+        messages in low-to-high priority.  Consume from queue and verify how many
+        priority levels emerge.
+      </doc>
+    </rule>
+
+    <rule name = "08">
+      <doc>
+        The server MUST deliver messages of the same priority in order irrespective of
+        their individual persistence.
+      </doc>
+      <doc type = "scenario">
+        Send a set of messages with the same priority but different persistence
+        settings to a queue.  Consume and verify that messages arrive in same order
+        as originally published.
+      </doc>
+    </rule>
+
+    <rule name = "09">
+      <doc>
+        The server MUST support un-acknowledged delivery of Basic content, i.e.
+        consumers with the no-ack field set to TRUE.
+      </doc>
+    </rule>
+
+    <rule name = "10">
+      <doc>
+        The server MUST support explicitly acknowledged delivery of Basic content,
+        i.e. consumers with the no-ack field set to FALSE.
+      </doc>
+      <doc type = "scenario">
+        Declare a queue and a consumer using explicit acknowledgements.  Publish a
+        set of messages to the queue.  Consume the messages but acknowledge only
+        half of them.  Disconnect and reconnect, and consume from the queue.
+        Verify that the remaining messages are received.
+      </doc>
+    </rule>
+
+    <!--  These are the properties for a Basic content  -->
+
+    <!--  MIME typing -->
+    <field name = "content-type"    domain = "shortstr"   label = "MIME content type" />
+    <!--  MIME typing -->
+    <field name = "content-encoding" domain = "shortstr"  label = "MIME content encoding" />
+    <!--  For applications, and for header exchange routing -->
+    <field name = "headers"         domain = "table"      label = "message header field table" />
+    <!--  For queues that implement persistence -->
+    <field name = "delivery-mode"   domain = "octet"      label = "non-persistent (1) or persistent (2)" />
+    <!--  For queues that implement priorities -->
+    <field name = "priority"        domain = "octet"      label = "message priority, 0 to 9" />
+    <!--  For application use, no formal behaviour -->
+    <field name = "correlation-id"  domain = "shortstr"   label = "application correlation identifier" />
+    <!--  For application use, no formal behaviour but may hold the
+          name of a private response queue, when used in request messages -->
+    <field name = "reply-to"        domain = "shortstr"   label = "address to reply to" />
+    <!--  For implementation use, no formal behaviour -->
+    <field name = "expiration"      domain = "shortstr"   label = "message expiration specification" />
+    <!--  For application use, no formal behaviour -->
+    <field name = "message-id"      domain = "shortstr"   label = "application message identifier" />
+    <!--  For application use, no formal behaviour -->
+    <field name = "timestamp"       domain = "timestamp"  label = "message timestamp" />
+    <!--  For application use, no formal behaviour -->
+    <field name = "type"            domain = "shortstr"   label = "message type name" />
+    <!--  For application use, no formal behaviour -->
+    <field name = "user-id"         domain = "shortstr"   label = "creating user id" />
+    <!--  For application use, no formal behaviour -->
+    <field name = "app-id"          domain = "shortstr"   label = "creating application id" />
+    <!--  Deprecated, was old cluster-id property -->
+    <field name = "reserved"        domain = "shortstr"   label = "reserved, must be empty" />
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "qos" synchronous = "1" index = "10" label = "specify quality of service">
+      <doc>
+        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
+        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
+        for the server.
+      </doc>
+
+      <chassis name = "server" implement = "MUST" />
+      <response name = "qos-ok" />
+
+      <field name = "prefetch-size" domain = "long" label = "prefetch window in octets">
+        <doc>
+          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,
+          rather than needing to be sent down the channel. Prefetching gives a performance
+          improvement. This field specifies the prefetch window size in octets. The server
+          will send a message in advance if it is equal to or smaller in size than the
+          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
+          apply. The prefetch-size is ignored if the no-ack option is set.
+        </doc>
+        <rule name = "01">
+          <doc>
+            The server MUST ignore this setting when the client is not processing any
+            messages - i.e. the prefetch size does not limit the transfer of single
+            messages to a client, only the sending in advance of more messages while
+            the client still has one or more unacknowledged messages.
+          </doc>
+          <doc type = "scenario">
+            Define a QoS prefetch-size limit and send a single message that exceeds
+            that limit.  Verify that the message arrives correctly.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "prefetch-count" domain = "short" label = "prefetch window in messages">
+        <doc>
+          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
+          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.
+        </doc>
+        <rule name = "01">
+          <doc>
+            The server may send less data in advance than allowed by the client's
+            specified prefetch windows but it MUST NOT send more.
+          </doc>
+          <doc type = "scenario">
+            Define a QoS prefetch-size limit and a prefetch-count limit greater than
+            one.  Send multiple messages that exceed the prefetch size.  Verify that
+            no more than one message arrives at once.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "global" domain = "bit" label = "apply to entire connection">
+        <doc>
+          By default the QoS settings apply to the current channel only. If this field is
+          set, they are applied to the entire connection.
+        </doc>
+      </field>
+    </method>
+
+    <method name = "qos-ok" synchronous = "1" index = "11" label = "confirm the requested qos">
+      <doc>
+        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
+        defined.
+      </doc>
+      <chassis name = "client" implement = "MUST" />
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "consume" synchronous = "1" index = "20" label = "start a queue consumer">
+      <doc>
+        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
+        declared on, or until the client cancels them.
+      </doc>
+
+      <rule name = "01">
+        <doc>
+          The server SHOULD support at least 16 consumers per queue, and ideally, impose
+          no limit except as defined by available resources.
+        </doc>
+        <doc type = "scenario">
+          Declare a queue and create consumers on that queue until the server closes the
+          connection. Verify that the number of consumers created was at least sixteen
+          and report the total number.
+        </doc>
+      </rule>
+
+      <chassis name = "server" implement = "MUST" />
+      <response name = "consume-ok" />
+
+      <!-- Deprecated: "ticket", must be zero -->
+      <field name = "reserved-1" type = "short" reserved = "1" />
+
+      <field name = "queue" domain = "queue-name">
+        <doc>Specifies the name of the queue to consume from.</doc>
+      </field>
+
+      <field name = "consumer-tag" domain = "consumer-tag">
+        <doc>
+          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
+          empty the server will generate a unique tag.
+        </doc>
+        <rule name = "01" on-failure = "not-allowed">
+          <doc>
+            The client MUST NOT specify a tag that refers to an existing consumer.
+          </doc>
+          <doc type = "scenario">
+            Attempt to create two consumers with the same non-empty tag, on the
+            same channel.
+          </doc>
+        </rule>
+        <rule name = "02" on-failure = "not-allowed">
+          <doc>
+            The consumer tag is valid only within the channel from which the
+            consumer was created. I.e. a client MUST NOT create a consumer in one
+            channel and then use it in another.
+          </doc>
+          <doc type = "scenario">
+            Attempt to create a consumer in one channel, then use in another channel,
+            in which consumers have also been created (to test that the server uses
+            unique consumer tags).
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "no-local" domain = "no-local" />
+
+      <field name = "no-ack" domain = "no-ack" />
+
+      <field name = "exclusive" domain = "bit" label = "request exclusive access">
+        <doc>
+          Request exclusive consumer access, meaning only this consumer can access the
+          queue.
+        </doc>
+
+        <rule name = "01" on-failure = "access-refused">
+          <doc>
+            The client MAY NOT gain exclusive access to a queue that already has
+            active consumers.
+          </doc>
+          <doc type = "scenario">
+            Open two connections to a server, and in one connection declare a shared
+            (non-exclusive) queue and then consume from the queue.  In the second
+            connection attempt to consume from the same queue using the exclusive
+            option.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "no-wait" domain = "no-wait" />
+
+      <field name = "arguments" domain = "table" label = "arguments for declaration">
+        <doc>
+          A set of arguments for the consume. The syntax and semantics of these
+          arguments depends on the server implementation.
+        </doc>
+      </field>
+    </method>
+
+    <method name = "consume-ok" synchronous = "1" index = "21" label = "confirm a new consumer">
+      <doc>
+        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.
+      </doc>
+      <chassis name = "client" implement = "MUST" />
+      <field name = "consumer-tag" domain = "consumer-tag">
+        <doc>
+          Holds the consumer tag specified by the client or provided by the server.
+        </doc>
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "cancel" synchronous = "1" index = "30" label = "end a queue consumer">
+      <doc>
+        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
+        that consumer. The client may receive an arbitrary number of messages in
+        between sending the cancel method and receiving the cancel-ok reply.
+      </doc>
+
+      <rule name = "01">
+        <doc>
+          If the queue does not exist the server MUST ignore the cancel method, so
+          long as the consumer tag is valid for that channel.
+        </doc>
+        <doc type = "scenario">
+          TODO.
+        </doc>
+      </rule>
+
+      <chassis name = "server" implement = "MUST" />
+      <response name = "cancel-ok" />
+
+      <field name = "consumer-tag" domain = "consumer-tag" />
+      <field name = "no-wait" domain = "no-wait" />
+    </method>
+
+    <method name = "cancel-ok" synchronous = "1" index = "31" label = "confirm a cancelled consumer">
+      <doc>
+        This method confirms that the cancellation was completed.
+      </doc>
+      <chassis name = "client" implement = "MUST" />
+      <field name = "consumer-tag" domain = "consumer-tag" />
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "publish" content = "1" index = "40" label = "publish a message">
+      <doc>
+        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
+        consumers when the transaction, if any, is committed.
+      </doc>
+
+      <chassis name = "server" implement = "MUST" />
+
+      <!-- Deprecated: "ticket", must be zero -->
+      <field name = "reserved-1" type = "short" reserved = "1" />
+
+      <field name = "exchange" domain = "exchange-name">
+        <doc>
+          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
+          exchange does not exist, the server will raise a channel exception.
+        </doc>
+
+        <rule name = "must-exist" on-failure = "not-found">
+          <doc>
+            The client MUST NOT attempt to publish a content to an exchange that
+            does not exist.
+          </doc>
+          <doc type = "scenario">
+            The client attempts to publish a content to a non-existent exchange.
+          </doc>
+        </rule>
+        <rule name = "default-exchange">
+          <doc>
+            The server MUST accept a blank exchange name to mean the default exchange.
+          </doc>
+          <doc type = "scenario">
+            The client declares a queue and binds it to a blank exchange name.
+          </doc>
+        </rule>
+        <rule name = "02">
+          <doc>
+            If the exchange was declared as an internal exchange, the server MUST raise
+            a channel exception with a reply code 403 (access refused).
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+
+        <rule name = "03">
+          <doc>
+            The exchange MAY refuse basic content in which case it MUST raise a channel
+            exception with reply code 540 (not implemented).
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "routing-key" domain = "shortstr" label = "Message routing key">
+        <doc>
+          Specifies the routing key for the message. The routing key is used for routing
+          messages depending on the exchange configuration.
+        </doc>
+      </field>
+
+      <field name = "mandatory" domain = "bit" label = "indicate mandatory routing">
+        <doc>
+          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
+          Return method. If this flag is zero, the server silently drops the message.
+        </doc>
+
+        <rule name = "01">
+          <doc>
+            The server SHOULD implement the mandatory flag.
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+      </field>
+
+      <field name = "immediate" domain = "bit" label = "request immediate delivery">
+        <doc>
+          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
+          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.
+        </doc>
+
+        <rule name = "01">
+          <doc>
+            The server SHOULD implement the immediate flag.
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+      </field>
+    </method>
+
+    <method name = "return" content = "1" index = "50" label = "return a failed message">
+      <doc>
+        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
+        reply code and text provide information about the reason that the message was
+        undeliverable.
+      </doc>
+
+      <chassis name = "client" implement = "MUST" />
+
+      <field name = "reply-code" domain = "reply-code" />
+      <field name = "reply-text" domain = "reply-text" />
+
+      <field name = "exchange" domain = "exchange-name">
+        <doc>
+          Specifies the name of the exchange that the message was originally published
+          to.  May be empty, meaning the default exchange.
+        </doc>
+      </field>
+
+      <field name = "routing-key" domain = "shortstr" label = "Message routing key">
+        <doc>
+          Specifies the routing key name specified when the message was published.
+        </doc>
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "deliver" content = "1" index = "60"
+      label = "notify the client of a consumer message">
+      <doc>
+        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
+        the server responds with Deliver methods as and when messages arrive for that
+        consumer.
+      </doc>
+
+      <rule name = "01">
+        <doc>
+          The server SHOULD track the number of times a message has been delivered to
+          clients and when a message is redelivered a certain number of times - e.g. 5
+          times - without being acknowledged, the server SHOULD consider the message to be
+          unprocessable (possibly causing client applications to abort), and move the
+          message to a dead letter queue.
+        </doc>
+        <doc type = "scenario">
+          TODO.
+        </doc>
+      </rule>
+
+      <chassis name = "client" implement = "MUST" />
+
+      <field name = "consumer-tag" domain = "consumer-tag" />
+      <field name = "delivery-tag" domain = "delivery-tag" />
+      <field name = "redelivered" domain = "redelivered" />
+
+      <field name = "exchange" domain = "exchange-name">
+        <doc>
+          Specifies the name of the exchange that the message was originally published to.
+          May be empty, indicating the default exchange.
+        </doc>
+      </field>
+
+      <field name = "routing-key" domain = "shortstr" label = "Message routing key">
+        <doc>Specifies the routing key name specified when the message was published.</doc>
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "get" synchronous = "1" index = "70" label = "direct access to a queue">
+      <doc>
+        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
+        functionality is more important than performance.
+      </doc>
+
+      <response name = "get-ok" />
+      <response name = "get-empty" />
+      <chassis name = "server" implement = "MUST" />
+
+      <!-- Deprecated: "ticket", must be zero -->
+      <field name = "reserved-1" type = "short" reserved = "1" />
+
+      <field name = "queue" domain = "queue-name">
+        <doc>Specifies the name of the queue to get a message from.</doc>
+      </field>
+      <field name = "no-ack" domain = "no-ack" />
+    </method>
+
+    <method name = "get-ok" synchronous = "1" content = "1" index = "71"
+      label = "provide client with a message">
+      <doc>
+        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
+        get method.
+      </doc>
+
+      <chassis name = "client" implement = "MAY" />
+
+      <field name = "delivery-tag" domain = "delivery-tag" />
+      <field name = "redelivered" domain = "redelivered" />
+      <field name = "exchange" domain = "exchange-name">
+        <doc>
+          Specifies the name of the exchange that the message was originally published to.
+          If empty, the message was published to the default exchange.
+        </doc>
+      </field>
+
+      <field name = "routing-key" domain = "shortstr" label = "Message routing key">
+        <doc>Specifies the routing key name specified when the message was published.</doc>
+      </field>
+
+      <field name = "message-count" domain = "message-count" />
+    </method>
+
+    <method name = "get-empty" synchronous = "1" index = "72"
+      label = "indicate no messages available">
+      <doc>
+        This method tells the client that the queue has no messages available for the
+        client.
+      </doc>
+      <chassis name = "client" implement = "MAY" />
+      <!-- Deprecated: "cluster-id", must be empty -->
+      <field name = "reserved-1" type = "shortstr" reserved = "1" />
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "ack" index = "80" label = "acknowledge one or more messages">
+      <doc>
+        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
+        and including a specific message.
+      </doc>
+
+      <chassis name = "server" implement = "MUST" />
+
+      <field name = "delivery-tag" domain = "delivery-tag" />
+      <field name = "multiple" domain = "bit" label = "acknowledge multiple messages">
+        <doc>
+          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,
+          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.
+        </doc>
+        <rule name = "exists" on-failure = "precondition-failed">
+          <doc>
+            The server MUST validate that a non-zero delivery-tag refers to a delivered
+            message, and raise a channel exception if this is not the case.  On a transacted
+            channel, this check MUST be done immediately and not delayed until a Tx.Commit.
+            Specifically, a client MUST not acknowledge the same message more than once.
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "reject" index = "90" label = "reject an incoming message">
+      <doc>
+        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
+        queue.
+      </doc>
+
+      <rule name = "01">
+        <doc>
+          The server SHOULD be capable of accepting and process the Reject method while
+          sending message content with a Deliver or Get-Ok method. I.e. the server should
+          read and process incoming methods while sending output frames. To cancel a
+          partially-send content, the server sends a content body frame of size 1 (i.e.
+          with no data except the frame-end octet).
+        </doc>
+      </rule>
+
+      <rule name = "02">
+        <doc>
+          The server SHOULD interpret this method as meaning that the client is unable to
+          process the message at this time.
+        </doc>
+        <doc type = "scenario">
+          TODO.
+        </doc>
+      </rule>
+
+      <rule name = "03">
+        <doc>
+          The client MUST NOT use this method as a means of selecting messages to process.
+        </doc>
+        <doc type = "scenario">
+          TODO.
+        </doc>
+      </rule>
+
+      <chassis name = "server" implement = "MUST" />
+
+      <field name = "delivery-tag" domain = "delivery-tag" />
+
+      <field name = "requeue" domain = "bit" label = "requeue the message">
+        <doc>
+          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.
+        </doc>
+
+        <rule name = "01">
+          <doc>
+            The server MUST NOT deliver the message to the same client within the
+            context of the current channel. The recommended strategy is to attempt to
+            deliver the message to an alternative consumer, and if that is not possible,
+            to move the message to a dead-letter queue. The server MAY use more
+            sophisticated tracking to hold the message on the queue and redeliver it to
+            the same client at a later stage.
+          </doc>
+          <doc type = "scenario">
+            TODO.
+          </doc>
+        </rule>
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "recover-async" index = "100" label = "redeliver unacknowledged messages"
+        deprecated = "1">
+      <doc>
+        This method asks the server to redeliver all unacknowledged messages on a
+        specified channel. Zero or more messages may be redelivered.  This method
+        is deprecated in favour of the synchronous Recover/Recover-Ok.
+      </doc>
+      <rule name = "01">
+        <doc>
+          The server MUST set the redelivered flag on all messages that are resent.
+        </doc>
+        <doc type = "scenario">
+          TODO.
+        </doc>
+      </rule>
+      <chassis name = "server" implement = "MAY" />
+      <field name = "requeue" domain = "bit" label = "requeue the message">
+        <doc>
+          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,
+          potentially then delivering it to an alternative subscriber.
+        </doc>
+      </field>
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "recover" index = "110" label = "redeliver unacknowledged messages">
+      <doc>
+        This method asks the server to redeliver all unacknowledged messages on a
+        specified channel. Zero or more messages may be redelivered.  This method
+        replaces the asynchronous Recover.
+      </doc>
+      <rule name = "01">
+        <doc>
+          The server MUST set the redelivered flag on all messages that are resent.
+        </doc>
+        <doc type = "scenario">
+          TODO.
+        </doc>
+      </rule>
+      <chassis name = "server" implement = "MUST" />
+      <field name = "requeue" domain = "bit" label = "requeue the message">
+        <doc>
+          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,
+          potentially then delivering it to an alternative subscriber.
+        </doc>
+      </field>
+    </method>
+
+    <method name = "recover-ok" synchronous = "1" index = "111" label = "confirm recovery">
+      <doc>
+        This method acknowledges a Basic.Recover method.
+      </doc>
+      <chassis name = "client" implement = "MUST" />
+    </method>
+  </class>
+
+  <!-- ==  TX  =============================================================== -->
+
+  <class name = "tx" handler = "channel" index = "90" label = "work with transactions">
+    <doc>
+      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
+      within a transaction will complete successfully or none of them will.
+      Servers SHOULD implement atomic transactions at least where all publish
+      or ack requests affect a single queue.  Transactions that cover multiple
+      queues may be non-atomic, given that queues can be created and destroyed
+      asynchronously, and such events do not form part of any transaction.
+      Further, the behaviour of transactions with respect to the immediate and
+      mandatory flags on Basic.Publish methods is not defined.
+    </doc>
+
+    <rule name = "not multiple queues">
+      <doc>
+      Applications MUST NOT rely on the atomicity of transactions that
+      affect more than one queue.
+      </doc>
+    </rule>
+    <rule name = "not immediate">
+      <doc>
+      Applications MUST NOT rely on the behaviour of transactions that
+      include messages published with the immediate option.
+      </doc>
+    </rule>
+    <rule name = "not mandatory">
+      <doc>
+      Applications MUST NOT rely on the behaviour of transactions that
+      include messages published with the mandatory option.
+      </doc>
+    </rule>
+
+    <doc type = "grammar">
+      tx                  = C:SELECT S:SELECT-OK
+                          / C:COMMIT S:COMMIT-OK
+                          / C:ROLLBACK S:ROLLBACK-OK
+    </doc>
+
+    <chassis name = "server" implement = "SHOULD" />
+    <chassis name = "client" implement = "MAY" />
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "select" synchronous = "1" index = "10" label = "select standard transaction mode">
+      <doc>
+        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.
+      </doc>
+      <chassis name = "server" implement = "MUST" />
+      <response name = "select-ok" />
+    </method>
+
+    <method name = "select-ok" synchronous = "1" index = "11" label = "confirm transaction mode">
+      <doc>
+        This method confirms to the client that the channel was successfully set to use
+        standard transactions.
+      </doc>
+      <chassis name = "client" implement = "MUST" />
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "commit" synchronous = "1" index = "20" label = "commit the current transaction">
+      <doc>
+        This method commits all message publications and acknowledgments performed in
+        the current transaction.  A new transaction starts immediately after a commit.
+      </doc>
+      <chassis name = "server" implement = "MUST" />
+      <response name = "commit-ok" />
+
+      <rule name = "transacted" on-failure = "precondition-failed">
+        <doc>
+          The client MUST NOT use the Commit method on non-transacted channels.
+        </doc>
+        <doc type = "scenario">
+          The client opens a channel and then uses Tx.Commit.
+        </doc>
+      </rule>
+    </method>
+
+    <method name = "commit-ok" synchronous = "1" index = "21" label = "confirm a successful commit">
+      <doc>
+        This method confirms to the client that the commit succeeded. Note that if a commit
+        fails, the server raises a channel exception.
+      </doc>
+      <chassis name = "client" implement = "MUST" />
+    </method>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <method name = "rollback" synchronous = "1" index = "30"
+      label = "abandon the current transaction">
+      <doc>
+        This method abandons all message publications and acknowledgments performed in
+        the current transaction. A new transaction starts immediately after a rollback.
+        Note that unacked messages will not be automatically redelivered by rollback;
+        if that is required an explicit recover call should be issued.
+      </doc>
+      <chassis name = "server" implement = "MUST" />
+      <response name = "rollback-ok" />
+
+      <rule name = "transacted" on-failure = "precondition-failed">
+        <doc>
+          The client MUST NOT use the Rollback method on non-transacted channels.
+        </doc>
+        <doc type = "scenario">
+          The client opens a channel and then uses Tx.Rollback.
+        </doc>
+      </rule>
+    </method>
+
+    <method name = "rollback-ok" synchronous = "1" index = "31" label = "confirm successful rollback">
+      <doc>
+        This method confirms to the client that the rollback succeeded. Note that if an
+        rollback fails, the server raises a channel exception.
+      </doc>
+      <chassis name = "client" implement = "MUST" />
+    </method>
+  </class>
+
+</amqp>
diff --git a/setup.cfg b/setup.cfg
index 81af2bb8d76f960564e3394a03f6f2b6c290ffe9..cdd41f09fc5bca6d9a07e432abcb45b64a7ea694 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -3,3 +3,6 @@ description-file = README.md
 
 [pycodestyle]
 max-line-length=120
+
+[bdist_wheel]
+universal=1
diff --git a/setup.py b/setup.py
index fe3b9ae87d3a24da0152382bc308d61695e80e82..c438ee99013b6c7f331015190563f5e4756e816c 100644
--- a/setup.py
+++ b/setup.py
@@ -2,8 +2,9 @@
 # coding=UTF-8
 from setuptools import setup
 
+
 setup(name='CoolAMQP',
-      version='0.12',
+      version='0.8',
       description='AMQP client with sane reconnects',
       author='DMS Serwis s.c.',
       author_email='piotrm@smok.co',
@@ -12,7 +13,12 @@ setup(name='CoolAMQP',
       keywords=['amqp', 'pyamqp', 'rabbitmq', 'client', 'network', 'ha', 'high availability'],
       packages=[
           'coolamqp',
-          'coolamqp.backends'
+          'coolamqp.backends',
+          'coolamqp.uplink',
+          'coolamqp.uplink.framing',
+          'coolamqp.uplink.framing.compilation',
+          'coolamqp.uplink.streams',
+
       ],
       license='MIT License',
       long_description=u'The AMQP client that handles reconnection madness for you',
diff --git a/tests/run.py b/tests/run.py
new file mode 100644
index 0000000000000000000000000000000000000000..f7fe4c6077fe797bf56791b0a4d71251980a309b
--- /dev/null
+++ b/tests/run.py
@@ -0,0 +1,24 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+import time, logging, threading
+from coolamqp.objects import Message, MessageProperties, NodeDefinition, Queue
+from coolamqp.clustering import Cluster
+
+import time
+
+
+NODE = NodeDefinition('127.0.0.1', 'user', 'user', heartbeat=20)
+logging.basicConfig(level=logging.DEBUG)
+
+if __name__ == '__main__':
+    amqp = Cluster([NODE])
+    amqp.start(wait=True)
+
+
+    c1 = amqp.consume(Queue(b'siema-eniu', exclusive=True), qos=(None, 20))
+    c2 = amqp.consume(Queue(b'jo-malina', exclusive=True))
+
+    while True:
+        time.sleep(30)
+
+    amqp.shutdown(True)
diff --git a/tests/test_basics.py b/tests/test_basics.py
deleted file mode 100644
index b0051c672514ac182a59d92607b706d6382d9a39..0000000000000000000000000000000000000000
--- a/tests/test_basics.py
+++ /dev/null
@@ -1,139 +0,0 @@
-# coding=UTF-8
-from __future__ import absolute_import, division, print_function
-import six
-
-from coolamqp import Cluster, ClusterNode, Queue, MessageReceived, ConnectionUp, ConsumerCancelled, Message, Exchange
-
-from tests.utils import getamqp, CoolAMQPTestCase
-
-class TestThings(CoolAMQPTestCase):
-    INIT_AMQP = False
-
-    def test_different_constructor_for_clusternode(self):
-        cn = ClusterNode(host='127.0.0.1', user='guest', password='guest', virtual_host='/')
-        amqp = Cluster([cn])
-        amqp.start()
-        self.assertIsInstance(amqp.drain(1), ConnectionUp)
-        amqp.shutdown()
-
-
-#todo discard on fail needs tests
-
-class TestBasics(CoolAMQPTestCase):
-
-    def test_acknowledge(self):
-        myq = Queue('myqueue', exclusive=True)
-
-        self.amqp.consume(myq)
-        self.amqp.send(Message(b'what the fuck'), '', routing_key='myqueue')
-
-        p = self.drainTo(MessageReceived, 4)
-        self.assertEquals(p.message.body, b'what the fuck')
-        self.assertIsInstance(p.message.body, six.binary_type)
-        p.message.ack()
-
-        self.assertIs(self.amqp.drain(wait=1), None)
-
-    def test_send_bullshit(self):
-        self.assertRaises(TypeError, lambda: Message(u'what the fuck'))
-
-    def test_send_nonobvious_bullshit(self):
-        self.assertEquals(Message(bytearray(b'what the fuck')).body, b'what the fuck')
-
-    def test_nacknowledge(self):
-        myq = Queue('myqueue', exclusive=True)
-
-        self.amqp.consume(myq)
-        self.amqp.send(Message(b'what the fuck'), '', routing_key='myqueue')
-
-        p = self.drainTo(MessageReceived, 4)
-        self.assertEquals(p.message.body, b'what the fuck')
-        p.message.nack()
-
-        p = self.drainTo(MessageReceived, 4)
-        self.assertEquals(p.message.body, b'what the fuck')
-
-    def test_bug_hangs(self):
-        p = Queue('lol', exclusive=True)
-        self.amqp.consume(p)
-        self.amqp.consume(p).result()
-
-    def test_consume_declare(self):
-        """Spawn a second connection. One declares an exclusive queue, other tries to consume from it"""
-        with self.new_amqp_connection() as amqp2:
-
-            has_failed = {'has_failed': False}
-
-            self.amqp.declare_queue(Queue('lol', exclusive=True)).result()
-            amqp2.consume(Queue('lol', exclusive=True), on_failed=lambda e: has_failed.update({'has_failed': True})).result()
-
-            self.assertTrue(has_failed['has_failed'])
-
-    def test_qos(self):
-        self.amqp.qos(0, 1)
-
-        self.amqp.consume(Queue('lol', exclusive=True)).result()
-        self.amqp.send(Message(b'what the fuck'), '', routing_key='lol')
-        self.amqp.send(Message(b'what the fuck'), '', routing_key='lol')
-
-        p = self.drainTo(MessageReceived, 4)
-
-        self.drainToNone(5)
-        p.message.ack()
-        self.assertIsInstance(self.amqp.drain(wait=4), MessageReceived)
-
-    def test_consume_twice(self):
-        """Spawn a second connection and try to consume an exclusive queue twice"""
-        with self.new_amqp_connection() as amqp2:
-
-            has_failed = {'has_failed': False}
-
-            self.amqp.consume(Queue('lol', exclusive=True)).result()
-            amqp2.consume(Queue('lol', exclusive=True), on_failed=lambda e: has_failed.update({'has_failed': True})).result()
-
-            self.assertTrue(has_failed['has_failed'])
-
-    def test_send_and_receive(self):
-        myq = Queue('myqueue', exclusive=True)
-
-        self.amqp.consume(myq)
-        self.amqp.send(Message(b'what the fuck'), '', routing_key='myqueue')
-
-        self.assertEquals(self.drainTo(MessageReceived, 3).message.body, b'what the fuck')
-
-    def test_consumer_cancelled_on_queue_deletion(self):
-        myq = Queue('myqueue', exclusive=True)
-
-        self.amqp.consume(myq)
-        self.amqp.delete_queue(myq)
-
-        self.assertEquals(self.drainTo(ConsumerCancelled, 5).reason, ConsumerCancelled.BROKER_CANCEL)
-
-    def test_consumer_cancelled_on_consumer_cancel(self):
-        myq = Queue('myqueue', exclusive=True)
-
-        self.amqp.consume(myq)
-        self.amqp.cancel(myq)
-
-        c = self.drainTo(ConsumerCancelled, 10)
-        self.assertEquals(c.reason, ConsumerCancelled.USER_CANCEL)
-
-    def test_delete_exchange(self):
-        xchg = Exchange('a_fanout', type='fanout')
-        self.amqp.declare_exchange(xchg)
-        self.amqp.delete_exchange(xchg).result()
-
-    def test_exchanges(self):
-        xchg = Exchange('a_fanout', type='fanout')
-        self.amqp.declare_exchange(xchg)
-
-        q1 = Queue('q1', exclusive=True, exchange=xchg)
-        q2 = Queue('q2', exclusive=True, exchange=xchg)
-
-        self.amqp.consume(q1)
-        self.amqp.consume(q2)
-
-        self.amqp.send(Message(b'hello'), xchg)
-
-        self.drainTo(MessageReceived, 4)
-        self.drainTo(MessageReceived, 4)
diff --git a/tests/test_connection/__init__.py b/tests/test_connection/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f2b35b38d89264ee25685611d0a65a192e165f6
--- /dev/null
+++ b/tests/test_connection/__init__.py
@@ -0,0 +1,2 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
diff --git a/tests/test_connection/test_state.py b/tests/test_connection/test_state.py
new file mode 100644
index 0000000000000000000000000000000000000000..2d36b98a966d3ddb9cd1ba071ed8175385ebf222
--- /dev/null
+++ b/tests/test_connection/test_state.py
@@ -0,0 +1,36 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+import unittest
+
+
+from coolamqp.connection.state import Broker
+from coolamqp.objects import NodeDefinition
+from coolamqp.uplink import ListenerThread, Connection, Handshaker
+import socket
+import time
+
+
+def newc():
+    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    s.connect(('127.0.0.1', 5672))
+    s.settimeout(0)
+    s.send('AMQP\x00\x00\x09\x01')
+    return s
+
+
+NODE = NodeDefinition('127.0.0.1', 5672, 'user', 'user')
+
+
+class TestState(unittest.TestCase):
+    def test_basic(self):
+        lt = ListenerThread()
+        lt.start()
+
+        broker = Broker(Connection(newc(), lt), NODE)
+
+        ord = broker.connect()
+        ord.wait()
+
+        time.sleep(100)
+
+        lt.terminate()
diff --git a/tests/test_failures.py b/tests/test_failures.py
deleted file mode 100644
index c658026279a85db9039718dbb51a76173ff1cbf1..0000000000000000000000000000000000000000
--- a/tests/test_failures.py
+++ /dev/null
@@ -1,146 +0,0 @@
-# coding=UTF-8
-from __future__ import absolute_import, division, print_function
-
-import unittest
-import time
-from coolamqp import Cluster, ClusterNode, Queue, MessageReceived, ConnectionUp, \
-    ConnectionDown, ConsumerCancelled, Message, Exchange
-
-
-NODE = ClusterNode('127.0.0.1', 'guest', 'guest')
-
-from tests.utils import CoolAMQPTestCase
-
-
-class TestSpecialCases(CoolAMQPTestCase):
-    INIT_AMQP = False
-
-    def test_termination_while_disconnect(self):
-        self.amqp = Cluster([NODE])
-        self.amqp.start()
-        self.assertIsInstance(self.amqp.drain(wait=1), ConnectionUp)
-
-        self.fail_amqp()
-        time.sleep(5)
-        self.assertIsInstance(self.amqp.drain(wait=1), ConnectionDown)
-
-        self.amqp.shutdown()
-        self.assertIsNone(self.amqp.thread.backend)
-        self.assertFalse(self.amqp.connected)
-
-        self.unfail_amqp()
-
-
-class TestFailures(CoolAMQPTestCase):
-
-    def test_cancel_not_consumed_queue(self):
-        self.amqp.cancel(Queue('hello world')).result()
-
-    def test_longer_disconnects(self):
-        self.fail_amqp()
-        time.sleep(3)
-        self.drainTo(ConnectionDown, 4)
-        time.sleep(12)
-        self.unfail_amqp()
-        self.drainTo(ConnectionUp, 35)
-
-    def test_qos_redeclared_on_fail(self):
-        self.amqp.qos(0, 1).result()
-
-        self.restart_rmq()
-
-        self.amqp.consume(Queue('lol', exclusive=True)).result()
-        self.amqp.send(Message(b'what the fuck'), '', routing_key='lol')
-        self.amqp.send(Message(b'what the fuck'), '', routing_key='lol')
-
-        p = self.drainTo(MessageReceived, 4)
-
-        self.drainToNone(5)
-        p.message.ack()
-        self.assertIsInstance(self.amqp.drain(wait=4), MessageReceived)
-
-    def test_connection_flags_are_okay(self):
-        self.fail_amqp()
-        self.drainTo(ConnectionDown, 8)
-        self.assertFalse(self.amqp.connected)
-        self.unfail_amqp()
-        self.drainTo(ConnectionUp, 5)
-        self.assertTrue(self.amqp.connected)
-
-    def test_sending_a_message_is_cancelled(self):
-        """are messages generated at all? does it reconnect?"""
-
-        self.amqp.consume(Queue('wtf1', exclusive=True))
-
-        self.fail_amqp()
-        self.drainTo(ConnectionDown, 5)
-
-        p = self.amqp.send(Message(b'what the fuck'), routing_key='wtf1')
-        p.cancel()
-        self.assertTrue(p.wait())
-        self.assertFalse(p.has_failed())
-
-        self.fail_unamqp()
-        self.drainToAny([ConnectionUp], 30, forbidden=[MessageReceived])
-
-    def test_qos_after_failure(self):
-        self.amqp.qos(0, 1)
-
-        self.amqp.consume(Queue('lol', exclusive=True)).result()
-        self.amqp.send(Message(b'what the fuck'), '', routing_key='lol')
-        self.amqp.send(Message(b'what the fuck'), '', routing_key='lol')
-
-        p = self.drainTo(MessageReceived, 4)
-
-        self.assertIsNone(self.amqp.drain(wait=5))
-        p.message.ack()
-        self.drainTo(MessageReceived, 4)
-
-        self.restart_rmq()
-
-        self.amqp.send(Message(b'what the fuck'), '', routing_key='lol')
-        self.amqp.send(Message(b'what the fuck'), '', routing_key='lol')
-
-        p = self.drainTo(MessageReceived, 4)
-
-        self.drainToNone(5)
-        p.message.ack()
-        self.drainTo(MessageReceived, 4)
-
-    def test_connection_down_and_up_redeclare_queues(self):
-        """are messages generated at all? does it reconnect?"""
-        q1 = Queue('wtf1', exclusive=True, auto_delete=True)
-        self.amqp.consume(q1).result()
-
-        self.restart_rmq()
-
-        self.amqp.send(Message(b'what the fuck'), routing_key='wtf1')
-
-        self.drainTo(MessageReceived, 10)
-
-    def test_exchanges_are_redeclared(self):
-        xchg = Exchange('a_fanout', type='fanout')
-        self.amqp.declare_exchange(xchg)
-
-        q1 = Queue('q1', exclusive=True, exchange=xchg)
-        q2 = Queue('q2', exclusive=True, exchange=xchg)
-
-        self.amqp.consume(q1)
-        self.amqp.consume(q2).result()
-
-        self.restart_rmq()
-
-        self.amqp.send(Message(b'hello'), xchg)
-        self.drainTo([MessageReceived, MessageReceived], 20)
-
-    def test_consuming_exclusive_queue(self):
-        # declare and eat
-        q1 = Queue('q1', exclusive=True)
-
-        self.amqp.consume(q1).wait()
-
-        with self.new_amqp_connection() as amqp2:
-            q2 = Queue('q1', exclusive=True)
-
-            r = amqp2.consume(q2)
-            self.assertFalse(r.wait())
diff --git a/tests/test_framing/__init__.py b/tests/test_framing/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f2b35b38d89264ee25685611d0a65a192e165f6
--- /dev/null
+++ b/tests/test_framing/__init__.py
@@ -0,0 +1,2 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
diff --git a/tests/test_framing/test_definitions/__init__.py b/tests/test_framing/test_definitions/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f2b35b38d89264ee25685611d0a65a192e165f6
--- /dev/null
+++ b/tests/test_framing/test_definitions/__init__.py
@@ -0,0 +1,2 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
diff --git a/tests/test_framing/test_definitions/test_cpl.py b/tests/test_framing/test_definitions/test_cpl.py
new file mode 100644
index 0000000000000000000000000000000000000000..03b8e9f12830f200aa25a6cc9823acb856ee4acf
--- /dev/null
+++ b/tests/test_framing/test_definitions/test_cpl.py
@@ -0,0 +1,32 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+import unittest
+import io
+
+
+from coolamqp.framing.definitions import BasicContentPropertyList
+
+
+class TestBasicContentPropertyList(unittest.TestCase):
+    def test_bcpl1(self):
+        bcpl = BasicContentPropertyList(content_type='text/plain', content_encoding='utf8')
+
+        self.assertEquals(bcpl.content_type, 'text/plain')
+        self.assertEquals(bcpl.content_encoding, 'utf8')
+
+        buf = io.BytesIO()
+        bcpl.write_to(buf)
+
+        ser = buf.getvalue()
+        self.assertEquals(ser, '\xC0\x00' + chr(len('text/plain')) + b'text/plain\x04utf8')
+
+    def test_bcpl2(self):
+        bcpl = BasicContentPropertyList(content_type='text/plain')
+
+        self.assertEquals(bcpl.content_type, 'text/plain')
+
+        buf = io.BytesIO()
+        bcpl.write_to(buf)
+
+        ser = buf.getvalue()
+        self.assertEquals(ser, '\x80\x00' + chr(len('text/plain')) + b'text/plain')
diff --git a/tests/test_framing/test_definitions/test_frames.py b/tests/test_framing/test_definitions/test_frames.py
new file mode 100644
index 0000000000000000000000000000000000000000..c805647442bd32414d4d2c0bc9a412b1fead5f06
--- /dev/null
+++ b/tests/test_framing/test_definitions/test_frames.py
@@ -0,0 +1,46 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+import unittest
+import io
+import struct
+from coolamqp.framing.frames import AMQPHeaderFrame
+from coolamqp.framing.definitions import BasicContentPropertyList, FRAME_HEADER, FRAME_END, ConnectionStartOk
+
+
+class TestShitSerializesRight(unittest.TestCase):
+
+    def test_unser_header_frame(self):
+        s = b'\x00\x3C\x00\x00' + \
+            b'\x00\x00\x00\x00\x00\x00\x00\x0A' + \
+            b'\xC0\x00\x0Atext/plain\x04utf8'
+
+        hf = AMQPHeaderFrame.unserialize(0, buffer(s))
+
+        self.assertEquals(hf.class_id, 60)
+        self.assertEquals(hf.weight, 0)
+        self.assertEquals(hf.body_size, 10)
+        self.assertEquals(hf.properties.content_type, b'text/plain')
+        self.assertEquals(hf.properties.content_encoding, b'utf8')
+
+    def test_ser_header_frame(self):
+
+        a_cpl = BasicContentPropertyList(content_type='text/plain')
+
+        # content_type has len 10
+
+        buf = io.BytesIO()
+
+        hdr = AMQPHeaderFrame(0, 60, 0, 0, a_cpl)
+        hdr.write_to(buf)
+
+        s = b'\x00\x00\x00\x00' + \
+            b'\x00\x00\x00\x00\x00\x00\x00\x00' + \
+            b'\x80\x00\x0Atext/plain'
+        s = chr(FRAME_HEADER) + b'\x00\x00' + \
+                  struct.pack('!L', len(s)) + s + chr(FRAME_END)
+
+        self.assertEquals(buf.getvalue(),
+
+                          )
+
+
diff --git a/tests/test_framing/test_field_table.py b/tests/test_framing/test_field_table.py
new file mode 100644
index 0000000000000000000000000000000000000000..0f3f7ccbb82889b6f48322f10bda69aaa916a499
--- /dev/null
+++ b/tests/test_framing/test_field_table.py
@@ -0,0 +1,46 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+import unittest
+import struct
+import io
+
+from coolamqp.framing.field_table import enframe_table, deframe_table, frame_table_size, \
+                                         enframe_field_value, deframe_field_value, frame_field_value_size
+
+
+class TestFramingTables(unittest.TestCase):
+    def test_frame_unframe_table(self):
+
+        tab = [
+                (b'field', (b'yo', b's'))
+            ]
+
+        buf = io.BytesIO()
+
+        self.assertEquals(frame_table_size(tab), 4+6+4)
+
+        enframe_table(buf, tab)
+        buf = buf.getvalue()
+
+        self.assertEquals(buf, struct.pack('!I', 10) + b'\x05fields\x02yo')
+
+        tab, delta = deframe_table(buffer(buf), 0)
+
+        self.assertEquals(len(tab), 1)
+        self.assertEquals(delta, 14)
+        self.assertEquals(tab[0], (b'field', (b'yo', b's')))
+
+    def test_frame_unframe_value(self):
+
+        buf = io.BytesIO()
+
+        enframe_field_value(buf, (b'yo', b's'))
+
+        buf = buf.getvalue()
+        self.assertEquals(b's\x02yo', buf)
+
+        fv, delta = deframe_field_value(buffer(buf), 0)
+        self.assertEquals(fv, (b'yo', b's'))
+        self.assertEquals(delta, 4)
+
+        self.assertEquals(frame_field_value_size(fv), 4)
diff --git a/tests/test_noack.py b/tests/test_noack.py
deleted file mode 100644
index 2720947bb85057fbbaf9dd90a755232438e682d8..0000000000000000000000000000000000000000
--- a/tests/test_noack.py
+++ /dev/null
@@ -1,109 +0,0 @@
-# coding=UTF-8
-from __future__ import absolute_import, division, print_function
-import six
-import unittest
-import time
-
-from tests.utils import CoolAMQPTestCase
-from coolamqp import Cluster, ClusterNode, Queue, MessageReceived, ConnectionUp, ConnectionDown, ConsumerCancelled, Message, Exchange
-
-class TestNoAcknowledge(CoolAMQPTestCase):
-    def test_noack_works(self):
-        myq = Queue('myqueue', exclusive=True)
-
-        self.amqp.qos(0, 1, False)
-
-        self.amqp.consume(myq, no_ack=True)
-
-        self.amqp.send(Message(b'what the fuck'), '', routing_key='myqueue')
-        self.amqp.send(Message(b'what the fuck'), '', routing_key='myqueue')
-        self.amqp.send(Message(b'what the fuck'), '', routing_key='myqueue')
-
-        self.drainTo([MessageReceived, MessageReceived, MessageReceived], [3, 3, 3])
-
-    def test_noack_works_after_restart(self):
-        myq = Queue('myqueue', exclusive=True)
-
-        self.amqp.qos(0, 1, False)
-
-        self.amqp.consume(myq, no_ack=True)
-
-        self.amqp.send(Message(b'what the fuck'), '', routing_key='myqueue')
-        self.amqp.send(Message(b'what the fuck'), '', routing_key='myqueue')
-        self.amqp.send(Message(b'what the fuck'), '', routing_key='myqueue')
-
-        self.drainTo([MessageReceived, MessageReceived, MessageReceived], [3, 3, 3])
-
-        self.restart_rmq()
-
-        self.amqp.send(Message(b'what the fuck'), routing_key='myqueue')
-        self.amqp.send(Message(b'what the fuck'), routing_key='myqueue')
-        self.amqp.send(Message(b'what the fuck'), routing_key='myqueue')
-
-        self.drainTo([MessageReceived, MessageReceived, MessageReceived], [3, 3, 3])
-
-    def test_noack_coexists(self):
-        self.amqp.qos(0, 1, False)
-
-        self.amqp.consume(Queue('myqueue', exclusive=True), no_ack=True)
-        self.amqp.consume(Queue('myqueue2', exclusive=True))
-
-        msg = Message(b'zz')
-
-        for i in range(3):
-            self.amqp.send(msg, routing_key='myqueue')
-            self.amqp.send(msg, routing_key='myqueue2')
-
-        mq2s = []
-        for i in range(4):
-            # I should have received 3 messages from myqueue, and 2 from myqueue2
-            print('beng')
-            mer = self.drainTo(MessageReceived)
-            if mer.message.routing_key == 'myqueue2':
-                mq2s.append(mer.message)
-
-        # Should receive nothing, since not acked
-        self.assertIsNone(self.amqp.drain(wait=4))
-
-        self.assertEquals(len(mq2s), 1)
-
-        # ack and receive
-        for me in mq2s: me.ack()
-        self.drainTo(MessageReceived, 1).message.ack() # 2nd
-        self.drainTo(MessageReceived, 1).message.ack() # 3rd
-
-    @unittest.skip('demonstrates a py-amqp bug')
-    def test_noack_coexists_empty_message_body(self):
-        self.amqp.qos(0, 1, False)
-
-        self.amqp.consume(Queue('myqueue', exclusive=True), no_ack=True)
-        self.amqp.consume(Queue('myqueue2', exclusive=True))
-
-        msg = Message(b'')      # if this is empty, py-amqp fails
-
-        for i in range(3):
-            self.amqp.send(msg, routing_key='myqueue')
-            self.amqp.send(msg, routing_key='myqueue2')
-
-        # And here connection with the broker snaps .....
-
-        mq2s = []
-        for i in range(4):
-            # I should have received 3 messages from myqueue, and 2 from myqueue2
-            mer = self.drainTo(MessageReceived)
-            if mer.message.routing_key == 'myqueue2':
-                mq2s.append(mer.message)
-
-        # Should receive nothing, since not acked
-        self.assertIsNone(self.amqp.drain(wait=4))
-
-        self.assertEquals(len(mq2s), 1)
-
-        # ack and receive
-        for me in mq2s: me.ack()
-        mer = self.amqp.drain(wait=1)       # 2nd
-        self.assertIsInstance(mer, MessageReceived)
-        mer.message.ack()
-        mer = self.amqp.drain(wait=1)       # 3rd
-        self.assertIsInstance(mer, MessageReceived)
-        mer.message.ack()
\ No newline at end of file
diff --git a/tests/test_performance.py b/tests/test_performance.py
deleted file mode 100644
index d5bc651ccd7b8f0c9380f450ec0e783111cadc8b..0000000000000000000000000000000000000000
--- a/tests/test_performance.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# coding=UTF-8
-from __future__ import absolute_import, division, print_function
-from tests.utils import CoolAMQPTestCase
-import six
-import time
-
-from coolamqp import Cluster, ClusterNode, Queue, MessageReceived, ConnectionUp, \
-    ConnectionDown, ConsumerCancelled, Message, Exchange
-
-
-class TestBasics(CoolAMQPTestCase):
-    def setUp(self):
-        self.amqp = Cluster([ClusterNode('127.0.0.1', 'guest', 'guest')])
-        self.amqp.start()
-        self.assertIsInstance(self.amqp.drain(1), ConnectionUp)
-
-    def tearDown(self):
-        self.amqp.shutdown()
-
-    def test_sending_a_message(self):
-
-        with self.takes_less_than(0.5):
-            self.amqp.send(Message(b''), routing_key='nowhere').result()
-
diff --git a/tests/test_uplink/__init__.py b/tests/test_uplink/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f2b35b38d89264ee25685611d0a65a192e165f6
--- /dev/null
+++ b/tests/test_uplink/__init__.py
@@ -0,0 +1,2 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
diff --git a/tests/test_uplink/test_basic.py b/tests/test_uplink/test_basic.py
new file mode 100644
index 0000000000000000000000000000000000000000..034d7e9aad28a57f19e70a347a4e5254df5a12ef
--- /dev/null
+++ b/tests/test_uplink/test_basic.py
@@ -0,0 +1,57 @@
+# coding=UTF-8
+from __future__ import absolute_import, division, print_function
+import unittest
+
+from coolamqp.uplink import ListenerThread, Connection, Handshaker
+import socket
+import time
+
+
+def newc():
+    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    s.connect(('127.0.0.1', 5672))
+    s.settimeout(0)
+    s.send('AMQP\x00\x00\x09\x01')
+    return s
+
+
+class TestBasic(unittest.TestCase):
+    def test_gets_connectionstart(self):
+
+        hnd_ok = {'ok': False}
+        def hnd_suc():
+            hnd_ok['ok'] = True
+
+        lt = ListenerThread()
+        lt.start()
+
+        con = Connection(newc(), lt)
+
+        Handshaker(con, 'user', 'user', '/', hnd_suc, lambda: None)
+        con.start()
+
+        time.sleep(5)
+
+        lt.terminate()
+        self.assertTrue(hnd_ok['ok'])
+
+
+    def test_heartbeats(self):
+
+        hnd_ok = {'ok': False}
+        def hnd_suc():
+            hnd_ok['ok'] = True
+
+        lt = ListenerThread()
+        lt.start()
+
+        con = Connection(newc(), lt)
+
+        Handshaker(con, 'user', 'user', '/', hnd_suc, lambda: None, 3)
+        con.start()
+
+        time.sleep(20)
+
+        self.assertFalse(con.failed)
+        lt.terminate()
+        self.assertTrue(hnd_ok['ok'])