UNPKG

amqplib

Version:

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

724 lines (633 loc) 21.2 kB
var FS = require('fs'); var format = require('util').format; var defs = require('./amqp-rabbitmq-0.9.1.json'); var FRAME_OVERHEAD = 8; // type + channel + size + frame-end var METHOD_OVERHEAD = FRAME_OVERHEAD + 4; // F_O + classId + methodId var PROPERTIES_OVERHEAD = FRAME_OVERHEAD + 4 + 8 + 2; // F_O + classId + weight + content size + flags var out = process.stdout; function printf() { out.write(format.apply(format, arguments), 'utf8'); } function nl() { out.write('\n'); } function println() { printf.apply(printf, arguments); nl(); } function isEmptyObject(val) { return (val != null && typeof val === 'object' && Object.keys(val).length === 0); } function stringifyValue(val) { return (isEmptyObject(val)) ? 'EMPTY_OBJECT' : JSON.stringify(val); } var constants = {}; var constant_strs = {}; for (var i = 0, len = defs.constants.length; i < len; i++) { var cdef = defs.constants[i]; constants[constantName(cdef)] = cdef.value; constant_strs[cdef.value] = cdef.name; } function constantName(def) { return def.name.replace(/-/g, '_'); } function methodName(clazz, method) { return initial(clazz.name) + method.name.split('-').map(initial).join(''); } function propertyName(dashed) { var parts = dashed.split('-'); return parts[0] + parts.slice(1).map(initial).join(''); } function initial(part) { return part.charAt(0).toUpperCase() + part.substr(1); } function argument(a) { var type = a.type || domains[a.domain]; var friendlyName = propertyName(a.name); return {type: type, name: friendlyName, default: a['default-value']}; } var domains = {}; for (var i=0, len = defs.domains.length; i < len; i++) { var dom = defs.domains[i]; domains[dom[0]] = dom[1]; } var methods = {}; var propertieses = {}; for (var i = 0, len = defs.classes.length; i < len; i++) { var clazz = defs.classes[i]; for (var j = 0, num = clazz.methods.length; j < num; j++) { var method = clazz.methods[j]; var name = methodName(clazz, method); var info = 'methodInfo' + name; methods[name] = { id: methodId(clazz, method), name: name, methodId: method.id, clazzId: clazz.id, clazz: clazz.name, args: method['arguments'].map(argument), isReply: method.answer, encoder: 'encode' + name, decoder: 'decode' + name, info: info }; } if (clazz.properties && clazz.properties.length > 0) { var name = propertiesName(clazz); var props = clazz.properties; propertieses[name] = { id: clazz.id, name: name, encoder: 'encode' + name, decoder: 'decode' + name, info: 'propertiesInfo' + name, args: props.map(argument), }; } } // OK let's get emitting println( '/** @preserve This file is generated by the script\n', '* ../bin/generate-defs.js, which is not in general included in a\n', '* distribution, but is available in the source repository e.g. at\n', '* https://github.com/squaremo/amqp.node/\n', '*/'); println("'use strict';"); nl(); println('var Buffer = require("safe-buffer").Buffer;'); nl() println('var codec = require("./codec");'); println('var ints = require("buffer-more-ints");'); println('var encodeTable = codec.encodeTable;'); println('var decodeFields = codec.decodeFields;'); nl(); println('var SCRATCH = Buffer.alloc(65536);'); println('var EMPTY_OBJECT = Object.freeze({});'); println('module.exports.constants = %s', JSON.stringify(constants)); nl(); println('module.exports.constant_strs = %s', JSON.stringify(constant_strs)); nl(); println('module.exports.FRAME_OVERHEAD = %d;', FRAME_OVERHEAD); nl(); println('module.exports.decode = function(id, buf) {'); println('switch (id) {'); for (var m in methods) { var method = methods[m]; println('case %d: return %s(buf);', method.id, method.decoder); } for (var p in propertieses) { var props = propertieses[p]; println('case %d: return %s(buf);', props.id, props.decoder); } println('default: throw new Error("Unknown class/method ID");'); println('}}'); nl(); println('module.exports.encodeMethod =', 'function(id, channel, fields) {'); println('switch (id) {'); for (var m in methods) { var method = methods[m]; println('case %d: return %s(channel, fields);', method.id, method.encoder); } println('default: throw new Error("Unknown class/method ID");'); println('}}'); nl(); println('module.exports.encodeProperties =' , 'function(id, channel, size, fields) {'); println('switch (id) {'); for (var p in propertieses) { var props = propertieses[p]; println('case %d: return %s(channel, size, fields);', props.id, props.encoder); } println('default: throw new Error("Unknown class/properties ID");'); println('}}'); nl(); println('module.exports.info = function(id) {'); println('switch(id) {'); for (var m in methods) { var method = methods[m]; println('case %d: return %s; ', method.id, method.info); } for (var p in propertieses) { var properties = propertieses[p]; println('case %d: return %s', properties.id, properties.info); } println('default: throw new Error("Unknown class/method ID");'); println('}}'); nl(); for (var m in methods) { var method = methods[m]; println('module.exports.%s = %d;', m, method.id); decoderFn(method); nl(); encoderFn(method); nl(); infoObj(method); nl(); } for (var p in propertieses) { var properties = propertieses[p]; println('module.exports.%s = %d;', p, properties.id); encodePropsFn(properties); nl(); decodePropsFn(properties); nl(); infoObj(properties); nl(); } function methodId(clazz, method) { return (clazz.id << 16) + method.id; } function propertiesName(clazz) { return initial(clazz.name) + 'Properties'; } function valTypeTest(arg) { switch (arg.type) { // everything is booleany case 'bit': return 'true' case 'octet': case 'short': case 'long': case 'longlong': case 'timestamp': return "typeof val === 'number' && !isNaN(val)"; case 'shortstr': return "typeof val === 'string' &&" + " Buffer.byteLength(val) < 256"; case 'longstr': return "Buffer.isBuffer(val)"; case 'table': return "typeof val === 'object'"; } } function typeDesc(t) { switch (t) { case 'bit': return 'booleany'; case 'octet': case 'short': case 'long': case 'longlong': case 'timestamp': return "a number (but not NaN)"; case 'shortstr': return "a string (up to 255 chars)"; case 'longstr': return "a Buffer"; case 'table': return "an object"; } } function defaultValueRepr(arg) { switch (arg.type) { case 'longstr': return format("Buffer.from(%s)", JSON.stringify(arg.default)); default: // assumes no tables as defaults return JSON.stringify(arg.default); } } // Emit code to assign the arg value to `val`. function assignArg(a) { println("val = fields['%s'];", a.name); } function assignOrDefault(a) { println("val = fields['%s'];", a.name); println("if (val === undefined) val = %s;", defaultValueRepr(a)); } // Emit code for assigning an argument value to `val`, checking that // it exists (if it does not have a default) and is the correct // type. function checkAssignArg(a) { assignArg(a); println('if (val === undefined) {'); if (a.default !== undefined) { println('val = %s;', defaultValueRepr(a)); } else { println('throw new Error("Missing value for mandatory field \'%s\'");', a.name); } println('}'); // undefined test println('else if (!(%s)) {', valTypeTest(a)); println('throw new TypeError('); println('"Field \'%s\' is the wrong type; must be %s");', a.name, typeDesc(a.type)); println('}'); // type test } // Emit code for encoding `val` as a table and assign to a fresh // variable (based on the arg name). I use a scratch buffer to compose // the encoded table, otherwise I'd have to do a size calculation pass // first. I can get away with this only because 1. the encoding // procedures are not re-entrant; and, 2. I copy the result into // another buffer before returning. `scratchOffset`, `val`, `len` are // expected to have been declared. function assignTable(a) { var varname = tableVar(a); println( "len = encodeTable(SCRATCH, val, scratchOffset);"); println('var %s = SCRATCH.slice(scratchOffset, scratchOffset + len);', varname); println('scratchOffset += len;'); } function tableVar(a) { return a.name + '_encoded'; } function stringLenVar(a) { return a.name + '_len'; } function assignStringLen(a) { var v = stringLenVar(a); // Assumes the value or default is in val println("var %s = Buffer.byteLength(val, 'utf8');", v); } function encoderFn(method) { var args = method['args']; println('function %s(channel, fields) {', method.encoder); println('var offset = 0, val = null, bits = 0, varyingSize = 0;'); println('var len, scratchOffset = 0;'); // Encoding is split into two parts. Some fields have a fixed size // (e.g., integers of a specific width), while some have a size that // depends on the datum (e.g., strings). Each field will therefore // either 1. contribute to the fixed size; or 2. emit code to // calculate the size (and possibly the encoded value, in the case // of tables). var fixedSize = METHOD_OVERHEAD; var bitsInARow = 0; for (var i=0, len = args.length; i < len; i++) { var arg = args[i]; if (arg.type != 'bit') bitsInARow = 0; switch (arg.type) { // varying size case 'shortstr': checkAssignArg(arg); assignStringLen(arg); println("varyingSize += %s;", stringLenVar(arg)); fixedSize += 1; break; case 'longstr': checkAssignArg(arg); println("varyingSize += val.length;"); fixedSize += 4; break; case 'table': // For a table we have to encode the table before we can see its // length. checkAssignArg(arg); assignTable(arg); println('varyingSize += %s.length;', tableVar(arg)); break; // fixed size case 'octet': fixedSize += 1; break; case 'short': fixedSize += 2; break; case 'long': fixedSize += 4; break; case 'longlong': //fall through case 'timestamp': fixedSize += 8; break; case 'bit': bitsInARow ++; // open a fresh pack o' bits if (bitsInARow === 1) fixedSize += 1; // just used a pack; reset else if (bitsInARow === 8) bitsInARow = 0; break; } } println('var buffer = Buffer.alloc(%d + varyingSize);', fixedSize); println('buffer[0] = %d;', constants.FRAME_METHOD); println('buffer.writeUInt16BE(channel, 1);'); // skip size for now, we'll write it in when we know println('buffer.writeUInt32BE(%d, 7);', method.id); println('offset = 11;'); bitsInARow = 0; for (var i = 0, len = args.length; i < len; i++) { var a = args[i]; // Flush any collected bits before doing a new field if (a.type != 'bit' && bitsInARow > 0) { bitsInARow = 0; println('buffer[offset] = bits; offset++; bits = 0;'); } switch (a.type) { case 'octet': checkAssignArg(a); println('buffer.writeUInt8(val, offset); offset++;'); break; case 'short': checkAssignArg(a); println('buffer.writeUInt16BE(val, offset); offset += 2;'); break; case 'long': checkAssignArg(a); println('buffer.writeUInt32BE(val, offset); offset += 4;'); break; case 'longlong': case 'timestamp': checkAssignArg(a); println('ints.writeUInt64BE(buffer, val, offset); offset += 8;'); break; case 'bit': checkAssignArg(a); println('if (val) bits += %d;', 1 << bitsInARow); if (bitsInARow === 7) { // I don't think this ever happens, but whatever println('buffer[offset] = bits; offset++; bits = 0;'); bitsInARow = 0; } else bitsInARow++; break; case 'shortstr': assignOrDefault(a); println('buffer[offset] = %s; offset++;', stringLenVar(a)); println('buffer.write(val, offset, "utf8"); offset += %s;', stringLenVar(a)); break; case 'longstr': assignOrDefault(a); println('len = val.length;'); println('buffer.writeUInt32BE(len, offset); offset += 4;'); println('val.copy(buffer, offset); offset += len;'); break; case 'table': println('offset += %s.copy(buffer, offset);', tableVar(a)); break; default: throw new Error("Unexpected argument type: " + a.type); } } // Flush any collected bits at the end if (bitsInARow > 0) { println('buffer[offset] = bits; offset++;'); } println('buffer[offset] = %d;', constants.FRAME_END); // size does not include the frame header or frame end byte println('buffer.writeUInt32BE(offset - 7, 3);'); println('return buffer;'); println('}'); } function fieldsDecl(args) { println('var fields = {'); for (var i=0, num=args.length; i < num; i++) { println('%s: undefined,', args[i].name); } println('};'); } function decoderFn(method) { var args = method.args; println('function %s(buffer) {', method.decoder); println('var offset = 0, val, len;'); fieldsDecl(args); var bitsInARow = 0; for (var i=0, num=args.length; i < num; i++) { var a = args[i]; var field = "fields['" + a.name + "']"; // Flush any collected bits before doing a new field if (a.type != 'bit' && bitsInARow > 0) { bitsInARow = 0; println('offset++;'); } switch (a.type) { case 'octet': println('val = buffer[offset]; offset++;'); break; case 'short': println('val = buffer.readUInt16BE(offset); offset += 2;'); break; case 'long': println('val = buffer.readUInt32BE(offset); offset += 4;'); break; case 'longlong': case 'timestamp': println('val = ints.readUInt64BE(buffer, offset); offset += 8;'); break; case 'bit': var bit = 1 << bitsInARow; println('val = !!(buffer[offset] & %d);', bit); if (bitsInARow === 7) { println('offset++;'); bitsInARow = 0; } else bitsInARow++; break; case 'longstr': println('len = buffer.readUInt32BE(offset); offset += 4;'); println('val = buffer.slice(offset, offset + len);'); println('offset += len;'); break; case 'shortstr': println('len = buffer.readUInt8(offset); offset++;'); println('val = buffer.toString("utf8", offset, offset + len);'); println('offset += len;'); break; case 'table': println('len = buffer.readUInt32BE(offset); offset += 4;'); println('val = decodeFields(buffer.slice(offset, offset + len));'); println('offset += len;'); break; default: throw new TypeError("Unexpected type in argument list: " + a.type); } println('%s = val;', field); } println('return fields;'); println('}'); } function infoObj(thing) { var info = JSON.stringify({id: thing.id, classId: thing.clazzId, methodId: thing.methodId, name: thing.name, args: thing.args}); println('var %s = module.exports.%s = %s;', thing.info, thing.info, info); } // The flags are laid out in groups of fifteen in a short (high to // low bits), with a continuation bit (at 0) and another group // following if there's more than fifteen. Presence and absence // are conflated with true and false, for bit fields (i.e., if the // flag for the field is set, it's true, otherwise false). // // However, none of that is actually used in AMQP 0-9-1. The only // instance of properties -- basic properties -- has 14 fields, none // of them bits. function flagAt(index) { return 1 << (15 - index); } function encodePropsFn(props) { println('function %s(channel, size, fields) {', props.encoder); println('var offset = 0, flags = 0, val, len;'); println('var scratchOffset = 0, varyingSize = 0;'); var fixedSize = PROPERTIES_OVERHEAD; var args = props.args; function incVarying(by) { println("varyingSize += %d;", by); } for (var i=0, num=args.length; i < num; i++) { var p = args[i]; assignArg(p); println("if (val != undefined) {"); println("if (%s) {", valTypeTest(p)); switch (p.type) { case 'shortstr': assignStringLen(p); incVarying(1); println('varyingSize += %s;', stringLenVar(p)); break; case 'longstr': incVarying(4); println('varyingSize += val.length;'); break; case 'table': assignTable(p); println('varyingSize += %s.length;', tableVar(p)); break; case 'octet': incVarying(1); break; case 'short': incVarying(2); break; case 'long': incVarying(4); break; case 'longlong': // fall through case 'timestamp': incVarying(8); break; // no case for bit, as they are accounted for in the flags } println('} else {'); println('throw new TypeError('); println('"Field \'%s\' is the wrong type; must be %s");', p.name, typeDesc(p.type)); println('}'); println('}'); } println('var buffer = Buffer.alloc(%d + varyingSize);', fixedSize); println('buffer[0] = %d', constants.FRAME_HEADER); println('buffer.writeUInt16BE(channel, 1);'); // content class ID and 'weight' (== 0) println('buffer.writeUInt32BE(%d, 7);', props.id << 16); // skip frame size for now, we'll write it in when we know. // body size println('ints.writeUInt64BE(buffer, size, 11);'); println('flags = 0;'); // we'll write the flags later too println('offset = 21;'); for (var i=0, num=args.length; i < num; i++) { var p = args[i]; var flag = flagAt(i); assignArg(p); println("if (val != undefined) {"); if (p.type === 'bit') { // which none of them are .. println('if (val) flags += %d;', flag); } else { println('flags += %d;', flag); // %%% FIXME only slightly different to the method args encoding switch (p.type) { case 'octet': println('buffer.writeUInt8(val, offset); offset++;'); break; case 'short': println('buffer.writeUInt16BE(val, offset); offset += 2;'); break; case 'long': println('buffer.writeUInt32BE(val, offset); offset += 4;'); break; case 'longlong': case 'timestamp': println('ints.writeUInt64BE(buffer, val, offset);'); println('offset += 8;'); break; case 'shortstr': var v = stringLenVar(p); println('buffer[offset] = %s; offset++;', v); println("buffer.write(val, offset, 'utf8');"); println("offset += %s;", v); break; case 'longstr': println('buffer.writeUInt32BE(val.length, offset);'); println('offset += 4;'); println('offset += val.copy(buffer, offset);'); break; case 'table': println('offset += %s.copy(buffer, offset);', tableVar(p)); break; default: throw new Error("Unexpected argument type: " + p.type); } } println('}'); // != undefined } println('buffer[offset] = %d;', constants.FRAME_END); // size does not include the frame header or frame end byte println('buffer.writeUInt32BE(offset - 7, 3);'); println('buffer.writeUInt16BE(flags, 19);'); println('return buffer.slice(0, offset + 1);'); println('}'); } function decodePropsFn(props) { var args = props.args; println('function %s(buffer) {', props.decoder); println('var flags, offset = 2, val, len;'); println('flags = buffer.readUInt16BE(0);'); println('if (flags === 0) return {};'); fieldsDecl(args); for (var i=0, num=args.length; i < num; i++) { var p = argument(args[i]); var field = "fields['" + p.name + "']"; println('if (flags & %d) {', flagAt(i)); if (p.type === 'bit') { println('%d = true;', field); } else { switch (p.type) { case 'octet': println('val = buffer[offset]; offset++;'); break; case 'short': println('val = buffer.readUInt16BE(offset); offset += 2;'); break; case 'long': println('val = buffer.readUInt32BE(offset); offset += 4;'); break; case 'longlong': case 'timestamp': println('val = ints.readUInt64BE(buffer, offset); offset += 8;'); break; case 'longstr': println('len = buffer.readUInt32BE(offset); offset += 4;'); println('val = buffer.slice(offset, offset + len);'); println('offset += len;'); break; case 'shortstr': println('len = buffer.readUInt8(offset); offset++;'); println('val = buffer.toString("utf8", offset, offset + len);'); println('offset += len;'); break; case 'table': println('len = buffer.readUInt32BE(offset); offset += 4;'); println('val = decodeFields(buffer.slice(offset, offset + len));'); println('offset += len;'); break; default: throw new TypeError("Unexpected type in argument list: " + p.type); } println('%s = val;', field); } println('}'); } println('return fields;'); println('}'); }