:mod:`amqp.protocol` -- AMQP Protocol Helpers ============================================== .. module:: amqp.protocol :synopsis: Helper functions and classes for building various versions of the AMQP protocol. Properties ---------- .. attribute:: default_version the protocol version used if no version was specified when a connection is established. When a version of the protocol is loaded by the JavaScript interpreter it registers itself with the amqp.protocol module. The most recent version registered is set as the default version. .. attribute:: protocols a dictionary of registered protocol versions Top Level Functions ------------------- .. function:: deep_copy_defaults(object) Copy's JavaScript objects of unlimited depth This function is used internally to properly copy defaults into options in MetaClasses. While pointers and references are not a direct JavaScript construct, under the hood JavaScript uses these concepts extensively to avoid performance issues. Simply assigning a JavaScript object to another variable does not copy it. We must walk the object's members and clone each sub object to create an exact copy of the original. :param object: a javascript dictionary object :rtype: a deep copy of the dictionary object .. function:: MetaClass(cls_members) Convenience construct for creating full featured classes This is used for classes which need a constructor and default member values. Any object passed to cls_members becomes the resulting class' prototype. .. code-block:: javascript var a = MetaClass({ _init: function(){alert('_init called')} }); is equivalent to: .. code-block:: javascript var a = function(){}; a.prototype._init = function(){alert('_init called')}; MetaClass classes have a few special members: * _init - This is the object constructor method. It is run when the resulting class is instantiated. * _new - This is the class constructor method. It is run when the class is first created. * _defaults - This should be a dictionary object (e.g. {}). It lists the options the _init constructor can accept and their default values if they are not passed into the constructor A MetaClass class looks something like this: .. code-block:: javascript var person = MetaClass({ _defaults: { name: 'Unknown', id: 1 }, _init: function(options){ this._options = options; alert(options.name + ' has id ' + options.id); }, talk: function(words) { alert(this._options.name + ' says ' + words); } }); You would use this object as such: .. code-block:: javascript var john = new person({name: 'John'}); // alert | John has id 1 | john.talk('hello!!!'); // alert | John says hello!!! | :param cls_members: object of class methods and properties :rtype: JavaScript class prototype Misc. Module Methods -------------------- .. function:: register(version, module) When loaded a versioned protocol script will use this method to register itself This must be called at the end of every protocol module. For instance the version 0.10 amqp module does something similar to this: .. code-block:: javascript (function(){ amqp.protocol_0_10 = { . . . }; amqp.protocol.register('0.10', amqp.protocol_0_10); })(); :param version: the version of the module being registered :param module: a reference to the module being registered .. function:: get_connection(version, options) get an instantiated connection object from a versioned protocol library This is the entry point for creating connections to an AMQP server. Options are version dependent but usually can contain these members: * host - the host name to connect to * port - the port number to connect to * username - if authentication is required, use this to set the username * password - if authentication is required, use this to set the password * send_hook - used for debugging, if set to a function this function will be called with the raw byte stream and parsed frame data as parameters every time data is sent over this connection * recive_hook - used for debugging, if set to a function this function will be called with the raw byte stream and parsed frame data as parameters every time data is recived over this connection If a version is not specified in the parameters (e.g. version==null || version==undefined) the default version is assumed. .. code-block:: javascript amqp_conn = amqp.protocol.get_connection(null, {host:'localhost', port:7000, send_hook: function(data, frame) { append_msg('SENT', frame, 'message_list'); }, recive_hook: function(data, frame) { append_msg('RECV', frame, 'message_list'); } }); amqp_conn.start(); :Note: Most higher level bindings will wrap this in their own higher level connection object. This is simply a convenience function for getting a raw versioned AMQP connection. A higher level binding may want to negotiate versions with the server. :param version: The version you wish to use or null for the default version :param options: The options to use when creating teh connection Decoder and Encoder Module Methods ---------------------------------- Decoders and encoders are the heart of the AMQP binding stack. AMQP messages are essentially streams of tightly packed encoded bytes which we marshal into data structures and then pass off to handlers which act on the data. The amqp.protocol module contains a number of data marshalers which are used by the generated protocol code to do all the heavy lifting. We do this because data marshaling of base types, and even some complex types shouldn't change from version to version of the protocol. If we find a bug in the marshaling code we can fix it in one place instead of a dozen. Most of this code is also generic enough to use for any type of binary encoding inside of JavaScript, not just the AMQP protocol. Helper Methods ~~~~~~~~~~~~~~ .. function:: int_to_bytestr(u, size) Takes a numerical value and encodes it as a bytestring in network byte order The name of this method is a bit off since it will take any numerical value and treat it as a unsigned int when encoding it into a byte string. It is up to the decoder used to reinterpret the correct type from the byte string representation. This method is used by the other encoders to encode any numerical value they may have. It works by masking off 8 bits at a time, converting that to a character and then right shifting by 8 bits. :param u: the integer to encode :param size: the number of bytes to encode into :rtype: a bytestring .. function:: decode_typecode(assembly) Reads a uint of size 1 from the assembly stream and returns the value When a field's type in the data stream is not implicitly known, the type code will be encoded as a one byte uint. This method reads the byte and decodes it to a uint. :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are reading from :rtype: an unsigned integer representing the typecode we are reading from the bytestream .. function:: encode_typecode(assembly, typecode) Writes the typecode to the assembly stream Takes a typecode and encodes it as a 1 byte uint. If the typecode is ''''null'''', no value is encoded. :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are writing to :param typecode: the unsigned integer we are writing to the stream Static Type Decoding Methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Static type decoders are used directly by the generated code to handle any decoding duties for types with a static size. As such every static type decoder has a single signature: .. code-block:: javascript decode_ = function(assembly, size); This makes it easy to add decoders if necessary. All decoders read in size bytes from the assembly and returns the processed value to the caller. .. function:: decode_bin(assembly, size) This function simply ready size bytes from the assembly :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are reading from :param size: the number of bytes to read from the stream :rtype: byte string .. function:: decode_int(assembly, size) Read in size bytes, convert to a uint by bit shifting and adding then check if the most significant bit is set. If it is take the twos compliment to convert to a negitive integer. :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are reading from :param size: the number of bytes to read from the stream :rtype: signed integer .. function:: decode_uint(assembly, size) Read in size bytes and convert to a uint by bit shifting and adding :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are reading from :param size: the number of bytes to read from the stream :rtype: unsigned integer .. function:: decode_void(assembly, size) Special case where we encounter a void typecode but don't read any further bytes. Always return null. :param assembly: unused :param size: size must be 0 :rtype: null .. function:: decode_bool(assembly, size) Reads in a uint of the given size and if it is greater than 0 return true else return false. :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are reading from :param size: the number of bytes to read from the stream :rtype: boolean .. function:: decode_dec(assembly, size) Right now we just decode this as a float. We read in the first byte to find out where the decimal place goes amd then decode the rest of the bytes and devide that number by the decimal place * 10. While this gives us the intended value it does not preserve the fixed decimal and can not be directly reencoded. Since JavaScript has no way of creating a new class that works like primitive types (e.g. overloaded operators) it is hard to create our own decimal type. In the end it may just need to be special cased and users will have to be aware of the issues. :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are reading from :param size: the number of bytes to read from the stream :rtype: float - this is wrong as decimal type has a fixed decimal place .. function:: decode_datetime(assembly, size) Reads in a uint of the specified size and calls setTime on a created Date object. :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are reading from :param size: the number of bytes to read from the stream :rtype: Date object .. function:: decode_float(assembly, size) FIXME: This is currently not implemented correctly :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are reading from :param size: the number of bytes to read from the stream :rtype: n/a .. function:: decode_bit(assembly, size) This is a special case decoder in which we always return 1. This is because this decoder is only called if the packing flag is set. Bits are read from the packing flags and not from the payload. Since the library only calls a decode function if the packing flag is set we will only ever be called if the bit is set to 1. Doing it this way avoids complicating the code by special casing when reading the packing flags. :param assembly: not used :param size: should be 0 :rtype: 1 Static Type Encoding Methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Static type encoders are used directly by the generated code to handle any encoding duties for types with a static size. As such every static type decoder has a single signature: .. code-block:: javascript encode_ = function(assembly, typecode, size, value); This makes it easy to add encoders if necessary. All encoders write out a bytestream representation of the value in the give size. If typecode is not null we also prepend the typecode to the encoded value. .. function:: encode_bin(assembly, typecode, size, value) Takes the value and copies it verbatum into the assembly byte stream :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are writting to :param typecode: if not null the typecode gets prepended to the encoded value :param size: the number of bytes to write to the stream :param value: The byte string to encode .. function:: encode_int(assembly, typecode, size, value) Encodes each byte by shifting and masking the last eight bytes and converting the value to an ascii character. We assume the JavaScript engine encodes in twos complement. :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are writting to :param typecode: if not null the typecode gets prepended to the encoded value :param size: the number of bytes to write to the stream :param value: The integer to encode .. function:: encode_uint(assembly, typecode, size, value) Encodes each byte by shifting and masking the last eight bytes and converting the value to an ascii character. :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are writting to :param typecode: if not null the typecode gets prepended to the encoded value :param size: the number of bytes to write to the stream :param value: The unsigned integer to encode .. function:: encode_void(assembly, typecode, size, value) Writes out the typecode and nothing else. :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are writting to :param typecode: can not be null :param size: must be 0 :param value: The byte string to encode .. function:: encode_bool(assembly, typecode, size, value If true we encode a uint of 1 else a uint of 0. :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are writting to :param typecode: if not null the typecode gets prepended to the encoded value :param size: the number of bytes to write to the stream :param value: must be true or false .. function:: encode_dec(assembly, typecode, size, value Not implemented yet :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are writting to :param typecode: if not null the typecode gets prepended to the encoded value :param size: the number of bytes to write to the stream :param value: The decimal number to encode .. function:: encode_datetime(assembly,typecode, size, value Get the number of seconds from the epoc using value.getTime() and encode it as a uint. :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are writting to :param typecode: if not null the typecode gets prepended to the encoded value :param size: the number of bytes to write to the stream :param value: The Date object to encode .. function:: encode_float(assembly, typecode, size, value Not implemented! :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are writting to :param typecode: if not null the typecode gets prepended to the encoded value :param size: the number of bytes to write to the stream :param value: The float to encode .. function:: encode_bit(assembly, typecode, size, value) A noop since this is special cased inside the versioned protocol code. :param assembly: not used :param typecode: not used :param size: not used :param value: not used Variable Type Decoding Methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Variable type decoders are used directly by the generated code to handle any decoding duties for types with a dynamic size (e.g. their size is encoded along with the value). As such every variable type decoder has a single signature: .. code-block:: javascript decode_ = function(assembly, varsize); This makes it easy to add decoders if necessary. All decoders read in varsize bytes from the assembly to first get the size of the variable data. The decoder then reads in size bytes and returns the processed value to the caller. .. function:: decode_map(assembly, varsize) To decode a map we read in varsize bytes to get the size of the map data We then read in 4 bytes to get the number of elements in the map. For each element we read in a string as the key, a typecode for the value and the value itself. :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are reading from :param varsize: the number of bytes to read in order to get the actual size of the data :rtype: object .. function:: decode_array(assembly, varsize) To decode an array we read in varsize bytes to get the size of the array data We then read in the typecode to get the type for the elements in the array. We then read in 4 bytes to get the element count and then we read in each element based on the typecode. :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are reading from :param varsize: the number of bytes to read in order to get the actual size of the data :rtype: array .. function:: decode_str(assembly, varsize) To decode a string we read in varsize bytes to get the size of the string data. We then read in size bytes and return the results. :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are reading from :param varsize: the number of bytes to read in order to get the actual size of the data :rtype: string .. function:: decode_seq_set(assembly, varsize) To decode a sequence set we read in varsize bytes to get the size of the set. Each byte in the set gets decoded as a unsigned integer. The lower bound of the sequence is taken from the first 4 significant bits of the decoded value. The upper bount is taken from the last 4 significant bits. These are placed in a tuple as [lower, upper] and then pushed into a list. :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are reading from :param varsize: the number of bytes to read in order to get the actual size of the data :rtype: a list of two element tupples [[upper, lower],...] Variable Type Encoding Methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Variable type encoders are used directly by the generated code to handle any encoding duties for types with a dynamic size (e.g. their size is encoded along with the value). As such every variable type decoder has a single signature: .. code-block:: javascript decode_ = function(assembly, typecode, size, value); This makes it easy to add encoders if necessary. All encoders write out their data to a seperate assembly to first get the size of the variable data. They then write out the size and the encoded data to passed in assembly. .. function:: encode_map(assembly, typecode, size, value) To encode a map we first loop over all the key/value pairs in an object. For each element we encode the key as a string and the value along with its type code. We then take the size of the resulting encoding and write the whole thing out to the main assembly. :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are writting to :param typecode: if not null the typecode gets prepended to the encoded value :param size: the number of bytes to encode the variable size of the data into :param value: a JavaScript object .. function:: encode_array(assembly, typecode, size, value) Not implemented yet .. function:: encode_str(assembly, typecode, size, value) Get the length of the string. Encoded the length and then the string. :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are writting to :param typecode: if not null the typecode gets prepended to the encoded value :param size: the number of bytes to encode the variable size of the data into :param value: a string .. function:: encode_seq_set(assembly, typecode, size, value) Not implemented yet Higher Level Encoders and Decoders ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Various convinience marshallers. .. function:: decode_header(assembly) Decodes the AMQP header and gets the class, instance and version infromation from the stream. This can be used to determine which plugin to load. :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are reading from :rtype: {class: int, instance: int, version_major: int, version_minor: int} .. function:: encode_header(assembly) Gets the header from the current call context and writes it out. :param assembly: :class:`amqp.protocol.Assembly` object which contains the bytestream we are writting to Classes ------- .. class:: Assembly({data:''}) This class makes it easy to read and write sequential bits and bytes while avoiding unnecessary copying. When data comes over a socket it is placed in an :class:`amqp.protocol.Assembly` object and passed to the parsers. :param data: Use this to set the initial stream data .. method:: read_bit(count) Reads ``count`` bits of the current byte in the stream. This is used to read fields smaller than a byte. Fields can not cross the byte boundry, so trying to read more bits than are in the current byte will cause an error to be thrown. :param count: The number of bits to be read .. method:: read_byte(count) Reads ``count`` bytes from the byte stream and increments the ``seek`` position. The stream must be byte aligned (e.g. if read_bit was previouly called, it must have finished reading the whole byte) :param count: The number of bytes to be read .. method:: write(bytes) Append ``bytes`` to the data stream. :param bytes: the data to append .. method:: prepend(bytes) Prepends ``bytes`` to the data stream :param bytes: the data to prepend .. method:: get_data() Returns the raw data stram. :rtype: string .. method:: get_size(from_current_pos) Returns the size of the data stream. If from_current_pos is set to ``true`` return the size of the data stream from the current ``seek`` position. :param from_current_pos: specifies if you want the total size of the stream or the size from the current ``seek`` position. :rtype: integer .. method:: seek(pos) Sets the current byte position. :param pos: the position in the byte stream to set the read cursor to .. method:: eof() Returns ``true`` if we are currently at the end of the stream. :rtype: boolean .. method:: mark_start() Used for debugging this method allows us to split a stream up without having to actually split the stream which would involve large copying of data. When called the current ``seek`` position is stored. .. method:: get_start_pos() Used for debugging this returns the position stored when `mark_start` was last called. :rtype: integer .. method:: get_pos() Returns the current seek position. :rtype: integer .. class:: Message({parsed_data:{}, template: null }) This is a wrapper class used by the generated bindings when encoding an AMQP message from a message template. *NOTE:* Message is currently only used for encoding though it could be used for decoding messages also with a few tweaks. :param parsed_data: Use this to set the values to encode to an Assembly :param template: use this to specifiy which generated message template will be used for encoding .. method:: get(key) Convinience method for grabbing data from the ``parsed_data`` map. :param key: the id of the value we wish to grab :rtype: the value inside of the ``parsed_data`` map pointed to by ``key`` .. method:: set(key, value) Convinience method for setting data inside the ``parsed_data`` map. :param key: the id of the value we wish to set :param value: the data to place into the map .. method:: get_size() Get's the size of the internal :class:`amqp.protocol.Assembly` from the current seek position. :rtype: integer .. method:: get_data(size) Reads ``size`` bytes from the internal assembly. :param size: The number of bytes to read :rtype: string of bytes .. method:: get_type() Returns the type code of message we are encoding :rtype: integer .. method:: get_name() Returns the class name of the message we are encoding :rtype: string .. method:: get_message_name() Returns the message name of the message we are encoding :rtype: string .. method:: encode() Using the template and parsed_data, encodes the message into the internal :class:`amqp.protocol.Assembly` .. method:: decode() Not used anywhere but this will decode the contents of the internal :class:`amqp.protocol.Assembly`.