UNPKG

amqplib

Version:

An AMQP 0-9-1 (e.g., RabbitMQ) library and client.

176 lines (145 loc) 4.32 kB
const ints = require('buffer-more-ints'); const defs = require('./defs'); const constants = defs.constants; const decode = defs.decode; module.exports.PROTOCOL_HEADER = `AMQP${String.fromCharCode(0, 0, 9, 1)}`; /* Frame format: 0 1 3 7 size+7 size+8 +------+---------+-------------+ +------------+ +-----------+ | type | channel | size | | payload | | frame-end | +------+---------+-------------+ +------------+ +-----------+ octet short long size octets octet In general I want to know those first three things straight away, so I can discard frames early. */ // framing constants const FRAME_METHOD = constants.FRAME_METHOD; const FRAME_HEARTBEAT = constants.FRAME_HEARTBEAT; const FRAME_HEADER = constants.FRAME_HEADER; const FRAME_BODY = constants.FRAME_BODY; const FRAME_END = constants.FRAME_END; // expected byte sizes for frame parts const TYPE_BYTES = 1; const CHANNEL_BYTES = 2; const SIZE_BYTES = 4; const FRAME_HEADER_BYTES = TYPE_BYTES + CHANNEL_BYTES + SIZE_BYTES; const FRAME_END_BYTES = 1; /** * @typedef {{ * type: number, * channel: number, * size: number, * payload: Buffer, * rest: Buffer * }} FrameStructure */ /** * This is a polyfill which will read a big int 64 bit as a number. * @arg { Buffer } buffer * @arg { number } offset * @returns { number } */ function readInt64BE(buffer, offset) { /** * We try to use native implementation if available here because * buffer-more-ints does not */ if (typeof Buffer.prototype.readBigInt64BE === 'function') { return Number(buffer.readBigInt64BE(offset)); } return ints.readInt64BE(buffer, offset); } // %%% TESTME possibly better to cons the first bit and write the // second directly, in the absence of IO lists /** * Make a frame header * @arg { number } channel * @arg { Buffer } payload */ module.exports.makeBodyFrame = (channel, payload) => { const frameSize = FRAME_HEADER_BYTES + payload.length + FRAME_END_BYTES; const frame = Buffer.alloc(frameSize); let offset = 0; offset = frame.writeUInt8(FRAME_BODY, offset); offset = frame.writeUInt16BE(channel, offset); offset = frame.writeInt32BE(payload.length, offset); payload.copy(frame, offset); offset += payload.length; frame.writeUInt8(FRAME_END, offset); return frame; }; /** * Parse an AMQP frame * @arg { Buffer } bin * @arg { number } max * @returns { FrameStructure | boolean } */ function parseFrame(bin) { if (bin.length < FRAME_HEADER_BYTES) { return false; } const type = bin.readUInt8(0); const channel = bin.readUInt16BE(1); const size = bin.readUInt32BE(3); const totalSize = FRAME_HEADER_BYTES + size + FRAME_END_BYTES; if (bin.length < totalSize) { return false; } const frameEnd = bin.readUInt8(FRAME_HEADER_BYTES + size); if (frameEnd !== FRAME_END) { throw new Error('Invalid frame'); } return { type, channel, size, payload: bin.subarray(FRAME_HEADER_BYTES, FRAME_HEADER_BYTES + size), rest: bin.subarray(totalSize), }; } module.exports.parseFrame = parseFrame; const HEARTBEAT = { channel: 0 }; /** * Decode AMQP frame into JS object * @param { FrameStructure } frame * @returns */ module.exports.decodeFrame = (frame) => { const payload = frame.payload; const channel = frame.channel; switch (frame.type) { case FRAME_METHOD: { const id = payload.readUInt32BE(0); const args = payload.subarray(4); const fields = decode(id, args); return { id, channel, fields }; } case FRAME_HEADER: { const id = payload.readUInt16BE(0); // const weight = payload.readUInt16BE(2) const size = readInt64BE(payload, 4); const flagsAndfields = payload.subarray(12); const fields = decode(id, flagsAndfields); return { id, channel, size, fields }; } case FRAME_BODY: return { channel, content: payload }; case FRAME_HEARTBEAT: return HEARTBEAT; default: throw new Error(`Unknown frame type ${frame.type}`); } }; // encoded heartbeat module.exports.HEARTBEAT_BUF = Buffer.from([ constants.FRAME_HEARTBEAT, 0, 0, 0, 0, // size = 0 0, 0, // channel = 0 constants.FRAME_END, ]); module.exports.HEARTBEAT = HEARTBEAT;