http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/4940e63d/console/stand-alone/plugin/lib/rhea.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/lib/rhea.js 
b/console/stand-alone/plugin/lib/rhea.js
new file mode 100644
index 0000000..fa323a3
--- /dev/null
+++ b/console/stand-alone/plugin/lib/rhea.js
@@ -0,0 +1,7716 @@
+require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof 
require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var 
f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var 
l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return 
s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof 
require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return 
s})({1:[function(require,module,exports){
+(function (process,Buffer){
+/*
+ * Copyright 2015 Red Hat Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+
+var frames = require('./frames.js');
+var log = require('./log.js');
+var sasl = require('./sasl.js');
+var types = require('./types.js');
+var util = require('./util.js');
+var EndpointState = require('./endpoint.js');
+var Session = require('./session.js');
+var Transport = require('./transport.js');
+
+var net = require("net");
+var tls = require("tls");
+var EventEmitter = require('events').EventEmitter;
+
+var AMQP_PROTOCOL_ID = 0x00;
+var TLS_PROTOCOL_ID = 0x02;
+
+function get_socket_id(socket) {
+    if (socket.get_id_string) return socket.get_id_string();
+    return socket.localAddress + ':' + socket.localPort + ' -> ' + 
socket.remoteAddress + ':' + socket.remotePort;
+};
+
+function session_per_connection(conn) {
+    var ssn = null;
+    return {
+        'get_session' : function () {
+            if (!ssn) {
+                ssn = conn.create_session();
+                ssn.begin();
+            }
+            return ssn;
+        }
+    };
+};
+
+function restrict(count, f) {
+    if (count) {
+        var current = count;
+        var reset;
+        return function (successful_attempts) {
+            if (reset !== successful_attempts) {
+                current = count;
+                reset = successful_attempts;
+            }
+            if (current--) return f(successful_attempts);
+            else return -1;
+        };
+    } else {
+        return f;
+    }
+}
+
+function backoff(initial, max) {
+    var delay = initial;
+    var reset;
+    return function (successful_attempts) {
+        if (reset !== successful_attempts) {
+            delay = initial;
+            reset = successful_attempts;
+        }
+        var current = delay;
+        var next = delay*2;
+        delay = max > next ? next : max;
+        return current;
+    };
+}
+
+function get_connect_fn(options) {
+    if (options.transport === undefined || options.transport === 'tcp') {
+        return net.connect;
+    } else if (options.transport === 'tls' || options.transport === 'ssl') {
+        return tls.connect;
+    } else {
+        throw Error('Unrecognised transport: ' + options.transport);
+    }
+}
+
+function connection_details(options) {
+    var details = {};
+    details.connect = options.connect ? options.connect : 
get_connect_fn(options);
+    details.host = options.host ? options.host : 'localhost';
+    details.port = options.port ? options.port : 5672;
+    details.options = options;
+    return details;
+};
+
+var conn_counter = 1;
+
+var Connection = function (options, container) {
+    this.options = {};
+    if (options) {
+        for (var k in options) {
+            this.options[k] = options[k];
+        }
+    }
+    this.container = container;
+    if (!this.options.id) {
+        this.options.id = 'connection-' + conn_counter++;
+    }
+    if (!this.options.container_id) {
+        this.options.container_id = container ? container.id : 
util.generate_uuid();
+    }
+    if (!this.options.connection_details) {
+        var self = this;
+        this.options.connection_details = function() { return 
connection_details(self.options); };
+    }
+    var reconnect = this.get_option('reconnect', true);
+    if (typeof reconnect === 'boolean' && reconnect) {
+        var initial = this.get_option('initial_reconnect_delay', 100);
+        var max = this.get_option('max_reconnect_delay', 60000);
+        this.options.reconnect = restrict(this.get_option('reconnect_limit'), 
backoff(initial, max));
+    } else if (typeof reconnect === 'number') {
+        var fixed = this.options.reconnect
+        this.options.reconnect = restrict(this.get_option('reconnect_limit'), 
function () { return fixed; });
+    }
+    this.registered = false;
+    this.state = new EndpointState();
+    this.local_channel_map = {};
+    this.remote_channel_map = {};
+    this.local = {};
+    this.remote = {};
+    this.local.open = frames.open(this.options);
+    this.local.close = frames.close({});
+    this.session_policy = session_per_connection(this);
+    this.amqp_transport = new Transport(this.options.id, AMQP_PROTOCOL_ID, 
frames.TYPE_AMQP, this);
+    this.sasl_transport = undefined;
+    this.transport = this.amqp_transport;
+    this.conn_established_counter = 0;
+    this.heartbeat_out = undefined;
+    this.heartbeat_in = undefined;
+    this.abort_idle = false;
+    this.socket_ready = false;
+};
+
+Connection.prototype = Object.create(EventEmitter.prototype);
+Connection.prototype.constructor = Connection;
+Connection.prototype.dispatch = function(name, context) {
+    log.events('Connection got event: ' + name);
+    if (this.listeners(name).length) {
+        EventEmitter.prototype.emit.apply(this, arguments);
+        return true;
+    } else if (this.container) {
+        return this.container.dispatch.apply(this.container, arguments);
+    }
+};
+
+Connection.prototype.reset = function() {
+    if (this.abort_idle) {
+        this.abort_idle = false;
+        this.local.close.error = undefined;
+        this.state = new EndpointState();
+        this.state.open();
+    }
+
+    //reset transport
+    this.amqp_transport = new Transport(this.options.id, AMQP_PROTOCOL_ID, 
frames.TYPE_AMQP, this);
+    this.sasl_transport = undefined;
+    this.transport = this.amqp_transport;
+
+    //reset remote endpoint state
+    this.state.disconnected();
+    this.remote = {};
+    //reset sessions:
+    this.remote_channel_map = {};
+    for (var k in this.local_channel_map) {
+        this.local_channel_map[k].reset();
+    }
+    this.socket_ready = false;
+}
+
+Connection.prototype.connect = function () {
+    this.is_server = false;
+    
this._connect(this.options.connection_details(this.conn_established_counter));
+    this.open();
+    return this;
+};
+Connection.prototype.reconnect = function () {
+    log.reconnect('reconnecting...');
+    this.reset();
+    
this._connect(this.options.connection_details(this.conn_established_counter));
+    process.nextTick(this._process.bind(this));
+    return this;
+};
+
+Connection.prototype._connect = function (details) {
+    if (details.connect) {
+        this.init(details.connect(details.port, details.host, details.options, 
this.connected.bind(this)));
+    } else {
+        this.init(get_connect_fn(details)(details.port, details.host, 
details.options, this.connected.bind(this)));
+    }
+    return this;
+};
+
+Connection.prototype.accept = function (socket) {
+    this.is_server = true;
+    log.io('[' + this.id + '] client accepted: '+ get_socket_id(socket));
+    this.socket_ready = true;
+    return this.init(socket);
+};
+
+Connection.prototype.init = function (socket) {
+    this.socket = socket;
+    this.socket.on('data', this.input.bind(this));
+    this.socket.on('error', this.error.bind(this));
+    this.socket.on('end', this.eof.bind(this));
+
+    if (this.is_server) {
+        var mechs;
+        if (this.container && 
Object.getOwnPropertyNames(this.container.sasl_server_mechanisms).length) {
+            mechs = this.container.sasl_server_mechanisms;
+        }
+        if (this.socket.encrypted && this.socket.authorized && 
this.get_option('enable_sasl_external', false)) {
+            mechs = sasl.server_add_external(mechs ? util.clone(mechs) : {});
+        }
+        if (mechs) {
+            this.sasl_transport = new sasl.Server(this, mechs);
+        }
+    } else {
+        var mechanisms = this.get_option('sasl_mechanisms');
+        if (!mechanisms) {
+            var username = this.get_option('username');
+            var password = this.get_option('password');
+            if (username) {
+                mechanisms = sasl.client_mechanisms();
+                if (password) mechanisms.enable_plain(username, password);
+                else mechanisms.enable_anonymous(username);
+            }
+        }
+        if (this.socket.encrypted && this.options.cert && 
this.get_option('enable_sasl_external', false)) {
+            if (!mechanisms) mechanisms = sasl.client_mechanisms();
+            mechanisms.enable_external();
+        }
+
+        if (mechanisms) {
+            this.sasl_transport = new sasl.Client(this, mechanisms);
+        }
+    }
+    this.transport = this.sasl_transport ? this.sasl_transport : 
this.amqp_transport;
+    return this;
+};
+
+Connection.prototype.attach_sender = function (options) {
+    return this.session_policy.get_session().attach_sender(options);
+};
+Connection.prototype.open_sender = Connection.prototype.attach_sender;//alias
+
+Connection.prototype.attach_receiver = function (options) {
+    return this.session_policy.get_session().attach_receiver(options);
+};
+Connection.prototype.open_receiver = 
Connection.prototype.attach_receiver;//alias
+
+Connection.prototype.get_option = function (name, default_value) {
+    if (this.options[name] !== undefined) return this.options[name];
+    else if (this.container) return this.container.get_option(name, 
default_value);
+    else return default_value;
+};
+
+Connection.prototype.connected = function () {
+    this.socket_ready = true;
+    this.conn_established_counter++;
+    log.io('[' + this.options.id + '] connected ' + 
get_socket_id(this.socket));
+    this.output();
+};
+Connection.prototype.sasl_failed = function (text) {
+    this.transport_error = {condition:'amqp:unauthorized-access', 
description:text};
+    this._handle_error();
+}
+
+Connection.prototype._handle_error = function () {
+    var error = this.get_error();
+    if (error) {
+        //TODO: invoke connection_close regardless of whether connection_error 
is handled
+        //TODO: example for error handling
+        if (!this.dispatch('connection_error', this._context())) {
+            if (!this.dispatch('connection_close', this._context())) {
+                console.log('error: ' + JSON.stringify(error));
+            }
+        }
+        return true;
+    } else {
+        return false;
+    }
+}
+
+Connection.prototype.get_error = function () {
+    if (this.transport_error) return this.transport_error;
+    if (this.remote.close && this.remote.close.error) return 
this.remote.close.error;
+    return undefined;
+}
+
+Connection.prototype.output = function () {
+    if (this.socket && this.socket_ready) {
+        if (this.heartbeat_out) clearTimeout(this.heartbeat_out);
+        this.transport.write(this.socket);
+        if (((this.is_closed() && this.state.has_settled()) || this.abort_idle 
|| this.transport_error) && !this.transport.has_writes_pending()) {
+            this.socket.end();
+        } else if (this.is_open() && this.remote.open.idle_time_out) {
+            this.heartbeat_out = setTimeout(this._write_frame.bind(this), 
this.remote.open.idle_time_out / 2);
+        }
+    }
+};
+
+Connection.prototype.input = function (buff) {
+    if (this.heartbeat_in) clearTimeout(this.heartbeat_in);
+    log.io('[' + this.options.id + '] read ' + buff.length + ' bytes');
+    var buffer;
+    if (this.previous_input) {
+        buffer = Buffer.concat([this.previous_input, buff], 
this.previous_input.length + buff.length);
+        this.previous_input = null;
+    } else {
+        buffer = buff;
+    }
+    var read = this.transport.read(buffer, this);
+    if (read < buffer.length) {
+        this.previous_input = buffer.slice(read);
+    }
+    if (this.local.open.idle_time_out) this.heartbeat_in = 
setTimeout(this.idle.bind(this), this.local.open.idle_time_out);
+    if (this.transport.has_writes_pending()) this.output();
+};
+
+Connection.prototype.idle = function () {
+    if (this.is_open()) {
+        this.abort_idle = true;
+        this.local.close.error = {condition:'amqp:resource-limit-exceeded', 
description:'max idle time exceeded'};
+        this.close();
+    }
+};
+
+Connection.prototype.error = function (e) {
+    console.log('[' + this.options.id + '] error: ' + e);
+    this._disconnected();
+};
+
+Connection.prototype.eof = function (e) {
+    this._disconnected();
+};
+
+Connection.prototype._disconnected = function () {
+    if (!this.is_closed()) {
+        if (!this.dispatch('disconnected', this._context())) {
+            console.log('[' + this.options.id + '] disconnected');
+        }
+        if (!this.is_server && !this.transport_error && 
this.options.reconnect) {
+            var delay = this.options.reconnect(this.conn_established_counter);
+            if (delay >= 0) {
+                log.reconnect('Scheduled reconnect in ' + delay + 'ms');
+                setTimeout(this.reconnect.bind(this), delay);
+            }
+        }
+    }
+};
+
+Connection.prototype.open = function () {
+    if (this.state.open()) {
+        this._register();
+    }
+};
+Connection.prototype.close = function () {
+    if (this.state.close()) {
+        this._register();
+    }
+};
+
+Connection.prototype.is_open = function () {
+    return this.state.is_open();
+};
+
+Connection.prototype.is_closed = function () {
+    return this.state.is_closed();
+};
+
+Connection.prototype.create_session = function () {
+    var i = 0;
+    while (this.local_channel_map[i]) i++;
+    var session = new Session(this, i);
+    this.local_channel_map[i] = session;
+    return session;
+}
+
+Connection.prototype.on_open = function (frame) {
+    if (this.state.remote_opened()) {
+        this.remote.open = frame.performative;
+        this.open();
+        this.dispatch('connection_open', this._context());
+    } else {
+        throw Error('Open already received');
+    }
+};
+
+Connection.prototype.on_close = function (frame) {
+    if (this.state.remote_closed()) {
+        this.remote.close = frame.performative;
+        this.close();
+        if (this.remote.close.error) {
+            this._handle_error();
+        } else {
+            this.dispatch('connection_close', this._context());
+        }
+        if (this.heartbeat_out) clearTimeout(this.heartbeat_out);
+    } else {
+        throw Error('Close already received');
+    }
+};
+
+Connection.prototype._register = function () {
+    if (!this.registered) {
+        this.registered = true;
+        process.nextTick(this._process.bind(this));
+    }
+};
+
+Connection.prototype._process = function () {
+    this.registered = false;
+    do {
+        if (this.state.need_open()) {
+            this._write_open();
+        }
+        for (var k in this.local_channel_map) {
+            this.local_channel_map[k]._process();
+        }
+        if (this.state.need_close()) {
+            this._write_close();
+        }
+    } while (!this.state.has_settled());
+};
+
+Connection.prototype._write_frame = function (channel, frame, payload) {
+    this.amqp_transport.encode(frames.amqp_frame(channel, frame, payload));
+    this.output();
+};
+
+Connection.prototype._write_open = function () {
+    this._write_frame(0, this.local.open.described());
+};
+
+Connection.prototype._write_close = function () {
+    this._write_frame(0, this.local.close.described());
+};
+
+Connection.prototype.on_begin = function (frame) {
+    var session;
+    if (frame.performative.remote_channel === null || 
frame.performative.remote_channel === undefined) {
+        //peer initiated
+        session = this.create_session();
+        session.local.begin.remote_channel = frame.channel;
+    } else {
+        session = this.local_channel_map[frame.performative.remote_channel];
+        if (!session) throw Error('Invalid value for remote channel ' + 
frame.performative.remote_channel);
+    }
+    session.on_begin(frame);
+    this.remote_channel_map[frame.channel] = session;
+};
+
+Connection.prototype.get_peer_certificate = function() {
+    if (this.socket && this.socket.getPeerCertificate) {
+        return this.socket.getPeerCertificate();
+    } else {
+        return undefined;
+    }
+};
+
+Connection.prototype._context = function (c) {
+    var context = c ? c : {};
+    context.connection = this;
+    if (this.container) context.container = this.container;
+    return context;
+};
+
+function delegate_to_session(name) {
+    Connection.prototype['on_' + name] = function (frame) {
+        var session = this.remote_channel_map[frame.channel];
+        if (!session) {
+            throw Error(name + ' received on invalid channel ' + 
frame.channel);
+        }
+        session['on_' + name](frame);
+    };
+};
+
+delegate_to_session('end');
+delegate_to_session('attach');
+delegate_to_session('detach');
+delegate_to_session('transfer');
+delegate_to_session('disposition');
+delegate_to_session('flow');
+
+module.exports = Connection
+
+}).call(this,require('_process'),require("buffer").Buffer)
+},{"./endpoint.js":2,"./frames.js":3,"./log.js":5,"./sasl.js":8,"./session.js":9,"./transport.js":11,"./types.js":12,"./util.js":13,"_process":24,"buffer":19,"events":23,"net":18,"tls":18}],2:[function(require,module,exports){
+/*
+ * Copyright 2015 Red Hat Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+
+var EndpointState = function () {
+    this.init();
+};
+
+EndpointState.prototype.init = function () {
+    this.local_open = false;
+    this.remote_open = false;
+    this.open_requests = 0;
+    this.close_requests = 0;
+    this.initialised = false;
+};
+
+EndpointState.prototype.open = function () {
+    this.initialised = true;
+    if (!this.local_open) {
+        this.local_open = true;
+        this.open_requests++;
+        return true;
+    } else {
+        return false;
+    }
+};
+
+EndpointState.prototype.close = function () {
+    if (this.local_open) {
+        this.local_open = false;
+        this.close_requests++;
+        return true;
+    } else {
+        return false;
+    }
+};
+
+EndpointState.prototype.disconnected = function () {
+    var was_open = this.local_open;
+    this.init();
+    if (was_open) {
+        this.open();
+    } else {
+        this.close();
+    }
+};
+
+EndpointState.prototype.remote_opened = function (frame) {
+    if (!this.remote_open) {
+        this.remote_open = true;
+        return true;
+    } else {
+        return false;
+    }
+};
+
+EndpointState.prototype.remote_closed = function (frame) {
+    if (this.remote_open) {
+        this.remote_open = false;
+        return true;
+    } else {
+        return false;
+    }
+};
+
+EndpointState.prototype.is_open = function () {
+    return this.local_open && this.remote_open;
+};
+
+EndpointState.prototype.is_closed = function () {
+    return this.initialised && !this.local_open && !this.remote_open;
+};
+
+EndpointState.prototype.has_settled = function () {
+    return this.open_requests == 0 && this.close_requests == 0;
+};
+
+EndpointState.prototype.need_open = function () {
+    if (this.open_requests > 0) {
+        this.open_requests--;
+        return true;
+    } else {
+        return false;
+    }
+};
+
+EndpointState.prototype.need_close = function () {
+    if (this.close_requests > 0) {
+        this.close_requests--;
+        return true;
+    } else {
+        return false;
+    }
+};
+
+module.exports = EndpointState
+
+},{}],3:[function(require,module,exports){
+/*
+ * Copyright 2015 Red Hat Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+
+var types = require('./types.js');
+
+var frames = {};
+var by_descriptor = {};
+
+frames.read_header = function(buffer) {
+    var offset = 4;
+    var header = {};
+    var name = buffer.toString('ascii', 0, offset);
+    if (name !== 'AMQP') {
+        throw Error('Invalid protocol header for AMQP ' + name);
+    }
+    header.protocol_id = buffer.readUInt8(offset++);
+    header.major = buffer.readUInt8(offset++);
+    header.minor = buffer.readUInt8(offset++);
+    header.revision = buffer.readUInt8(offset++);
+    if (header.major !== 1 || header.minor !== 0) {
+        throw Error('Unsupported AMQP version: ' + JSON.stringify(header));
+    }
+    return header;
+};
+frames.write_header = function(buffer, header) {
+    var offset = 4;
+    buffer.write('AMQP', 0, offset, 'ascii');
+    buffer.writeUInt8(header.protocol_id, offset++);
+    buffer.writeUInt8(header.major, offset++);
+    buffer.writeUInt8(header.minor, offset++);
+    buffer.writeUInt8(header.revision, offset++);
+    return 8;
+};
+//todo: define enumeration for frame types
+frames.TYPE_AMQP = 0x00;
+frames.TYPE_SASL = 0x01;
+
+frames.read_frame = function(buffer) {
+    var reader = new types.Reader(buffer);
+    var frame = {};
+    frame.size = reader.read_uint(4);
+    if (reader.remaining < frame.size) {
+        return null;
+    }
+    var doff = reader.read_uint(1);
+    if (doff < 2) {
+        throw Error('Invalid data offset, must be at least 2 was ' + doff);
+    }
+    frame.type = reader.read_uint(1);
+    if (frame.type === frames.TYPE_AMQP) {
+        frame.channel = reader.read_uint(2);
+    } else if (frame.type === frames.TYPE_SASL) {
+        reader.skip(2);
+    } else {
+        throw Error('Unknown frame type ' + frame.type);
+    }
+    if (doff > 1) {
+        //ignore any extended header
+        reader.skip(doff * 4 - 8);
+    }
+    if (reader.remaining()) {
+        frame.performative = reader.read();
+        var c = by_descriptor[frame.performative.descriptor.value];
+        if (c) {
+            frame.performative = new c(frame.performative.value);
+        }
+        if (reader.remaining()) {
+            frame.payload = reader.read_bytes(reader.remaining());
+        }
+    }
+    return frame;
+};
+
+frames.write_frame = function(frame) {
+    var writer = new types.Writer();
+    writer.skip(4);//skip size until we know how much we have written
+    writer.write_uint(2, 1);//doff
+    writer.write_uint(frame.type, 1);
+    if (frame.type === frames.TYPE_AMQP) {
+        writer.write_uint(frame.channel, 2);
+    } else if (frame.type === frames.TYPE_SASL) {
+        writer.write_uint(0, 2);
+    } else {
+        throw Error('Unknown frame type ' + frame.type);
+    }
+    if (frame.performative) {
+        writer.write(frame.performative);
+        if (frame.payload) {
+            writer.write_bytes(frame.payload);
+        }
+    }
+    var buffer = writer.toBuffer();
+    buffer.writeUInt32BE(buffer.length, 0);//fill in the size
+    return buffer;
+};
+
+frames.amqp_frame = function(channel, performative, payload) {
+    return {'channel': channel || 0, 'type': frames.TYPE_AMQP, 'performative': 
performative, 'payload': payload};
+};
+frames.sasl_frame = function(performative) {
+    return {'channel': 0, 'type': frames.TYPE_SASL, 'performative': 
performative};
+};
+
+function define_frame(type, def) {
+    var c = types.define_composite(def);
+    frames[def.name] = c.create;
+    by_descriptor[Number(c.descriptor.numeric).toString(10)] = c;
+    by_descriptor[c.descriptor.symbolic] = c;
+};
+
+var open = {name: "open",
+            code: 0x10,
+            fields: [
+                 {name:"container_id", type:"string", mandatory:true},
+                 {name:"hostname", type:"string"},
+                 {name:"max_frame_size", type:"uint", 
default_value:4294967295},
+                 {name:"channel_max", type:"ushort", default_value:65535},
+                 {name:"idle_time_out", type:"uint"},
+                 {name:"outgoing_locales", type:"symbol", multiple:true},
+                 {name:"incoming_locales", type:"symbol", multiple:true},
+                 {name:"offered_capabilities", type:"symbol", multiple:true},
+                 {name:"desired_capabilities", type:"symbol", multiple:true},
+                 {name:"properties", type:"symbolic_map"}
+             ]
+           };
+
+var begin = {name:"begin",
+             code:0x11,
+             fields:[
+                 {name:"remote_channel", type:"ushort"},
+                 {name:"next_outgoing_id", type:"uint", mandatory:true},
+                 {name:"incoming_window", type:"uint", mandatory:true},
+                 {name:"outgoing_window", type:"uint", mandatory:true},
+                 {name:"handle_max", type:"uint", default_value:"4294967295"},
+                 {name:"offered_capabilities", type:"symbol", multiple:true},
+                 {name:"desired_capabilities", type:"symbol", multiple:true},
+                 {name:"properties", type:"symbolic_map"}
+             ]
+            };
+
+var attach = {name:"attach",
+              code:0x12,
+              fields:[
+                  {name:"name", type:"string", mandatory:true},
+                  {name:"handle", type:"uint", mandatory:true},
+                  {name:"role", type:"boolean", mandatory:true},
+                  {name:"snd_settle_mode", type:"ubyte", default_value:2},
+                  {name:"rcv_settle_mode", type:"ubyte", default_value:0},
+                  {name:"source", type:"*"},
+                  {name:"target", type:"*"},
+                  {name:"unsettled", type:"map"},
+                  {name:"incomplete_unsettled", type:"boolean", 
default_value:false},
+                  {name:"initial_delivery_count", type:"uint"},
+                  {name:"max_message_size", type:"ulong"},
+                  {name:"offered_capabilities", type:"symbol", multiple:true},
+                  {name:"desired_capabilities", type:"symbol", multiple:true},
+                  {name:"properties", type:"symbolic_map"}
+              ]
+             };
+
+var flow = {name:"flow",
+            code:0x13,
+            fields:[
+                {name:"next_incoming_id", type:"uint"},
+                {name:"incoming_window", type:"uint", mandatory:true},
+                {name:"next_outgoing_id", type:"uint", mandatory:true},
+                {name:"outgoing_window", type:"uint", mandatory:true},
+                {name:"handle", type:"uint"},
+                {name:"delivery_count", type:"uint"},
+                {name:"link_credit", type:"uint"},
+                {name:"available", type:"uint"},
+                {name:"drain", type:"boolean", default_value:false},
+                {name:"echo", type:"boolean", default_value:false},
+                {name:"properties", type:"symbolic_map"}
+            ]
+           };
+
+var transfer = {name:"transfer",
+                code:0x14,
+                fields:[
+                    {name:"handle", type:"uint", mandatory:true},
+                    {name:"delivery_id", type:"uint"},
+                    {name:"delivery_tag", type:"binary"},
+                    {name:"message_format", type:"uint"},
+                    {name:"settled", type:"boolean"},
+                    {name:"more", type:"boolean", default_value:false},
+                    {name:"rcv_settle_mode", type:"ubyte"},
+                    {name:"state", type:"delivery_state"},
+                    {name:"resume", type:"boolean", default_value:false},
+                    {name:"aborted", type:"boolean", default_value:false},
+                    {name:"batchable", type:"boolean", default_value:false}
+                ]
+               };
+
+var disposition = {name:"disposition",
+                   code:0x15,
+                   fields:[
+                       {name:"role", type:"boolean", mandatory:true},
+                       {name:"first", type:"uint", mandatory:true},
+                       {name:"last", type:"uint"},
+                       {name:"settled", type:"boolean", default_value:false},
+                       {name:"state", type:"*"},
+                       {name:"batchable", type:"boolean", default_value:false}
+                   ]
+                  };
+
+var detach = {name: "detach",
+             code: 0x16,
+              fields: [
+                  {name:"handle", type:"uint", mandatory:true},
+                  {name:"closed", type:"boolean", default_value:false},
+                  {name:"error", type:"error"}
+              ]
+             };
+
+var end = {name: "end",
+             code: 0x17,
+             fields: [
+                 {name:"error", type:"error"}
+             ]
+            };
+
+var close = {name: "close",
+             code: 0x18,
+             fields: [
+                 {name:"error", type:"error"}
+             ]
+            };
+
+define_frame(frames.TYPE_AMQP, open);
+define_frame(frames.TYPE_AMQP, begin);
+define_frame(frames.TYPE_AMQP, attach);
+define_frame(frames.TYPE_AMQP, flow);
+define_frame(frames.TYPE_AMQP, transfer);
+define_frame(frames.TYPE_AMQP, disposition);
+define_frame(frames.TYPE_AMQP, detach);
+define_frame(frames.TYPE_AMQP, end);
+define_frame(frames.TYPE_AMQP, close);
+
+var sasl_mechanisms = {name:"sasl_mechanisms", code:0x40,
+                       fields: [
+                           {name:"sasl_server_mechanisms", type:"symbol", 
multiple:true, mandatory:true}
+                       ]};
+
+var sasl_init = {name:"sasl_init", code:0x41,
+                 fields: [
+                     {name:"mechanism", type:"symbol", mandatory:true},
+                     {name:"initial_response", type:"binary"},
+                     {name:"hostname", type:"string"}
+                 ]};
+
+var sasl_challenge = {name:"sasl_challenge", code:0x42,
+                      fields: [
+                          {name:"challenge", type:"binary", mandatory:true}
+                      ]};
+
+var sasl_response = {name:"sasl_response", code:0x43,
+                     fields: [
+                         {name:"response", type:"binary", mandatory:true}
+                     ]};
+
+var sasl_outcome = {name:"sasl_outcome", code:0x44,
+                    fields: [
+                        {name:"code", type:"ubyte", mandatory:true},
+                        {name:"additional_data", type:"binary"}
+                    ]};
+
+define_frame(frames.TYPE_SASL, sasl_mechanisms);
+define_frame(frames.TYPE_SASL, sasl_init);
+define_frame(frames.TYPE_SASL, sasl_challenge);
+define_frame(frames.TYPE_SASL, sasl_response);
+define_frame(frames.TYPE_SASL, sasl_outcome);
+
+module.exports = frames;
+
+},{"./types.js":12}],4:[function(require,module,exports){
+/*
+ * Copyright 2015 Red Hat Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+
+var frames = require('./frames.js');
+var log = require('./log.js');
+var message = require('./message.js');
+var terminus = require('./terminus.js')
+var types = require('./types.js')
+var EndpointState = require('./endpoint.js');
+
+var FlowController = function (window) {
+    this.window = window;
+};
+FlowController.prototype.update = function (context) {
+    var delta = this.window - context.receiver.credit;
+    context.receiver.flow(delta);
+};
+
+function auto_settle(context) {
+    context.delivery.settled = true;
+};
+
+function auto_accept(context) {
+    context.delivery.update(true, message.accepted().described());
+};
+
+var EventEmitter = require('events').EventEmitter;
+
+var link = Object.create(EventEmitter.prototype);
+link.dispatch = function(name, context) {
+    log.events('Link got event: '+ name);
+    EventEmitter.prototype.emit.apply(this.observers, arguments);
+    if (this.listeners(name).length) {
+        EventEmitter.prototype.emit.apply(this, arguments);
+    } else {
+        this.session.dispatch.apply(this.session, arguments);
+    }
+};
+link.set_source = function (fields) {
+    this.local.attach.source = terminus.source(fields).described();
+};
+link.set_target = function (fields) {
+    this.local.attach.target = terminus.target(fields).described();
+};
+
+link.attach = function () {
+    if (this.state.open()) {
+        this.connection._register();
+    }
+};
+link.open = link.attach;
+
+link.detach = function () {
+    this.local.detach.closed = false;
+    if (this.state.close()) {
+        this.connection._register();
+    }
+};
+link.close = function() {
+    this.local.detach.closed = true;
+    if (this.state.close()) {
+        this.connection._register();
+    }
+}
+
+link.is_open = function () {
+    return this.session.is_open() && this.state.is_open();
+};
+
+link.is_closed = function () {
+    return this.session.is_closed() || this.state.is_closed();
+};
+
+link._process = function () {
+    do {
+        if (this.state.need_open()) {
+            this.session.output(this.local.attach.described());
+        }
+
+        if (this.issue_flow) {
+            this.session._write_flow(this);
+            this.issue_flow = false;
+        }
+
+        if (this.state.need_close()) {
+            this.session.output(this.local.detach.described());
+        }
+    } while (!this.state.has_settled());
+};
+
+link.on_attach = function (frame) {
+    if (this.state.remote_opened()) {
+        if (!this.remote.handle) {
+            this.remote.handle = frame.handle;
+        }
+        frame.performative.source = terminus.unwrap(frame.performative.source);
+        frame.performative.target = terminus.unwrap(frame.performative.target);
+        this.remote.attach = frame.performative;
+        this.open();
+        this.dispatch(this.is_receiver() ? 'receiver_open' : 'sender_open', 
this._context());
+    } else {
+        throw Error('Attach already received');
+    }
+};
+
+link.on_detach = function (frame) {
+    if (this.state.remote_closed()) {
+        this.remote.detach = frame.performative;
+        this.close();
+        this.dispatch(this.local.attach.role ? 'receiver_close' : 
'sender_close', this._context());
+    } else {
+        throw Error('Detach already received');
+    }
+};
+
+function is_internal(name) {
+    switch (name) {
+    case 'handle':
+    case 'role':
+    case 'initial_delivery_count':
+        return true;
+    default:
+        return false;
+    }
+}
+
+link.init = function (session, name, local_handle, opts, is_receiver) {
+    this.session = session;
+    this.connection = session.connection;
+    this.name = name;
+    this.options = opts === undefined ? {} : opts;
+    this.state = new EndpointState();
+    this.issue_flow = false;//currently only used by receiver
+    this.local = {'handle': local_handle};
+    this.local.attach = frames.attach({'handle':local_handle,'name':name, 
role:is_receiver});
+    for (var f in this.local.attach) {
+        if (!is_internal(f) && this.options[f] !== undefined) {
+            this.local.attach[f] = this.options[f];
+        }
+    }
+    this.local.detach = frames.detach({'handle':local_handle, 'closed':true});
+    this.remote = {'handle':undefined};
+    this.delivery_count = 0;
+    this.credit = 0;
+    this.observers = new EventEmitter();
+};
+link.reset = function() {
+    this.state.disconnected();
+    this.remote = {'handle':undefined};
+    this.delivery_count = 0;
+    this.credit = 0;
+};
+
+link.has_credit = function () {
+    return this.credit > 0;
+};
+link.is_receiver = function () {
+    return this.local.attach.role;
+};
+link._context = function (c) {
+    var context = c ? c : {};
+    if (this.is_receiver()) {
+        context.receiver = this;
+    } else {
+        context.sender = this;
+    }
+    return this.session._context(context);
+};
+link.get_option = function (name, default_value) {
+    if (this.options[name] !== undefined) return this.options[name];
+    else return this.session.get_option(name, default_value);
+};
+
+var Sender = function (session, name, local_handle, opts) {
+    this.init(session, name, local_handle, opts, false);
+    this.local.attach.initial_delivery_count = 0;
+    this.tag = 0;
+    if (this.get_option('autosettle', true)) {
+        this.observers.on('settled', auto_settle);
+    }
+};
+Sender.prototype = Object.create(link);
+Sender.prototype.constructor = Sender;
+Sender.prototype.next_tag = function () {
+    return new String(this.tag++);
+};
+Sender.prototype.sendable = function (frame) {
+    return this.credit && this.session.outgoing.available();
+}
+Sender.prototype.on_flow = function (frame) {
+    var flow = frame.performative;
+    this.credit = flow.delivery_count + flow.link_credit - this.delivery_count;
+    if (this.is_open()) {
+        this.dispatch('sender_flow', this._context());
+        if (this.sendable()) {
+            this.dispatch('sendable', this._context());
+        }
+    }
+};
+Sender.prototype.on_transfer = function (frame) {
+    throw Error('got transfer on sending link');
+};
+Sender.prototype.send = function (msg, tag) {
+    return this.session.send(this, tag ? tag : this.next_tag(), 
message.encode(msg), 0);
+};
+
+
+var Receiver = function (session, name, local_handle, opts) {
+    this.init(session, name, local_handle, opts, true);
+    this.set_prefetch(this.get_option('prefetch', 100));
+    if (this.get_option('autoaccept', true)) {
+        this.observers.on('message', auto_accept);
+    }
+};
+Receiver.prototype = Object.create(link);
+Receiver.prototype.constructor = Receiver;
+Receiver.prototype.on_flow = function (frame) {
+    this.dispatch('receiver_flow', this._context());
+};
+Receiver.prototype.flow = function(credit) {
+    if (credit > 0) {
+        this.credit += credit;
+        this.issue_flow = true;
+        this.connection._register();
+    }
+};
+
+Receiver.prototype.set_prefetch = function(prefetch) {
+    if (prefetch > 0) {
+        var flow_controller = new FlowController(prefetch);
+        var listener = flow_controller.update.bind(flow_controller);
+        this.observers.on('message', listener);
+        this.observers.on('receiver_open', listener);
+    }
+}
+
+module.exports = {'Sender': Sender, 'Receiver':Receiver};
+
+},{"./endpoint.js":2,"./frames.js":3,"./log.js":5,"./message.js":6,"./terminus.js":10,"./types.js":12,"events":23}],5:[function(require,module,exports){
+/*
+ * Copyright 2015 Red Hat Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+
+var debug = require('debug');
+
+module.exports = {
+    'frames' : debug('rhea:frames'),
+    'raw' : debug('rhea:raw'),
+    'reconnect' : debug('rhea:reconnect'),
+    'events' : debug('rhea:events'),
+    'message' : debug('rhea:message'),
+    'flow' : debug('rhea:flow'),
+    'io' : debug('rhea:io')
+}
+
+},{"debug":15}],6:[function(require,module,exports){
+/*
+ * Copyright 2015 Red Hat Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+
+var log = require('./log.js');
+var types = require('./types.js');
+
+var by_descriptor = {};
+var unwrappers = {};
+var wrappers = [];
+var message = {};
+
+function define_section(descriptor, unwrap, wrap) {
+    unwrap.descriptor = descriptor;
+    unwrappers[descriptor.symbolic] = unwrap;
+    unwrappers[Number(descriptor.numeric).toString(10)] = unwrap;
+    if (wrap) {
+        wrappers.push(wrap);
+    }
+};
+
+function define_composite_section(def) {
+    var c = types.define_composite(def);
+    message[def.name] = c.create;
+    by_descriptor[Number(c.descriptor.numeric).toString(10)] = c;
+    by_descriptor[c.descriptor.symbolic] = c;
+
+    var unwrap = function (msg, section) {
+        msg[def.name] = new c(section.value);
+    };
+
+    var wrap = function (sections, msg) {
+        if (msg[def.name]) {
+            if (msg[def.name].described) {
+                sections.push(msg[def.name].described());
+            } else {
+                sections.push(c.create(msg[def.name]).described());
+            }
+        }
+    };
+    define_section(c.descriptor, unwrap, wrap);
+};
+
+
+function define_map_section(def) {
+    var descriptor = {numeric:def.code};
+    descriptor.symbolic = 'amqp:' + def.name.replace(/_/g, '-') + ':map';
+    var unwrap = function (msg, section) {
+        msg[def.name] = types.unwrap(section);
+    };
+    var wrap = function (sections, msg) {
+        if (msg[def.name]) {
+            
sections.push(types.described(types.wrap_ulong(descriptor.numeric), 
types.wrap_map(msg[def.name])));
+        }
+    };
+    define_section(descriptor, unwrap, wrap);
+};
+
+define_composite_section({name:"header",
+                          code:0x70,
+                          fields:[
+                              {name:"durable", type:"boolean", 
default_value:false},
+                              {name:"priority", type:"ubyte", default_value:4},
+                              {name:"ttl", type:"uint"},
+                              {name:"first_acquirer", type:"boolean", 
default_value:false},
+                              {name:"delivery_count", type:"uint", 
default_value:0}
+                          ]
+                         });
+define_map_section({name:"delivery_annotations", code:0x71});
+define_map_section({name:"message_annotations", code:0x72});
+define_composite_section({name:"properties",
+                          code:0x73,
+                          fields:[
+                              {name:"message_id", type:"message_id"},
+                              {name:"user_id", type:"binary"},
+                              {name:"to", type:"string"},
+                              {name:"subject", type:"string"},
+                              {name:"reply_to", type:"string"},
+                              {name:"correlation_id", type:"message_id"},
+                              {name:"content_type", type:"symbol"},
+                              {name:"content_encoding", type:"symbol"},
+                              {name:"absolute_expiry_time", type:"timestamp"},
+                              {name:"creation_time", type:"timestamp"},
+                              {name:"group_id", type:"string"},
+                              {name:"group_sequence", type:"uint"},
+                              {name:"reply_to_group_id", type:"string"}
+                          ]
+                         });
+define_map_section({name:"application_properties", code:0x74});
+
+define_section({numeric:0x77, symbolic:'amqp:value:*'},
+               function(msg, section) { msg.body = types.unwrap(section); },
+               function(sections, msg) { 
sections.push(types.described(types.wrap_ulong(0x77), types.wrap(msg.body))); 
});
+
+define_map_section({name:"footer", code:0x78});
+
+message.encode = function(obj) {
+    var sections = [];
+
+    wrappers.forEach(function (wrapper_fn) { wrapper_fn(sections, obj); });
+    var writer = new types.Writer();
+    for (var i = 0; i < sections.length; i++) {
+        log.message('Encoding section ' + (i+1) + ' of ' + sections.length + 
': ' + sections[i]);
+        writer.write(sections[i]);
+    }
+    var data = writer.toBuffer();
+    log.message('encoded ' + data.length + ' bytes');
+    return data;
+}
+
+message.decode = function(buffer) {
+    var msg = {};
+    var reader = new types.Reader(buffer);
+    while (reader.remaining()) {
+        var s = reader.read();
+        log.message('decoding section: ' + JSON.stringify(s) + ' of type: ' + 
JSON.stringify(s.descriptor));
+        if (s.descriptor) {
+            var unwrap = unwrappers[s.descriptor.value];
+            if (unwrap) {
+                unwrap(msg, s);
+            } else {
+                console.log("WARNING: did not recognise message section with 
descriptor " + s.descriptor);
+            }
+        } else {
+            console.log("WARNING: expected described message section got " + 
JSON.stringify(s));
+        }
+    }
+    return msg;
+}
+
+var outcomes = {};
+
+function define_outcome(def) {
+    var c = types.define_composite(def);
+    c.composite_type = def.name;
+    message[def.name] = c.create;
+    outcomes[Number(c.descriptor.numeric).toString(10)] = c;
+    outcomes[c.descriptor.symbolic] = c;
+    message['is_' + def.name] = function (o) {
+        if (o && o.descriptor) {
+            var c = outcomes[o.descriptor.value];
+            if (c) {
+                return c.descriptor.numeric == def.code;
+            }
+        }
+        return false;
+    };
+}
+
+message.unwrap_outcome = function (outcome) {
+    if (outcome && outcome.descriptor) {
+        var c = outcomes[outcome.descriptor.value];
+        if (c) {
+            return new c(outcome);
+        }
+    }
+    console.log('unrecognised outcome');
+    return outcome;
+};
+
+message.are_outcomes_equivalent = function(a, b) {
+    if (a === undefined && b === undefined) return true;
+    else if (a === undefined || b === undefined) return false;
+    else return a.descriptor.value == b.descriptor.value && JSON.stringify(a) 
== JSON.stringify(b);
+};
+
+define_outcome({name:"received", code:0x23,
+                fields:[
+                    {name:"section-number", type:"uint", mandatory:true},
+                    {name:"section-offset", type:"ulong", mandatory:true}
+                ]});
+define_outcome({name:"accepted", code:0x24, fields:[]});
+define_outcome({name:"rejected", code:0x25, fields:[{name:"error", 
type:"error"}]});
+define_outcome({name:"released", code:0x26, fields:[]});
+define_outcome({name:"modified",
+ code:0x27,
+ fields:[
+     {name:"delivery-failed", type:"boolean"},
+     {name:"undeliverable-here", type:"boolean"},
+     {name:"message-annotations", type:"fields"}
+]});
+
+module.exports = message;
+
+},{"./log.js":5,"./types.js":12}],7:[function(require,module,exports){
+/*
+ * Copyright 2015 Red Hat Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+
+var url = require('url');
+
+var simple_id_generator = {
+    counter : 1,
+    next : function() {
+        return this.counter++;
+    }
+};
+
+var Client = function (container, address) {
+    var u = url.parse(address);
+    //TODO: handle scheme and user/password if present
+    this.connection = container.connect({'host':u.hostname, 'port':u.port});
+    this.connection.on('message', this._response.bind(this));
+    this.connection.on('receiver_open', this._ready.bind(this));
+    this.sender = this.connection.attach_sender(u.path.substr(1));
+    this.receiver = this.connection.attach_receiver({source:{dynamic:true}});
+    this.id_generator = simple_id_generator;
+    this.pending = [];//requests yet to be made (waiting for receiver to open)
+    this.outstanding = {};//requests sent, for which responses have not yet 
been received
+};
+
+Client.prototype._request = function (id, name, args, callback) {
+    var request = {properties:{}};
+    request.properties.subject = name;
+    request.body = args;
+    request.properties.message_id = id;
+    request.properties.reply_to = this.receiver.remote.attach.source.address;
+    this.outstanding[id] = callback;
+    this.sender.send(request);
+};
+
+Client.prototype._response = function (context) {
+    var id = context.message.properties.correlation_id;
+    var callback = this.outstanding[id];
+    if (callback) {
+        if (context.message.properties.subject === 'ok') {
+            callback(context.message.body);
+        } else {
+            callback(undefined, {name: context.message.properties.subject, 
description: context.message.body});
+        }
+    } else {
+        console.log('no request pending for ' + id + ', ignoring response');
+    }
+};
+
+Client.prototype._ready = function (context) {
+    this._process_pending();
+};
+
+Client.prototype._process_pending = function () {
+    for (var i = 0; i < this.pending.length; i++) {
+        var r = this.pending[i];
+        this._request(r.id, r.name, r.args, r.callback);
+    }
+    this.pending = [];
+};
+
+Client.prototype.call = function (name, args, callback) {
+    var id = this.id_generator.next();
+    if (this.receiver.is_open() && this.pending.length === 0) {
+        this._request(id, name, args, callback);
+    } else {
+        //need to wait for reply-to address
+        this.pending.push({'name':name, 'args':args, 'callback':callback, 
'id':id});
+    }
+};
+
+Client.prototype.close = function () {
+    this.receiver.close();
+    this.sender.close();
+    this.connection.close();
+};
+
+Client.prototype.define = function (name) {
+    this[name] = function (args, callback) { this.call(name, args, callback); 
};
+};
+
+var Cache = function (ttl, purged) {
+    this.ttl = ttl;
+    this.purged = purged;
+    this.entries = {};
+    this.timeout = undefined;
+};
+
+Cache.prototype.clear = function () {
+    if (this.timeout) clearTimeout(this.timeout);
+    this.entries = {};
+}
+
+Cache.prototype.put = function (key, value) {
+    this.entries[key] = {'value':value, 'last_accessed': Date.now()};
+    if (!this.timeout) this.timeout = setTimeout(this.purge.bind(this), 
this.ttl);
+};
+
+Cache.prototype.get = function (key) {
+    var entry = this.entries[key];
+    if (entry) {
+        entry.last_accessed = Date.now();
+        return entry.value;
+    } else {
+        return undefined;
+    }
+};
+
+Cache.prototype.purge = function() {
+    //TODO: this could be optimised if the map is large
+    var now = Date.now();
+    var expired = [];
+    var live = 0;
+    for (var k in this.entries) {
+        if (now - this.entries[k].last_accessed >= this.ttl) {
+            expired.push(k);
+        } else {
+            live++;
+        }
+    }
+    for (var i = 0; i < expired.length; i++) {
+        var entry = this.entries[expired[i]];
+        delete this.entries[expired[i]];
+        this.purged(entry.value);
+    }
+    if (live && !this.timeout) {
+        this.timeout = setTimeout(this.purge.bind(this), this.ttl);
+    }
+};
+
+var LinkCache = function (factory, ttl) {
+    this.factory = factory;
+    this.cache = new Cache(ttl, function(link) { link.close(); });
+}
+
+LinkCache.prototype.clear = function () {
+    this.cache.clear();
+}
+
+LinkCache.prototype.get = function (address) {
+    var link = this.cache.get(address);
+    if (link === undefined) {
+        link = this.factory(address);
+        this.cache.put(address, link);
+    }
+    return link;
+};
+
+var Server = function (container, address, options) {
+    this.options = options || {};
+    var u = url.parse(address);
+    //TODO: handle scheme and user/password if present
+    this.connection = container.connect({'host':u.hostname, 'port':u.port});
+    this.connection.on('connection_open', this._connection_open.bind(this));
+    this.connection.on('message', this._request.bind(this));
+    this.receiver = this.connection.attach_receiver(u.path.substr(1));
+    this.callbacks = {};
+    this._send = undefined;
+    this._clear = undefined;
+};
+
+function match(desired, offered) {
+    if (offered) {
+        if (Array.isArray(offered)) {
+            return offered.indexOf(desired) > -1;
+        } else {
+            return desired === offered;
+        }
+    } else {
+        return false;
+    }
+}
+
+Server.prototype._connection_open = function (context) {
+    if (match('ANONYMOUS-RELAY', 
this.connection.remote.open.offered_capabilities)) {
+        var relay = this.connection.attach_sender({target:{}});
+        this._send = function (msg) { relay.send(msg); };
+    } else {
+        var cache = new 
LinkCache(this.connection.attach_sender.bind(this.connection), 
this.options.cache_ttl || 60000);
+        this._send = function (msg) { var s = cache.get(msg.properties.to); if 
(s) s.send(msg); };
+        this._clear = function () { cache.clear(); }
+    }
+}
+
+Server.prototype._respond = function (response) {
+    var server = this;
+    return function (result, error) {
+        if (error) {
+            response.properties.subject = error.name || 'error';
+            response.body = error.description || error;
+        } else {
+            response.properties.subject = 'ok';
+            response.body = result;
+        }
+        server._send(response);
+    };
+}
+
+Server.prototype._request = function (context) {
+    var request = context.message;
+    var response = {properties:{}};
+    response.properties.to = request.properties.reply_to;
+    response.properties.correlation_id = request.properties.message_id;
+    var callback = this.callbacks[request.properties.subject];
+    if (callback) {
+        callback(request.body, this._respond(response));
+    } else {
+        response.properties.subject = 'bad-method';
+        response.body = 'Unrecognised method ' + request.properties.subject;
+        this._send(response);
+    }
+};
+
+Server.prototype.bind_sync = function (f, name) {
+    this.callbacks[name || f.name] = function (args, callback) { var result = 
f(args); callback(result); };
+};
+Server.prototype.bind = function (f, name) {
+    this.callbacks[name || f.name] = f;
+};
+
+Server.prototype.close = function () {
+    if (this._clear) this._clear();
+    this.receiver.close();
+    this.connection.close();
+};
+
+module.exports = {
+    server : function(container, address, options) { return new 
Server(container, address, options); },
+    client : function(connection, address) { return new Client(connection, 
address); }
+};
+
+},{"url":29}],8:[function(require,module,exports){
+(function (Buffer){
+/*
+ * Copyright 2015 Red Hat Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+
+var frames = require('./frames.js');
+var log = require('./log.js');
+var Transport = require('./transport.js');
+
+var sasl_codes = {
+    "OK":0,
+    "AUTH":1,
+    "SYS":2,
+    "SYS_PERM":3,
+    "SYS_TEMP":4,
+};
+
+var SASL_PROTOCOL_ID = 0x03;
+
+function intersection(lista, listb) {
+    return lista.filter(function (a) { return listb.indexOf(a) >= 0; });
+}
+function extract(buffer) {
+    var results = [];
+    var start = 0;
+    var i = 0;
+    while (i < buffer.length) {
+        if (buffer[i] === 0x00) {
+            if (i > start) results.push(buffer.toString('utf8', start, i));
+            else results.push(null);
+            start = ++i;
+        } else {
+            ++i;
+        }
+    }
+    if (i > start) results.push(buffer.toString('utf8', start, i));
+    else results.push(null);
+    return results;
+}
+
+var PlainServer = function(callback) {
+    this.callback = callback;
+    this.outcome = undefined;
+    this.username = undefined;
+};
+
+PlainServer.prototype.start = function(response) {
+    var fields = extract(response);
+    if (fields.length !== 3) {
+        this.connection.sasl_failed('Unexpected response in PLAIN, got ' + 
fields.length + ' fields, expected 3');
+    }
+    if (this.callback(fields[1], fields[2])) {
+        this.outcome = true;
+        this.username = fields[1];
+    } else {
+        this.outcome = false;
+    }
+};
+
+var PlainClient = function(username, password) {
+    this.username = username;
+    this.password = password;
+};
+
+PlainClient.prototype.start = function() {
+    var response = new Buffer(1 + this.username.length + 1 + 
this.password.length);
+    response.writeUInt8(0, 0);
+    response.write(this.username, 1);
+    response.writeUInt8(0, 1 + this.username.length);
+    response.write(this.password, 1 + this.username.length + 1);
+    return response;
+};
+
+var AnonymousServer = function() {
+    this.outcome = undefined;
+    this.username = undefined;
+};
+
+AnonymousServer.prototype.start = function(response) {
+    this.outcome = true;
+    this.username = response ? response.toString('utf8') : 'anonymous';
+};
+
+var AnonymousClient = function(name) {
+    this.username = name ? name : 'anonymous';
+};
+
+AnonymousClient.prototype.start = function() {
+    var response = new Buffer(1 + this.username.length);
+    response.writeUInt8(0, 0);
+    response.write(this.username, 1);
+    return response;
+};
+
+var ExternalServer = function() {
+    this.outcome = undefined;
+    this.username = undefined;
+};
+
+ExternalServer.prototype.start = function(response) {
+    this.outcome = true;
+};
+
+var ExternalClient = function() {
+    this.username = undefined;
+};
+
+ExternalClient.prototype.start = function() {
+    return null;
+};
+
+/**
+ * The mechanisms argument is a map of mechanism names to factory
+ * functions for objects that implement that mechanism.
+ */
+var SaslServer = function (connection, mechanisms) {
+    this.connection = connection;
+    this.transport = new Transport(connection.amqp_transport.identifier, 
SASL_PROTOCOL_ID, frames.TYPE_SASL, this);
+    this.next = connection.amqp_transport;
+    this.mechanisms = mechanisms;
+    this.mechanism = undefined;
+    this.outcome = undefined;
+    this.username = undefined;
+    var mechlist = Object.getOwnPropertyNames(mechanisms);
+    
this.transport.encode(frames.sasl_frame(frames.sasl_mechanisms({sasl_server_mechanisms:mechlist}).described()));
+};
+
+SaslServer.prototype.do_step = function (challenge) {
+    if (this.mechanism.outcome === undefined) {
+        
this.transport.encode(frames.sasl_frame(frames.sasl_challenge({'challenge':challenge}).described()));
+    } else {
+        this.outcome = this.mechanism.outcome ? sasl_codes.OK : 
sasl_codes.AUTH;
+        this.transport.encode(frames.sasl_frame(frames.sasl_outcome({code: 
this.outcome}).described()));
+        if (this.outcome === sasl_codes.OK) {
+            this.username = this.mechanism.username;
+            this.transport.write_complete = true;
+            this.transport.read_complete = true;
+        }
+    }
+};
+
+SaslServer.prototype.on_sasl_init = function (frame) {
+    var f = this.mechanisms[frame.performative.mechanism];
+    if (f) {
+        this.mechanism = f();
+        var challenge = 
this.mechanism.start(frame.performative.initial_response);
+        this.do_step(challenge);
+    } else {
+        this.outcome = sasl_codes.AUTH;
+        this.transport.encode(frames.sasl_frame(frames.sasl_outcome({code: 
this.outcome}).described()));
+    }
+};
+SaslServer.prototype.on_sasl_response = function (frame) {
+    this.do_step(this.mechanism.step(frame.performative.response));
+};
+
+SaslServer.prototype.has_writes_pending = function () {
+    return this.transport.has_writes_pending() || 
this.next.has_writes_pending();
+}
+
+SaslServer.prototype.write = function (socket) {
+    if (this.transport.write_complete && this.transport.pending.length === 0) {
+        return this.next.write(socket);
+    } else {
+        return this.transport.write(socket);
+    }
+};
+
+SaslServer.prototype.read = function (buffer) {
+    if (this.transport.read_complete) {
+        return this.next.read(buffer);
+    } else {
+        return this.transport.read(buffer);
+    }
+};
+
+var SaslClient = function (connection, mechanisms) {
+    this.connection = connection;
+    this.transport = new Transport(connection.amqp_transport.identifier, 
SASL_PROTOCOL_ID, frames.TYPE_SASL, this);
+    this.next = connection.amqp_transport;
+    this.mechanisms = mechanisms;
+    this.mechanism = undefined;
+    this.mechanism_name = undefined;
+    this.failed = false;
+};
+
+SaslClient.prototype.on_sasl_mechanisms = function (frame) {
+    for (var i = 0; this.mechanism === undefined && i < 
frame.performative.sasl_server_mechanisms.length; i++) {
+        var mech = frame.performative.sasl_server_mechanisms[i];
+        var f = this.mechanisms[mech];
+        if (f) {
+            this.mechanism = f();
+            this.mechanism_name = mech;
+        }
+    }
+    if (this.mechanism) {
+        var response = this.mechanism.start();
+        
this.transport.encode(frames.sasl_frame(frames.sasl_init({'mechanism':this.mechanism_name,'initial_response':response}).described()));
+    } else {
+        this.failed = true;
+        this.connection.sasl_failed('No suitable mechanism; server supports ' 
+ frame.performative.sasl_server_mechanisms);
+    }
+};
+SaslClient.prototype.on_sasl_challenge = function (frame) {
+    var response = this.mechanism.step(frame.performative.challenge);
+    
this.transport.encode(frames.sasl_frame(frames.sasl_response({'response':response}).described()));
+};
+SaslClient.prototype.on_sasl_outcome = function (frame) {
+    switch (frame.performative.code) {
+    case sasl_codes.OK:
+        this.transport.read_complete = true;
+        this.transport.write_complete = true;
+        break;
+    default:
+        this.transport.write_complete = true;
+        this.connection.sasl_failed("Failed to authenticate: " + 
frame.performative.code);
+    }
+};
+
+SaslClient.prototype.has_writes_pending = function () {
+    return this.transport.has_writes_pending() || 
this.next.has_writes_pending();
+}
+
+SaslClient.prototype.write = function (socket) {
+    if (this.transport.write_complete) {
+        return this.next.write(socket);
+    } else {
+        return this.transport.write(socket);
+    }
+};
+
+SaslClient.prototype.read = function (buffer) {
+    if (this.transport.read_complete) {
+        return this.next.read(buffer);
+    } else {
+        return this.transport.read(buffer);
+    }
+};
+
+var default_server_mechanisms = {
+    enable_anonymous: function () {
+        this['ANONYMOUS'] = function() { return new AnonymousServer(); };
+    },
+    enable_plain: function (callback) {
+        this['PLAIN'] = function() { return new PlainServer(callback); };
+    }
+};
+
+var default_client_mechanisms = {
+    enable_anonymous: function (name) {
+        this['ANONYMOUS'] = function() { return new AnonymousClient(name); };
+    },
+    enable_plain: function (username, password) {
+        this['PLAIN'] = function() { return new PlainClient(username, 
password); };
+    },
+    enable_external: function () {
+        this['EXTERNAL'] = function() { return new ExternalClient(); };
+    }
+};
+
+module.exports = {
+    Client : SaslClient,
+    Server : SaslServer,
+    server_mechanisms : function () {
+        return Object.create(default_server_mechanisms);
+    },
+    client_mechanisms : function () {
+        return Object.create(default_client_mechanisms);
+    },
+    server_add_external: function (mechs) {
+        mechs['EXTERNAL'] = function() { return new ExternalServer(); };
+        return mechs;
+    }
+};
+
+}).call(this,require("buffer").Buffer)
+},{"./frames.js":3,"./log.js":5,"./transport.js":11,"buffer":19}],9:[function(require,module,exports){
+(function (Buffer){
+/*
+ * Copyright 2015 Red Hat Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+
+var frames = require('./frames.js');
+var link = require('./link.js');
+var log = require('./log.js');
+var message = require('./message.js');
+var types = require('./types.js');
+var util = require('./util.js');
+var EndpointState = require('./endpoint.js');
+
+var EventEmitter = require('events').EventEmitter;
+
+var CircularBuffer = function (capacity) {
+    this.capacity = capacity;
+    this.size = 0;
+    this.head = 0;
+    this.tail = 0;
+    this.entries = [];
+};
+
+CircularBuffer.prototype.available = function () {
+    return this.capacity - this.size;
+};
+
+CircularBuffer.prototype.push = function (o) {
+    if (this.size < this.capacity) {
+        this.entries[this.tail] = o;
+        this.tail = (this.tail + 1) % this.capacity;
+        this.size++;
+    } else {
+        throw Error('circular buffer overflow: head=' + this.head + ' tail=' + 
this.tail + ' size=' + this.size + ' capacity=' + this.capacity);
+    }
+};
+
+CircularBuffer.prototype.pop_if = function (f) {
+    var count = 0;
+    while (this.size && f(this.entries[this.head])) {
+        this.entries[this.head] = undefined;
+        this.head = (this.head + 1) % this.capacity;
+        this.size--;
+        count++;
+    }
+    return count;
+};
+
+CircularBuffer.prototype.by_id = function (id) {
+    if (this.size > 0) {
+        var gap = id - this.entries[this.head].id;
+        if (gap < this.size) {
+            return this.entries[(this.head + gap) % this.capacity];
+        }
+    }
+    return undefined;
+};
+
+CircularBuffer.prototype.get_head = function (id) {
+    return this.size > 0 ? this.entries[this.head] : undefined;
+};
+
+
+var Outgoing = function () {
+    this.deliveries = new CircularBuffer(2048/*TODO= configurable?*/);
+    this.updated = [];
+    this.next_delivery_id = 0;
+    this.next_pending_delivery = 0;
+    this.next_transfer_id = 0;
+    this.window = types.MAX_UINT;
+    this.remote_next_transfer_id = undefined;
+    this.remote_window = undefined;
+};
+
+Outgoing.prototype.available = function () {
+    return this.deliveries.available();
+};
+
+Outgoing.prototype.send = function (sender, tag, data, format) {
+    var d = {'id':this.next_delivery_id++,
+             'tag':tag,
+             'link':sender,
+             'data': data,
+             'format':format ? format : 0,
+             'sent': false,
+             'settled': false,
+             'state': undefined,
+             'remote_settled': false,
+             'remote_state': undefined};
+    this.deliveries.push(d);
+    return d;
+};
+
+Outgoing.prototype.on_begin = function (fields) {
+    this.remote_window = fields.incoming_window;
+};
+
+Outgoing.prototype.on_flow = function (fields) {
+    this.remote_next_transfer_id = fields.next_incoming_id;
+    this.remote_window = fields.incoming_window;
+};
+
+Outgoing.prototype.on_disposition = function (fields) {
+    var last = fields.last ? fields.last : fields.first;
+    for (var i = fields.first; i <= last; i++) {
+        var d = this.deliveries.by_id(i);
+        if (!d) {
+            console.log('Could not find delivery for ' + i + ' [' + 
JSON.stringify(fields) + ']');
+        }
+        if (d && !d.remote_settled) {
+            var updated = false;
+            if (fields.settled) {
+                d.remote_settled = fields.settled;
+                updated = true;
+            }
+            if (fields.state && fields.state !== d.remote_state) {
+                d.remote_state = fields.state;
+                updated = true;
+            }
+            if (updated) {
+                this.updated.push(d);
+            }
+        }
+    }
+};
+
+Outgoing.prototype.transfer_window = function() {
+    if (this.remote_window) {
+        return this.remote_window - (this.next_transfer_id - 
this.remote_next_transfer_id);
+    }
+};
+
+Outgoing.prototype.process = function() {
+    // send pending deliveries for which there is credit:
+    while (this.next_pending_delivery < this.next_delivery_id) {
+        var d = this.deliveries.by_id(this.next_pending_delivery);
+        if (d) {
+            if (d.link.has_credit()) {
+                d.link.delivery_count++;
+                //TODO: fragment as appropriate
+                d.transfers_required = 1;
+                if (this.transfer_window() >= d.transfers_required) {
+                    this.next_transfer_id += d.transfers_required;
+                    this.window -= d.transfers_required;
+                    
d.link.session.output(frames.transfer({'handle':d.link.local.handle,'message_format':d.format,'delivery_id':d.id,
 'delivery_tag':d.tag}).described(), d.data);
+                    d.link.credit--;
+                    this.next_pending_delivery++;
+                } else {
+                    log.flow('Incoming window of peer preventing sending 
further transfers: remote_window=' + this.remote_window + ", 
remote_next_transfer_id=" + this.remote_next_transfer_id
+                               + ", next_transfer_id=" + 
this.next_transfer_id);
+                    break;
+                }
+            } else {
+                log.flow('Link has no credit');
+                break;
+            }
+        } else {
+            console.log('ERROR: Next pending delivery not found: ' + 
this.next_pending_delivery);
+            break;
+        }
+    }
+
+    // notify application of any updated deliveries:
+    for (var i = 0; i < this.updated.length; i++) {
+        var d = this.updated[i];
+        if (d.remote_state) {
+            d.remote_state = message.unwrap_outcome(d.remote_state);
+            if (d.remote_state && d.remote_state.constructor.composite_type) {
+                d.link.dispatch(d.remote_state.constructor.composite_type, 
d.link._context({'delivery':d}));
+            }
+        }
+        if (d.remote_settled) d.link.dispatch('settled', 
d.link._context({'delivery':d}));
+    }
+    this.updated = [];
+
+    // remove any fully settled deliveries:
+    this.deliveries.pop_if(function (d) { return d.settled && 
d.remote_settled; });
+};
+
+var Incoming = function () {
+    this.deliveries = new CircularBuffer(2048/*TODO: configurable?*/);
+    this.updated = [];
+    this.next_transfer_id = 0;
+    this.next_delivery_id = undefined;
+    this.window = 2048/*TODO: configurable?*/;
+    this.remote_next_transfer_id = undefined;
+    this.remote_window = undefined;
+};
+
+Incoming.prototype.update = function (delivery, settled, state) {
+    if (delivery) {
+        delivery.settled = settled;
+        if (state !== undefined) delivery.state = state;
+        if (!delivery.remote_settled) {
+            this.updated.push(delivery);
+        }
+        delivery.link.connection._register();
+    }
+};
+
+Incoming.prototype.on_transfer = function(frame, receiver) {
+    this.next_transfer_id++;
+    if (receiver.is_open()) {
+        if (this.next_delivery_id === undefined) {
+            this.next_delivery_id = frame.performative.delivery_id;
+        }
+        var current;
+        var data;
+        var last = this.deliveries.get_head();
+        if (last && last.incomplete) {
+            if (frame.performative.delivery_id !== undefined && 
this.next_delivery_id != frame.performative.delivery_id) {
+                //TODO: better error handling
+                throw Error("frame sequence error: delivery " + 
this.next_delivery_id + " not complete, got " + frame.performative.delivery_id);
+            }
+            current = last;
+            data = Buffer.concat([current.data, frame.payload], 
current.data.size() + frame.payload.size());
+        } else if (this.next_delivery_id === frame.performative.delivery_id) {
+            current = {'id':frame.performative.delivery_id,
+                       'tag':frame.performative.delivery_tag,
+                       'link':receiver,
+                       'settled': false,
+                       'state': undefined,
+                       'remote_settled': frame.performative.settled,
+                       'remote_state': undefined};
+            var self = this;
+            current.update = function (settled, state) { self.update(current, 
settled, state); };
+            this.deliveries.push(current);
+            data = frame.payload;
+        } else {
+            //TODO: better error handling
+            throw Error("frame sequence error: expected " + 
this.next_delivery_id + ", got " + frame.performative.delivery_id);
+        }
+        current.incomplete = frame.performative.more;
+        if (current.incomplete) {
+            current.data = data;
+        } else {
+            receiver.credit--;
+            receiver.delivery_count++;
+            this.next_delivery_id++;
+            receiver.dispatch('message', 
receiver._context({'message':message.decode(data), 'delivery':current}));
+        }
+    }
+};
+
+Incoming.prototype.process = function () {
+    if (this.updated.length > 0) {
+        var first;
+        var last;
+        var next_id;
+
+        for (var i = 0; i < this.updated.length; i++) {
+            var delivery = this.updated[i];
+            if (first === undefined) {
+                first = delivery;
+                last = delivery;
+                next_id = delivery.id;
+            }
+
+            if (!message.are_outcomes_equivalent(last.state, delivery.state) 
|| last.settled !== delivery.settled || next_id !== delivery.id) {
+                
first.link.session.output(frames.disposition({'role':true,'first':first.id,'last':last.id,
 'state':first.state, 'settled':first.settled}).described());
+                first = delivery;
+                last = delivery;
+                next_id = delivery.id;
+            } else {
+                if (last.id !== delivery.id) {
+                    last = delivery;
+                }
+                next_id++;
+            }
+        }
+        if (first !== undefined && last !== undefined) {
+            
first.link.session.output(frames.disposition({'role':true,'first':first.id,'last':last.id,
 'state':first.state, 'settled':first.settled}).described());
+        }
+
+        this.updated = [];
+    }
+
+    // remove any fully settled deliveries:
+    this.deliveries.pop_if(function (d) { return d.settled; });
+};
+
+Incoming.prototype.on_begin = function (fields) {
+    this.remote_window = fields.outgoing_window;
+};
+
+Incoming.prototype.on_flow = function (fields) {
+    this.remote_next_transfer_id = fields.next_outgoing_id;
+    this.remote_window = fields.outgoing_window;
+};
+
+Incoming.prototype.on_disposition = function (fields) {
+    var last = fields.last ? fields.last : fields.first;
+    for (var i = fields.first; i <= last; i++) {
+        var d = this.deliveries.by_id(i);
+        if (!d) {
+            console.log('Could not find delivery for ' + i);
+        }
+        if (d && !d.remote_settled) {
+            var updated = false;
+            if (fields.settled) {
+                d.remote_settled = fields.settled;
+                updated = true;
+            }
+            if (fields.state && fields.state !== d.remote_state) {
+                d.remote_state = fields.state;
+                updated = true;
+            }
+            if (updated) {
+                console.log(d.link.connection.options.id + ' added delivery to 
updated list following receipt of disposition for incoming deliveries');
+                this.updated.push(d);
+            }
+        }
+    }
+
+};
+
+var Session = function (connection, local_channel) {
+    this.connection = connection;
+    this.outgoing = new Outgoing();
+    this.incoming = new Incoming();
+    this.state = new EndpointState();
+    this.local = {'channel': local_channel, 'handles':{}};
+    this.local.begin = 
frames.begin({next_outgoing_id:this.outgoing.next_transfer_id,incoming_window:this.incoming.window,outgoing_window:this.outgoing.window});
+    this.local.end = frames.end();
+    this.remote = {'handles':{}};
+    this.links = {}; // map by name
+    this.options = {};
+};
+Session.prototype = Object.create(EventEmitter.prototype);
+Session.prototype.constructor = Session;
+
+Session.prototype.reset = function() {
+    this.state.disconnected();
+    this.outgoing = new Outgoing();
+    this.incoming = new Incoming();
+    this.remote = {'handles':{}};
+    for (var l in this.links) {
+        this.links[l].reset();
+    }
+};
+
+Session.prototype.dispatch = function(name, context) {
+    log.events('Session got event: '+ name);
+    if (this.listeners(name).length) {
+        EventEmitter.prototype.emit.apply(this, arguments);
+    } else {
+        this.connection.dispatch.apply(this.connection, arguments);
+    }
+};
+Session.prototype.output = function (frame, payload) {
+    this.connection._write_frame(this.local.channel, frame, payload);
+};
+
+Session.prototype.create_sender = function (name, opts) {
+    return this.create_link(name, link.Sender, opts);
+};
+
+Session.prototype.create_receiver = function (name, opts) {
+    return this.create_link(name, link.Receiver, opts);
+};
+
+function attach(factory, args, remote_terminus) {
+    var opts = args ? args : {};
+    if (typeof args === 'string') {
+        opts = {};
+        opts[remote_terminus] = args;
+    }
+    if (!opts.name) opts.name = util.generate_uuid();
+    var l = factory(opts.name, opts);
+    for (var t in {'source':0, 'target':0}) {
+        if (opts[t]) {
+            if (typeof opts[t] === 'string') {
+                opts[t] = {'address' : opts[t]};
+            }
+            l['set_' + t](opts[t]);
+        }
+    }
+    l.attach();
+    return l;
+}
+
+Session.prototype.get_option = function (name, default_value) {
+    if (this.options[name] !== undefined) return this.options[name];
+    else return this.connection.get_option(name, default_value);
+};
+
+Session.prototype.attach_sender = function (args) {
+    return attach(this.create_sender.bind(this), args, 'target');
+};
+Session.prototype.open_sender = Session.prototype.attach_sender;//alias
+
+Session.prototype.attach_receiver = function (args) {
+    return attach(this.create_receiver.bind(this), args, 'source');
+};
+Session.prototype.open_receiver = Session.prototype.attach_receiver;//alias
+
+Session.prototype.create_link = function (name, constructor, opts) {
+    var i = 0;
+    while (this.local.handles[i]) i++;
+    var l = new constructor(this, name, i, opts);
+    this.links[name] = l;
+    this.local.handles[i] = l;
+    return l;
+};
+
+Session.prototype.begin = function () {
+    if (this.state.open()) {
+        this.connection._register();
+    }
+};
+Session.prototype.open = Session.prototype.begin;
+
+Session.prototype.end = function () {
+    if (this.state.close()) {
+        this.connection._register();
+    }
+};
+Session.prototype.close = Session.prototype.end;
+
+Session.prototype.is_open = function () {
+    return this.connection.is_open() && this.state.is_open();
+};
+
+Session.prototype.is_closed = function () {
+    return this.connection.is_closed() || this.state.is_closed();
+};
+
+Session.prototype._process = function () {
+    do {
+        if (this.state.need_open()) {
+            this.output(this.local.begin.described());
+        }
+
+        this.outgoing.process();
+        this.incoming.process();
+        for (var k in this.links) {
+            this.links[k]._process();
+        }
+
+        if (this.state.need_close()) {
+            this.output(this.local.end.described());
+        }
+    } while (!this.state.has_settled());
+};
+
+Session.prototype.send = function (sender, tag, data, format) {
+    var d = this.outgoing.send(sender, tag, data, format);
+    this.connection._register();
+    return d;
+};
+
+Session.prototype._write_flow = function (link) {
+    var fields = {'next_incoming_id':this.incoming.next_transfer_id,
+                  'incoming_window':this.incoming.window,
+                  'next_outgoing_id':this.outgoing.next_transfer_id,
+                  'outgoing_window':this.outgoing.window
+                 };
+    if (link) {
+        fields.delivery_count = link.delivery_count;
+        fields.handle = link.local.handle;
+        fields.link_credit = link.credit;
+    }
+    this.output(frames.flow(fields).described());
+};
+
+Session.prototype.on_begin = function (frame) {
+    if (this.state.remote_opened()) {
+        if (!this.remote.channel) {
+            this.remote.channel = frame.channel;
+        }
+        this.remote.begin = frame.performative;
+        this.outgoing.on_begin(frame.performative);
+        this.incoming.on_begin(frame.performative);
+        this.open();
+        this.dispatch('session_open', this._context());
+    } else {
+        throw Error('Begin already received');
+    }
+};
+Session.prototype.on_end = function (frame) {
+    if (this.state.remote_closed()) {
+        this.remote.end = frame.performative;
+        this.close();
+        this.dispatch('session_close', this._context());
+    } else {
+        throw Error('End already received');
+    }
+};
+
+Session.prototype.on_attach = function (frame) {
+    var name = frame.performative.name;
+    var link = this.links[name];
+    if (!link) {
+        // if role is true, peer is receiver, so we are sender
+        link = frame.performative.role ? this.create_sender(name) : 
this.create_receiver(name);
+    }
+    this.remote.handles[frame.performative.handle] = link;
+    link.on_attach(frame);
+    link.remote.attach = frame.performative;
+};
+
+Session.prototype.on_disposition = function (frame) {
+    if (frame.performative.role) {
+        log.events('Received disposition for outgoing transfers');
+        this.outgoing.on_disposition(frame.performative);
+    } else {
+        log.events('Received disposition for incoming transfers');
+        this.incoming.on_disposition(frame.performative);
+    }
+    this.connection._register();
+}
+
+Session.prototype.on_flow = function (frame) {
+    this.outgoing.on_flow(frame.performative);
+    this.incoming.on_flow(frame.performative);
+    if (frame.performative.handle !== undefined) {
+        this._get_link(frame).on_flow(frame);
+    }
+    this.connection._register();
+}
+Session.prototype._context = function (c) {
+    var context = c ? c : {};
+    context.session = this;
+    return this.connection._context(context);
+};
+
+Session.prototype._get_link = function (frame) {
+    var handle = frame.performative.handle;
+    var link = this.remote.handles[handle];
+    if (!link) {
+        throw Error('Invalid handle ' + handle);
+    }
+    return link;
+};
+
+Session.prototype.on_detach = function (frame) {
+    this._get_link(frame).on_detach(frame);
+    //remove link
+    var handle = frame.performative.handle;
+    var link = this.remote.handles[handle];
+    delete this.remote.handles[handle];
+    delete this.local.handles[link.local.handle];
+    delete this.links[link.name];
+};
+
+Session.prototype.on_transfer = function (frame) {
+    this.incoming.on_transfer(frame, this._get_link(frame));
+};
+
+module.exports = Session;
+
+}).call(this,require("buffer").Buffer)
+},{"./endpoint.js":2,"./frames.js":3,"./link.js":4,"./log.js":5,"./message.js":6,"./types.js":12,"./util.js":13,"buffer":19,"events":23}],10:[function(require,module,exports){
+/*
+ * Copyright 2015 Red Hat Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+
+var types = require('./types.js');
+
+var terminus = {};
+var by_descriptor = {};
+
+function define_terminus(def) {
+    var c = types.define_composite(def);
+    terminus[def.name] = c.create;
+    by_descriptor[Number(c.descriptor.numeric).toString(10)] = c;
+    by_descriptor[c.descriptor.symbolic] = c;
+};
+
+terminus.unwrap = function(field) {
+    if (field && field.descriptor) {
+        var c = by_descriptor[field.descriptor.value];
+        if (c) {
+            return new c(field.value)
+        } else {
+            console.log('Unknown terminus: ' + field.descriptor);
+        }
+    }
+    return null;
+};
+
+define_terminus(
+    {name:"source",
+     code:0x28,
+     fields: [
+         {name:"address", type:"string"},
+         {name:"durable", type:"uint", default_value:0},
+         {name:"expiry_policy", type:"symbol", default_value:"session-end"},
+         {name:"timeout", type:"uint", default_value:0},
+         {name:"dynamic", type:"boolean", default_value:false},
+         {name:"dynamic_node_properties", type:"symbolic_map"},
+         {name:"distribution_mode", type:"symbol"},
+         {name:"filter", type:"symbolic_map"},
+         {name:"default_outcome", type:"*"},
+         {name:"outcomes", type:"symbol", multiple:true},
+         {name:"capabilities", type:"symbol", multiple:true}
+     ]
+    });
+
+define_terminus(
+    {name:"target",
+     code:0x29,
+     fields: [
+         {name:"address", type:"string"},
+         {name:"durable", type:"uint", default_value:0},
+         {name:"expiry_policy", type:"symbol", default_value:"session-end"},
+         {name:"timeout", type:"uint", default_value:0},
+         {name:"dynamic", type:"boolean", default_value:false},
+         {name:"dynamic_node_properties", type:"symbolic_map"},
+         {name:"capabilities", type:"symbol", multiple:true}
+     ]
+    });
+
+module.exports = terminus;
+
+},{"./types.js":12}],11:[function(require,module,exports){
+(function (Buffer){
+/*
+ * Copyright 2015 Red Hat Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License

<TRUNCATED>

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to