rabbitmq-client
Version:
Robust, typed, RabbitMQ (0-9-1) client library
928 lines (927 loc) • 34.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReplyCode = exports.FrameType = exports.Cmd = exports.HEARTBEAT_FRAME = exports.PROTOCOL_HEADER = void 0;
exports.decodeFrame = decodeFrame;
exports.encodeFrame = encodeFrame;
exports.genFrame = genFrame;
exports.genContentFrames = genContentFrames;
/*
* This module encodes to, and decodes from, the AMQP binary protocol
*/
const exception_1 = require("./exception");
/** @internal AMQP 0091 */
exports.PROTOCOL_HEADER = Buffer.from([65, 77, 81, 80, 0, 0, 9, 1]);
/** @internal */
exports.HEARTBEAT_FRAME = Buffer.from([8, 0, 0, 0, 0, 0, 0, 206]);
/** @ignore */
var Cmd;
(function (Cmd) {
/** @internal */
Cmd[Cmd["ConnectionStart"] = 655370] = "ConnectionStart";
/** @internal */
Cmd[Cmd["ConnectionStartOK"] = 655371] = "ConnectionStartOK";
/** @internal */
Cmd[Cmd["ConnectionSecure"] = 655380] = "ConnectionSecure";
/** @internal */
Cmd[Cmd["ConnectionSecureOK"] = 655381] = "ConnectionSecureOK";
/** @internal */
Cmd[Cmd["ConnectionTune"] = 655390] = "ConnectionTune";
/** @internal */
Cmd[Cmd["ConnectionTuneOK"] = 655391] = "ConnectionTuneOK";
/** @internal */
Cmd[Cmd["ConnectionOpen"] = 655400] = "ConnectionOpen";
/** @internal */
Cmd[Cmd["ConnectionOpenOK"] = 655401] = "ConnectionOpenOK";
/** @internal */
Cmd[Cmd["ConnectionClose"] = 655410] = "ConnectionClose";
/** @internal */
Cmd[Cmd["ConnectionCloseOK"] = 655411] = "ConnectionCloseOK";
/** @internal */
Cmd[Cmd["ConnectionBlocked"] = 655420] = "ConnectionBlocked";
/** @internal */
Cmd[Cmd["ConnectionUnblocked"] = 655421] = "ConnectionUnblocked";
/** @internal */
Cmd[Cmd["ChannelOpen"] = 1310730] = "ChannelOpen";
/** @internal */
Cmd[Cmd["ChannelOpenOK"] = 1310731] = "ChannelOpenOK";
/** @internal */
Cmd[Cmd["ChannelClose"] = 1310760] = "ChannelClose";
/** @internal */
Cmd[Cmd["ChannelCloseOK"] = 1310761] = "ChannelCloseOK";
Cmd[Cmd["ExchangeDeclare"] = 2621450] = "ExchangeDeclare";
Cmd[Cmd["ExchangeDeclareOK"] = 2621451] = "ExchangeDeclareOK";
Cmd[Cmd["ExchangeDelete"] = 2621460] = "ExchangeDelete";
Cmd[Cmd["ExchangeDeleteOK"] = 2621461] = "ExchangeDeleteOK";
Cmd[Cmd["ExchangeBind"] = 2621470] = "ExchangeBind";
Cmd[Cmd["ExchangeBindOK"] = 2621471] = "ExchangeBindOK";
Cmd[Cmd["ExchangeUnbind"] = 2621480] = "ExchangeUnbind";
Cmd[Cmd["ExchangeUnbindOK"] = 2621491] = "ExchangeUnbindOK";
Cmd[Cmd["QueueDeclare"] = 3276810] = "QueueDeclare";
Cmd[Cmd["QueueDeclareOK"] = 3276811] = "QueueDeclareOK";
Cmd[Cmd["QueueBind"] = 3276820] = "QueueBind";
Cmd[Cmd["QueueBindOK"] = 3276821] = "QueueBindOK";
Cmd[Cmd["QueuePurge"] = 3276830] = "QueuePurge";
Cmd[Cmd["QueuePurgeOK"] = 3276831] = "QueuePurgeOK";
Cmd[Cmd["QueueDelete"] = 3276840] = "QueueDelete";
Cmd[Cmd["QueueDeleteOK"] = 3276841] = "QueueDeleteOK";
Cmd[Cmd["QueueUnbind"] = 3276850] = "QueueUnbind";
Cmd[Cmd["QueueUnbindOK"] = 3276851] = "QueueUnbindOK";
Cmd[Cmd["BasicQos"] = 3932170] = "BasicQos";
Cmd[Cmd["BasicQosOK"] = 3932171] = "BasicQosOK";
Cmd[Cmd["BasicConsume"] = 3932180] = "BasicConsume";
Cmd[Cmd["BasicConsumeOK"] = 3932181] = "BasicConsumeOK";
Cmd[Cmd["BasicCancel"] = 3932190] = "BasicCancel";
Cmd[Cmd["BasicCancelOK"] = 3932191] = "BasicCancelOK";
Cmd[Cmd["BasicPublish"] = 3932200] = "BasicPublish";
Cmd[Cmd["BasicReturn"] = 3932210] = "BasicReturn";
Cmd[Cmd["BasicDeliver"] = 3932220] = "BasicDeliver";
Cmd[Cmd["BasicGet"] = 3932230] = "BasicGet";
Cmd[Cmd["BasicGetOK"] = 3932231] = "BasicGetOK";
Cmd[Cmd["BasicGetEmpty"] = 3932232] = "BasicGetEmpty";
Cmd[Cmd["BasicAck"] = 3932240] = "BasicAck";
Cmd[Cmd["BasicReject"] = 3932250] = "BasicReject";
Cmd[Cmd["BasicRecover"] = 3932270] = "BasicRecover";
Cmd[Cmd["BasicRecoverOK"] = 3932271] = "BasicRecoverOK";
Cmd[Cmd["BasicNack"] = 3932280] = "BasicNack";
Cmd[Cmd["ConfirmSelect"] = 5570570] = "ConfirmSelect";
Cmd[Cmd["ConfirmSelectOK"] = 5570571] = "ConfirmSelectOK";
Cmd[Cmd["TxSelect"] = 5898250] = "TxSelect";
Cmd[Cmd["TxSelectOK"] = 5898251] = "TxSelectOK";
Cmd[Cmd["TxCommit"] = 5898260] = "TxCommit";
Cmd[Cmd["TxCommitOK"] = 5898261] = "TxCommitOK";
Cmd[Cmd["TxRollback"] = 5898270] = "TxRollback";
Cmd[Cmd["TxRollbackOK"] = 5898271] = "TxRollbackOK";
})(Cmd || (exports.Cmd = Cmd = {}));
/** @internal */
var FrameType;
(function (FrameType) {
FrameType[FrameType["METHOD"] = 1] = "METHOD";
FrameType[FrameType["HEADER"] = 2] = "HEADER";
FrameType[FrameType["BODY"] = 3] = "BODY";
FrameType[FrameType["HEARTBEAT"] = 8] = "HEARTBEAT";
})(FrameType || (exports.FrameType = FrameType = {}));
/** @internal */
var ReplyCode;
(function (ReplyCode) {
ReplyCode[ReplyCode["OK"] = 200] = "OK";
ReplyCode[ReplyCode["CONTENT_TOO_LARGE"] = 311] = "CONTENT_TOO_LARGE";
ReplyCode[ReplyCode["NO_ROUTE"] = 312] = "NO_ROUTE";
ReplyCode[ReplyCode["NO_CONSUMERS"] = 313] = "NO_CONSUMERS";
ReplyCode[ReplyCode["ACCESS_REFUSED"] = 403] = "ACCESS_REFUSED";
ReplyCode[ReplyCode["NOT_FOUND"] = 404] = "NOT_FOUND";
ReplyCode[ReplyCode["RESOURCE_LOCKED"] = 405] = "RESOURCE_LOCKED";
ReplyCode[ReplyCode["PRECONDITION_FAILED"] = 406] = "PRECONDITION_FAILED";
ReplyCode[ReplyCode["CONNECTION_FORCED"] = 320] = "CONNECTION_FORCED";
ReplyCode[ReplyCode["INVALID_PATH"] = 402] = "INVALID_PATH";
ReplyCode[ReplyCode["FRAME_ERROR"] = 501] = "FRAME_ERROR";
ReplyCode[ReplyCode["SYNTAX_ERROR"] = 502] = "SYNTAX_ERROR";
ReplyCode[ReplyCode["COMMAND_INVALID"] = 503] = "COMMAND_INVALID";
ReplyCode[ReplyCode["CHANNEL_ERROR"] = 504] = "CHANNEL_ERROR";
ReplyCode[ReplyCode["UNEXPECTED_FRAME"] = 505] = "UNEXPECTED_FRAME";
ReplyCode[ReplyCode["RESOURCE_ERROR"] = 506] = "RESOURCE_ERROR";
ReplyCode[ReplyCode["NOT_ALLOWED"] = 530] = "NOT_ALLOWED";
ReplyCode[ReplyCode["NOT_IMPLEMENTED"] = 540] = "NOT_IMPLEMENTED";
ReplyCode[ReplyCode["INTERNAL_ERROR"] = 541] = "INTERNAL_ERROR";
})(ReplyCode || (exports.ReplyCode = ReplyCode = {}));
const FRAME_END = 206;
/** Represents a list of boolean properties, encoded as a bitfield */
const BITS = (...keys) => {
const totalBytes = Math.ceil(keys.length / 8);
return {
sizeof() {
return totalBytes;
},
decode(src, offset) {
const res = {};
for (let byteIndex = 0; byteIndex < totalBytes; byteIndex++) {
const byte = src.readUInt8(offset + byteIndex);
for (let bitIndex = 0; bitIndex < 8 && (byteIndex * 8 + bitIndex) < keys.length; bitIndex++) {
res[keys[(byteIndex * 8) + bitIndex]] = !!((1 << bitIndex) & byte);
}
}
return [res, offset + totalBytes];
},
encode(out, props, offset) {
let byteIndex, bitIndex, key, byte;
for (byteIndex = 0; byteIndex < totalBytes; byteIndex++) {
byte = 0;
for (bitIndex = 0; bitIndex < 8; bitIndex++) {
key = keys[(byteIndex * 8) + bitIndex];
if (props[key])
byte += 1 << bitIndex;
}
out.writeUInt8(byte, offset + byteIndex);
}
return offset + totalBytes;
}
};
};
/** Represents AMQP methods parameters, which are non-null values encoded in a particular order. */
const STRUCT = (def) => ({
sizeof(props) {
let size = 0; // 12
let field, key, ptype;
for (field of def) {
if (Array.isArray(field)) {
[key, ptype] = field;
// TODO check for undefined props at a higher level
size += ptype.sizeof(props[key]);
}
else {
size += field.sizeof(props);
}
}
return size;
},
decode(src, offset) {
const result = {};
let field, key, ptype, props;
for (field of def) {
if (Array.isArray(field)) {
[key, ptype] = field;
[props, offset] = ptype.decode(src, offset);
result[key] = props;
}
else {
[props, offset] = field.decode(src, offset);
Object.assign(result, props);
}
}
return [result, offset];
},
encode(out, props, offset) {
let field, key, ptype;
if (props)
for (field of def) {
if (Array.isArray(field)) {
[key, ptype] = field;
offset = ptype.encode(out, props[key], offset);
}
else {
offset = field.encode(out, props, offset);
}
}
return offset;
}
});
/** The AMQP spec says: TABLE field names MUST start with a letter, '$' or '#' and
* may continue with letters, '$' or '#', digits, or underlines, to a maximum
* length of 128 characters.
*
* RabbitMQ, however, does not seem to enforce this. */
const SHORTSTR = {
sizeof(val) {
const str = val == null ? '' : String(val); // cast from Number
const len = Math.min(Buffer.byteLength(str), 255);
return 1 + len;
},
decode(src, offset) {
const total = offset + 1 + src.readUInt8(offset);
const val = src.toString('utf8', offset + 1, total);
return [val, total];
},
encode(out, val, offset) {
const str = val == null ? '' : String(val); // cast from Number
// truncate long strings
const len = Math.min(Buffer.byteLength(str), 255);
out.writeUInt8(len, offset);
out.write(str, offset + 1, len, 'utf8');
return 1 + offset + len;
}
};
const TYPE = {
UINT8: { id: 66,
sizeof() { return 1; },
decode(src, offset) {
return [src.readUInt8(offset), offset + 1];
},
encode(out, val, offset) {
return out.writeUInt8(val == null ? 0 : val, offset);
}
},
UINT16: { id: 66,
sizeof() { return 2; },
decode(src, offset) {
return [src.readUInt16BE(offset), offset + 2];
},
encode(out, val, offset) {
return out.writeUInt16BE(val == null ? 0 : val, offset);
}
},
UINT32: { id: 105,
sizeof() { return 4; },
decode(src, offset) {
return [src.readUInt32BE(offset), offset + 4];
},
encode(out, val, offset) {
return out.writeUInt32BE(val == null ? 0 : val, offset);
}
},
UINT64: { id: 84,
sizeof() { return 8; },
decode(src, offset) {
return [Number(src.readBigUint64BE(offset)), offset + 8];
},
encode(out, val, offset) {
return out.writeBigUint64BE(BigInt(val == null ? 0 : val), offset);
}
},
INT8: { id: 98,
sizeof() { return 1; },
decode(src, offset) {
return [src.readInt8(offset), offset + 1];
},
encode(out, val, offset) {
return out.writeInt8(val == null ? 0 : val, offset);
}
},
INT16: { id: 115,
sizeof() { return 2; },
decode(src, offset) {
return [src.readInt16BE(offset), offset + 2];
},
encode(out, val, offset) {
return out.writeInt16BE(val == null ? 0 : val, offset);
}
},
INT32: { id: 73,
sizeof() { return 4; },
decode(src, offset) {
return [src.readInt32BE(offset), offset + 4];
},
encode(out, val, offset) {
return out.writeInt32BE(val == null ? 0 : val, offset);
}
},
INT64: { id: 108,
sizeof() { return 8; },
decode(src, offset) {
return [Number(src.readBigInt64BE(offset)), offset + 8];
},
encode(out, val, offset) {
return out.writeBigInt64BE(BigInt(val == null ? 0 : val), offset);
}
},
FLOAT: { id: 102,
sizeof() { return 4; },
decode(src, offset) {
return [src.readFloatBE(offset), offset + 4];
},
encode(out, val, offset) {
return out.writeFloatBE(val, offset);
}
},
DOUBLE: { id: 100,
sizeof() { return 8; },
decode(src, offset) {
return [src.readDoubleBE(offset), offset + 8];
},
encode(out, val, offset) {
// NaN: 0x7ff80000 00000000
// Infinity: 0x7ff00000 00000000
// -Infinity: 0xfff00000 00000000
// -0: 0x80000000 00000000
return out.writeDoubleBE(val, offset);
}
},
VARBIN32: { id: 120,
sizeof(val) {
const len = val ? Buffer.byteLength(val) : 0;
return 4 + len;
},
decode(src, offset) {
const total = offset + 4 + src.readUInt32BE(offset);
const val = src.slice(offset + 4, total);
return [val, total];
},
encode(out, val, offset) {
if (val) {
const len = Buffer.byteLength(val);
offset = out.writeUInt32BE(len, offset);
if (len > 0)
out.fill(val, offset, offset + len);
return offset + len;
}
return out.writeUInt32BE(0, offset);
}
},
DECIMAL: { id: 68,
sizeof() { return 5; },
decode(src, offset) {
return [{
scale: src.readUInt8(offset),
value: src.readInt32BE(offset + 1)
}, offset + 5];
},
encode(out, val, offset) {
out.writeUInt8(val.scale, offset);
return out.writeInt32BE(val.value, offset + 1);
}
},
BOOL: { id: 116,
sizeof() { return 1; },
decode(src, offset) {
return [src.readUInt8(offset) === 1, offset + 1];
},
encode(out, val, offset) {
return out.writeUInt8(val ? 1 : 0, offset);
}
},
STRING: { id: 83,
sizeof(val) {
if (val == null)
val = '';
const len = Math.min(Buffer.byteLength(val), 0xffffffff);
return 4 + len;
},
decode(src, offset) {
const total = offset + 4 + src.readUInt32BE(offset);
const val = src.toString('utf8', offset + 4, total);
return [val, total];
},
encode(out, val, offset) {
if (val == null)
val = '';
// truncate really long strings
const len = Math.min(Buffer.byteLength(val), 0xffffffff);
out.writeUInt32BE(len, offset);
out.write(val, offset + 4, len, 'utf8');
return 4 + offset + len;
}
},
VOID: { id: 86,
sizeof() { return 0; },
decode(src, offset) {
return [null, offset];
},
encode(out, val, offset) {
return offset;
}
},
ARRAY: { id: 65,
sizeof(arr) {
let bytes = 4;
for (const el of arr) {
const etype = getBestType(el);
if (!etype)
continue; // not encodable
bytes += 1 + etype.sizeof(el);
}
return bytes;
},
decode(src, offset) {
const [data, nextOffset] = TYPE.VARBIN32.decode(src, offset);
const items = [];
const total = data.length;
let index = 0;
let val;
while (index < total) {
[val, index] = readSimpleType(data, index);
items.push(val);
}
return [items, nextOffset];
},
encode(out, val, offset) {
const start = offset;
offset += 4;
for (let index = 0; index < val.length; index++) {
const etype = getBestType(val[index]);
if (!etype)
continue; // not encodable
offset = out.writeUInt8(etype.id, offset);
offset = etype.encode(out, val[index], offset);
}
out.writeUInt32BE(offset - start - 4, start);
return offset;
}
},
TABLE: { id: 70,
sizeof(props) {
let bytes = 4;
const it = props instanceof Map ? props.entries()
: props != null ? Object.entries(props) : [];
for (const [k, v] of it) {
if (typeof v != 'undefined') {
const etype = getBestType(v);
if (!etype)
continue; // not encodable
bytes += SHORTSTR.sizeof(String(k)) + 1 + etype.sizeof(v);
}
}
return bytes;
},
decode(src, offset) {
const [data, nextOffset] = TYPE.VARBIN32.decode(src, offset);
const total = data.length;
const table = {};
let index = 0;
let key, val;
while (index < total) {
[key, index] = SHORTSTR.decode(data, index);
[val, index] = readSimpleType(data, index);
table[key] = val;
}
return [table, nextOffset];
},
encode(out, val, offset) {
const start = offset;
offset += 4;
const it = val instanceof Map ? val.entries()
: val != null ? Object.entries(val) : [];
for (const [k, v] of it) {
if (typeof v != 'undefined') {
const etype = getBestType(v);
if (!etype)
continue; // not encodable
offset = SHORTSTR.encode(out, String(k), offset);
offset = out.writeUInt8(etype.id, offset);
offset = etype.encode(out, v, offset);
}
}
out.writeUInt32BE(offset - start - 4, start);
return offset;
}
}
};
// http://www.rabbitmq.com/amqp-0-9-1-errata.html#section_3
const TYPE_BY_ID = new Map(Object.values(TYPE).map(el => [el.id, el]));
const COMMAND_CODECS = [
{ id: Cmd.ConnectionStart, ...STRUCT([
['versionMajor', TYPE.UINT8],
['versionMinor', TYPE.UINT8],
['serverProperties', TYPE.TABLE],
['mechanisms', TYPE.STRING],
['locales', TYPE.STRING]
]) },
{ id: Cmd.ConnectionStartOK, ...STRUCT([
['clientProperties', TYPE.TABLE],
['mechanism', SHORTSTR],
['response', TYPE.STRING],
['locale', SHORTSTR]
]) },
{ id: Cmd.ConnectionSecure, ...STRUCT([
['challenge', TYPE.STRING]
]) },
{ id: Cmd.ConnectionSecureOK, ...STRUCT([
['response', TYPE.STRING]
]) },
{ id: Cmd.ConnectionTune, ...STRUCT([
['channelMax', TYPE.UINT16],
['frameMax', TYPE.INT32],
['heartbeat', TYPE.UINT16]
]) },
{ id: Cmd.ConnectionTuneOK, ...STRUCT([
['channelMax', TYPE.UINT16], // 0
['frameMax', TYPE.INT32], // 0
['heartbeat', TYPE.UINT16]
]) }, // 0
{ id: Cmd.ConnectionOpen, ...STRUCT([
['virtualHost', SHORTSTR], // "/"
['rsvp1', SHORTSTR], // ""
['rsvp2', TYPE.BOOL]
]) },
{ id: Cmd.ConnectionOpenOK, ...STRUCT([
['knownHosts', SHORTSTR]
]) }, // ""
{ id: Cmd.ConnectionClose, ...STRUCT([
['replyCode', TYPE.UINT16],
['replyText', SHORTSTR], // ''
['methodId', TYPE.UINT32]
]) },
{ id: Cmd.ConnectionCloseOK, ...STRUCT([]) },
{ id: Cmd.ConnectionBlocked, ...STRUCT([
['reason', SHORTSTR]
]) }, // ""
{ id: Cmd.ConnectionUnblocked, ...STRUCT([]) },
{ id: Cmd.ChannelOpen, ...STRUCT([
['rsvp1', SHORTSTR]
]) }, // ""
{ id: Cmd.ChannelOpenOK, ...STRUCT([
['rsvp1', TYPE.STRING]
]) }, // ""
{ id: Cmd.ChannelClose, ...STRUCT([
['replyCode', TYPE.UINT16],
['replyText', SHORTSTR], // ""
['methodId', TYPE.UINT32]
]) },
{ id: Cmd.ChannelCloseOK, ...STRUCT([]) },
{ id: Cmd.ExchangeDeclare, ...STRUCT([
['rsvp1', TYPE.UINT16], // 0
['exchange', SHORTSTR],
['type', SHORTSTR], // direct
BITS('passive', 'durable', 'autoDelete', 'internal', 'nowait'),
['arguments', TYPE.TABLE]
]) }, // {}
{ id: Cmd.ExchangeDeclareOK, ...STRUCT([]) },
{ id: Cmd.ExchangeDelete, ...STRUCT([
['rsvp1', TYPE.UINT16], // 0
['exchange', SHORTSTR],
BITS('ifUnused', 'nowait')
]) },
{ id: Cmd.ExchangeDeleteOK, ...STRUCT([]) },
{ id: Cmd.ExchangeBind, ...STRUCT([
['rsvp1', TYPE.UINT16], // 0
['destination', SHORTSTR],
['source', SHORTSTR],
['routingKey', SHORTSTR], // ""
['nowait', TYPE.BOOL],
['arguments', TYPE.TABLE]
]) }, // {}
{ id: Cmd.ExchangeBindOK, ...STRUCT([]) },
{ id: Cmd.ExchangeUnbind, ...STRUCT([
['rsvp1', TYPE.UINT16], // 0
['destination', SHORTSTR],
['source', SHORTSTR],
['routingKey', SHORTSTR], // ""
['nowait', TYPE.BOOL],
['arguments', TYPE.TABLE]
]) }, // {}
{ id: Cmd.ExchangeUnbindOK, ...STRUCT([]) },
{ id: Cmd.QueueDeclare, ...STRUCT([
['rsvp1', TYPE.UINT16], // 0
['queue', SHORTSTR], // ""
BITS('passive', 'durable', 'exclusive', 'autoDelete', 'nowait'),
['arguments', TYPE.TABLE]
]) }, // {}
{ id: Cmd.QueueDeclareOK, ...STRUCT([
['queue', SHORTSTR],
['messageCount', TYPE.INT32],
['consumerCount', TYPE.INT32]
]) },
{ id: Cmd.QueueBind, ...STRUCT([
['rsvp1', TYPE.UINT16], // 0
['queue', SHORTSTR], // ""
['exchange', SHORTSTR],
['routingKey', SHORTSTR], // ""
['nowait', TYPE.BOOL],
['arguments', TYPE.TABLE]
]) }, // {}
{ id: Cmd.QueueBindOK, ...STRUCT([]) },
{ id: Cmd.QueuePurge, ...STRUCT([
['rsvp1', TYPE.UINT16], // 0
['queue', SHORTSTR], // ""
['nowait', TYPE.BOOL]
]) },
{ id: Cmd.QueuePurgeOK, ...STRUCT([
['messageCount', TYPE.INT32]
]) },
{ id: Cmd.QueueDelete, ...STRUCT([
['rsvp1', TYPE.UINT16], // 0
['queue', SHORTSTR], // ""
BITS('ifUnused', 'ifEmpty', 'nowait')
]) },
{ id: Cmd.QueueDeleteOK, ...STRUCT([
['messageCount', TYPE.INT32]
]) },
{ id: Cmd.QueueUnbind, ...STRUCT([
['rsvp1', TYPE.UINT16], // 0
['queue', SHORTSTR], // ""
['exchange', SHORTSTR],
['routingKey', SHORTSTR], // ""
['arguments', TYPE.TABLE]
]) }, // {}
{ id: Cmd.QueueUnbindOK, ...STRUCT([]) },
{ id: Cmd.BasicQos, ...STRUCT([
['prefetchSize', TYPE.INT32], // 0
['prefetchCount', TYPE.UINT16], // 0
['global', TYPE.BOOL]
]) },
{ id: Cmd.BasicQosOK, ...STRUCT([]) },
{ id: Cmd.BasicConsume, ...STRUCT([
['rsvp1', TYPE.UINT16], // 0
['queue', SHORTSTR], // ""
['consumerTag', SHORTSTR], // ""
BITS('noLocal', 'noAck', 'exclusive', 'nowait'),
['arguments', TYPE.TABLE]
]) }, // {}
{ id: Cmd.BasicConsumeOK, ...STRUCT([
['consumerTag', SHORTSTR]
]) },
{ id: Cmd.BasicCancel, ...STRUCT([
['consumerTag', SHORTSTR],
['nowait', TYPE.BOOL]
]) },
{ id: Cmd.BasicCancelOK, ...STRUCT([
['consumerTag', SHORTSTR]
]) },
{ id: Cmd.BasicPublish, ...STRUCT([
['rsvp1', TYPE.UINT16], // 0
['exchange', SHORTSTR], // ""
['routingKey', SHORTSTR], // ""
BITS('mandatory', 'immediate')
]) },
{ id: Cmd.BasicReturn, ...STRUCT([
['replyCode', TYPE.UINT16],
['replyText', SHORTSTR], // ""
['exchange', SHORTSTR],
['routingKey', SHORTSTR]
]) },
{ id: Cmd.BasicDeliver, ...STRUCT([
['consumerTag', SHORTSTR],
['deliveryTag', TYPE.INT64],
['redelivered', TYPE.BOOL],
['exchange', SHORTSTR],
['routingKey', SHORTSTR]
]) },
{ id: Cmd.BasicGet, ...STRUCT([
['rsvp1', TYPE.UINT16], // 0
['queue', SHORTSTR], // ""
['noAck', TYPE.BOOL]
]) },
{ id: Cmd.BasicGetOK, ...STRUCT([
['deliveryTag', TYPE.INT64],
['redelivered', TYPE.BOOL],
['exchange', SHORTSTR],
['routingKey', SHORTSTR],
['messageCount', TYPE.INT32]
]) },
{ id: Cmd.BasicGetEmpty, ...STRUCT([
['rsvp1', SHORTSTR]
]) }, // ""
{ id: Cmd.BasicAck, ...STRUCT([
['deliveryTag', TYPE.INT64],
['multiple', TYPE.BOOL]
]) },
{ id: Cmd.BasicReject, ...STRUCT([
['deliveryTag', TYPE.INT64],
['requeue', TYPE.BOOL]
]) }, // true
{ id: Cmd.BasicRecover, ...STRUCT([
['requeue', TYPE.BOOL]
]) },
{ id: Cmd.BasicRecoverOK, ...STRUCT([]) },
{ id: Cmd.BasicNack, ...STRUCT([
['deliveryTag', TYPE.INT64], // 0
BITS('multiple', 'requeue')
]) }, // true
{ id: Cmd.ConfirmSelect, ...STRUCT([
['nowait', TYPE.BOOL]
]) },
{ id: Cmd.ConfirmSelectOK, ...STRUCT([]) },
{ id: Cmd.TxSelect, ...STRUCT([]) },
{ id: Cmd.TxSelectOK, ...STRUCT([]) },
{ id: Cmd.TxCommit, ...STRUCT([]) },
{ id: Cmd.TxCommitOK, ...STRUCT([]) },
{ id: Cmd.TxRollback, ...STRUCT([]) },
{ id: Cmd.TxRollbackOK, ...STRUCT([]) },
];
const CMD_CODEC_BY_ID = new Map(COMMAND_CODECS.map(struct => [struct.id, struct]));
const CONTENT_PROPS = [
['contentType', SHORTSTR, 0x8000],
['contentEncoding', SHORTSTR, 0x4000],
['headers', TYPE.TABLE, 0x2000],
['deliveryMode', TYPE.UINT8, 0x1000],
['priority', TYPE.UINT8, 0x0800],
['correlationId', SHORTSTR, 0x0400],
['replyTo', SHORTSTR, 0x0200],
['expiration', SHORTSTR, 0x0100],
['messageId', SHORTSTR, 0x0080],
['timestamp', TYPE.UINT64, 0x0040],
['type', SHORTSTR, 0x0020],
['userId', SHORTSTR, 0x0010],
['appId', SHORTSTR, 0x0008],
['clusterId', SHORTSTR, 0x0004],
];
/** @internal */
async function decodeFrame(read) {
const chunk = await read(7);
const frameTypeId = chunk.readUint8(0);
const channelId = chunk.readUint16BE(1);
const frameSize = chunk.readUint32BE(3);
const payloadBuffer = await read(frameSize + 1);
if (payloadBuffer[frameSize] !== FRAME_END)
throw new exception_1.AMQPConnectionError('FRAME_ERROR', 'invalid FRAME_END octet: ' + payloadBuffer[frameSize]);
if (frameTypeId === FrameType.METHOD) {
const id = payloadBuffer.readUInt32BE(0);
const def = CMD_CODEC_BY_ID.get(id);
if (def == null) {
throw new exception_1.AMQPConnectionError('CODEC', 'invalid AMQP method id: ' + id);
}
const res = def.decode(payloadBuffer, 4);
return {
type: FrameType.METHOD,
channelId: channelId,
methodId: id,
params: res[0]
};
}
else if (frameTypeId === FrameType.HEADER) {
// skip 4 bytes
const bodySize = Number(payloadBuffer.readBigUint64BE(4));
const bits = payloadBuffer.readUInt16BE(12);
let offset = 14;
const fields = {};
let key, ptype, mask, val;
for ([key, ptype, mask] of CONTENT_PROPS) {
if (bits & mask) {
[val, offset] = ptype.decode(payloadBuffer, offset);
fields[key] = val;
}
}
return {
type: FrameType.HEADER,
channelId: channelId,
bodySize: bodySize,
fields: fields
};
}
else if (frameTypeId === FrameType.BODY) {
return { type: FrameType.BODY, channelId, payload: payloadBuffer.slice(0, -1) };
}
else if (frameTypeId === FrameType.HEARTBEAT) {
return { type: FrameType.HEARTBEAT, channelId: 0 };
}
else {
throw new exception_1.AMQPConnectionError('FRAME_ERROR', 'invalid data frame');
}
}
/** @internal */
function encodeFrame(data, maxSize = Infinity) {
if (data.type === FrameType.METHOD) {
const def = CMD_CODEC_BY_ID.get(data.methodId);
if (def == null) {
throw new exception_1.AMQPConnectionError('CODEC', 'unknown AMQP method id: ' + data.methodId);
}
const paramSize = data.params == null ? 0 : def.sizeof(data.params);
const frameSize = 4 + paramSize;
if (frameSize > maxSize) {
throw new exception_1.AMQPChannelError('CODEC', `frame size of ${frameSize} bytes exceeds maximum of ${maxSize}`);
}
const buf = Buffer.allocUnsafe(12 + paramSize);
let offset = buf.writeUInt8(FrameType.METHOD, 0);
offset = buf.writeUInt16BE(data.channelId, offset);
offset = buf.writeUInt32BE(frameSize, offset);
offset = buf.writeUInt32BE(data.methodId, offset);
if (data.params != null)
offset = def.encode(buf, data.params, offset);
buf.writeUInt8(FRAME_END, offset);
return buf;
}
else if (data.type === FrameType.HEADER) {
let paramSize = 0;
let bits = 0;
let key, ptype, mask, val;
for ([key, ptype, mask] of CONTENT_PROPS) {
val = data.fields[key];
if (val != null) {
paramSize += ptype.sizeof(val);
bits |= mask;
}
}
const frameSize = 14 + paramSize;
if (frameSize > maxSize) {
throw new exception_1.AMQPChannelError('CODEC', `frame size of ${frameSize} bytes exceeds maximum of ${maxSize}`);
}
const buf = Buffer.allocUnsafe(22 + paramSize);
let offset = buf.writeUInt8(FrameType.HEADER, 0);
offset = buf.writeUInt16BE(data.channelId, offset);
offset = buf.writeUInt32BE(frameSize, offset);
offset = buf.writeUInt32BE(0x003c0000, offset);
offset = buf.writeBigUInt64BE(BigInt(data.bodySize), offset);
offset = buf.writeUInt16BE(bits, offset);
for ([key, ptype] of CONTENT_PROPS) {
val = data.fields[key];
if (val != null) {
offset = ptype.encode(buf, val, offset);
}
}
buf.writeUInt8(FRAME_END, offset); // frame end
return buf;
}
else if (data.type === FrameType.BODY) {
const buf = Buffer.allocUnsafe(8 + data.payload.byteLength);
let offset = buf.writeUInt8(FrameType.BODY, 0);
offset = buf.writeUInt16BE(data.channelId, offset);
offset = buf.writeUInt32BE(data.payload.byteLength, offset);
offset += data.payload.copy(buf, offset, 0);
buf.writeUInt8(FRAME_END, offset); // frame end
return buf;
}
else {
throw new Error('not implemented');
}
}
/** @internal */
function* genFrame(frame, frameMax) {
yield encodeFrame(frame, frameMax);
}
/** @internal Allocate DataFrame buffers on demand, right before writing to the socket */
function* genContentFrames(channelId, params, body, frameMax) {
// Immediately encode header frame to catch any codec errors before we send
// the method frame. If we send the method frame, but can't send the header
// frame, this will cause a connection-level error and reset. This way the
// error is contained to the channel-level.
const methodFrame = encodeFrame({
type: FrameType.METHOD,
channelId,
methodId: Cmd.BasicPublish,
params
});
const headerFrame = encodeFrame({
type: FrameType.HEADER,
channelId,
bodySize: body.length,
fields: params
}, frameMax);
yield methodFrame;
yield headerFrame;
const totalContentFrames = Math.ceil(body.length / frameMax);
for (let index = 0; index < totalContentFrames; ++index) {
const offset = frameMax * index;
yield encodeFrame({
type: FrameType.BODY,
channelId,
payload: body.slice(offset, offset + frameMax)
});
}
}
function readSimpleType(src, offset) {
const id = src.readUInt8(offset);
const etype = TYPE_BY_ID.get(id);
if (!etype) {
throw new Error(`unknown AMQP 0.9.1 type code: ${id}`);
}
return etype.decode(src, offset + 1);
}
function getBestType(val) {
if (typeof val === 'string') {
return TYPE.STRING;
}
else if (typeof val === 'boolean') {
return TYPE.BOOL;
}
else if (typeof val === 'number') {
// 0x4000000000000 (2^50):
// insufficient precision to distinguish floats or ints
// e.g. Math.pow(2, 50) + 0.1 === Math.pow(2, 50)
// so use INT64 instead
if (val >= 0x8000000000000000
|| val > -0x4000000000000 && val < 0x4000000000000 && Math.floor(val) !== val) {
return TYPE.DOUBLE;
}
else if (val >= -0x80 && val < 0x80) {
return TYPE.INT8;
}
else if (val >= -0x8000 && val < 0x8000) {
return TYPE.INT16;
}
else if (val >= -0x80000000 && val < 0x80000000) {
return TYPE.INT32;
}
else {
return TYPE.INT64;
}
}
else if (typeof val == 'bigint') {
return TYPE.INT64;
}
else if (Array.isArray(val)) {
return TYPE.ARRAY;
}
else if (val === null) {
return TYPE.VOID;
}
else if (typeof val === 'object') {
return TYPE.TABLE;
}
}