UNPKG

rabbitmq-client

Version:
928 lines (927 loc) 34.4 kB
"use strict"; 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; } }