    // @tag enterprise
    /**
     * @class Ext.data.amf.Packet
     * This class represents an Action Message Format (AMF) Packet.  It contains all
     * the logic required to decode an AMF Packet from a byte array.
     * To decode a Packet, first construct a Packet:
     *
     *     var packet = Ext.create('Ext.data.amf.Packet');
     *
     * Then use the decode method to decode an AMF byte array:
     *
     *     packet.decode(bytes);
     *
     * where "bytes" is a Uint8Array or an array of numbers representing the binary
     * AMF data.
     *
     * To access the decoded data use the #version, #headers, and #messages properties:
     *
     *     console.log(packet.version, packet.headers, packet.messages);
     *
     * For more information on working with AMF data please refer to the
     * [AMF Guide](#/guide/amf).
     */
    Ext.define('Ext.data.amf.Packet', function() {
        var twoPowN52 = Math.pow(2, -52),
            twoPow8 = Math.pow(2, 8),
            pos = 0,
            bytes, strings, objects, traits;

        return {
            /**
             * @property {Array} headers
             * @readonly
             * The decoded headers. Each header has the following properties:
             *
             * - `name`: String
             * The header name. Typically identifies a remote operation or method to
             * be invoked by this context header.
             * - `mustUnderstand`: Boolean
             * If `true` this flag instructs the endpoint to abort and generate an
             * error if the header is not understood.
             * - `byteLength`: Number
             * If the byte-length of a header is known it can be specified to optimize
             * memory allocation at the remote endpoint.
             * - `value`: Mixed
             * The header value
             */

            /**
             * @property {Array} messages
             * @readonly
             * The decoded messages. Each message has the following properties:
             *
             * - `targetURI`: String
             * Describes which operation, function, or method is to be remotely
             * invoked.
             * - `responseURI`: String
             * A unique operation name
             * - `byteLength`: Number
             * Optional byte-length of the message body
             * - `body`: Mixed
             * The message body
             */

            /**
             * @property {Number} version
             * @readonly
             * The AMF version number (0 or 3)
             */

            /**
             * Mapping of AMF data types to the names of the methods responsible for
             * reading them.
             * @private
             */
            typeMap: {
                // AMF0 mapping
                0: {
                    0: 'readDouble',
                    1: 'readBoolean',
                    2: 'readAmf0String',
                    3: 'readAmf0Object',
                    5: 'readNull',
                    6: 'readUndefined',
                    7: 'readReference',
                    8: 'readEcmaArray',
                    10: 'readStrictArray',
                    11: 'readAmf0Date',
                    12: 'readLongString',
                    13: 'readUnsupported',
                    15: 'readAmf0Xml',
                    16: 'readTypedObject'
                },
                // AMF3 mapping
                3: {
                    0: 'readUndefined',
                    1: 'readNull',
                    2: 'readFalse',
                    3: 'readTrue',
                    4: 'readUInt29',
                    5: 'readDouble',
                    6: 'readAmf3String',
                    7: 'readAmf3Xml',
                    8: 'readAmf3Date',
                    9: 'readAmf3Array',
                    10: 'readAmf3Object',
                    11: 'readAmf3Xml',
                    12: 'readByteArray'
                }
            },

            /**
             * Decodes an AMF btye array and sets the decoded data as the
             * Packet's #version, #headers, and #messages properties
             * @param {Array} byteArray A byte array containing the encoded AMF data.
             * @return {Ext.data.amf.Packet} this AMF Packet
             */
            decode: function(byteArray) {
                var me = this,
                    headers = me.headers = [],
                    messages = me.messages = [],
                    headerCount, messageCount;

                pos = 0;

                bytes = me.bytes = byteArray;

                // The strings array holds references to all of the deserialized
                // AMF3 strings for a given header value or message body so that
                // repeat instances of the same string can be deserialized by
                // reference
                strings = me.strings = [];

                // The objects array holds references to deserialized objects so
                // that repeat occurrences of the same object instance in the byte
                // array can be deserialized by reference.
                // If version is AMF0 this array holds anonymous objects, typed
                // objects, arrays, and ecma-arrays.
                // If version is AMF3 this array holds instances of Object, Array, XML,
                // XMLDocument, ByteArray, Date, and instances of user defined Classes
                objects = me.objects = [];

                // The traits array holds references to the "traits" (the
                // characteristics of objects that define a strong type such as the
                // class name and public member names) of deserialized AMF3 objects
                // so that if they are repeated they can be deserialized by reference.
                traits = me.traits = [];

                // The first two bytes of an AMF packet contain the AMF version
                // as an unsigned 16 bit integer.
                me.version = me.readUInt(2);

                // the next 2 bytes contain the header count
                for (headerCount = me.readUInt(2); headerCount--;) {
                    headers.push({
                        name: me.readAmf0String(),
                        mustUnderstand: me.readBoolean(),
                        byteLength: me.readUInt(4),
                        value: me.readValue()
                    });
                    // reset references (reference indices are local to each header)
                    strings = me.strings = [];
                    objects = me.objects = [];
                    traits = me.traits = [];
                }

                // The 2 bytes immediately after the header contain the message count.
                for (messageCount = me.readUInt(2); messageCount--;) {
                    messages.push({
                        targetURI: me.readAmf0String(),
                        responseURI: me.readAmf0String(),
                        byteLength: me.readUInt(4),
                        body: me.readValue()
                    });
                    // reset references (reference indices are local to each message)
                    strings = me.strings = [];
                    objects = me.objects = [];
                    traits = me.traits = [];
                }

                // reset the pointer
                pos = 0;
                // null the bytes array and reference arrays to free up memory.
                bytes = strings = objects = traits =
                    me.bytes = me.strings = me.objects = me.traits = null;

                return me;
            },

            /**
             * Decodes an AMF3 byte array and that has one value and returns it.
             * Note: Destroys previously stored data in this Packet.
             * @param {Array} byteArray A byte array containing the encoded AMF data.
             * @return {Object} the decoded object
             */
            decodeValue: function(byteArray) {
                var me = this;

                bytes = me.bytes = byteArray;

                // reset the pointer
                pos = 0;

                // The first two bytes of an AMF packet contain the AMF version
                // as an unsigned 16 bit integer.
                me.version = 3;

                // The strings array holds references to all of the deserialized
                // AMF3 strings for a given header value or message body so that
                // repeat instances of the same string can be deserialized by
                // reference
                strings = me.strings = [];

                // The objects array holds references to deserialized objects so
                // that repeat occurrences of the same object instance in the byte
                // array can be deserialized by reference.
                // If version is AMF0 this array holds anonymous objects, typed
                // objects, arrays, and ecma-arrays.
                // If version is AMF3 this array holds instances of Object, Array, XML,
                // XMLDocument, ByteArray, Date, and instances of user defined Classes
                objects = me.objects = [];

                // The traits array holds references to the "traits" (the
                // characteristics of objects that define a strong type such as the
                // class name and public member names) of deserialized AMF3 objects
                // so that if they are repeated they can be deserialized by reference.
                traits = me.traits = [];

                // read one value and return it
                return me.readValue();
            },

            /**
             * Parses an xml string and returns an xml document
             * @private
             * @param {String} xml
             */
            parseXml: function(xml) {
                var doc;

                if (window.DOMParser) {
                    doc = (new DOMParser()).parseFromString(xml, "text/xml");
                } else {
                    doc = new ActiveXObject("Microsoft.XMLDOM");
                    doc.loadXML(xml);
                }

                return doc;
            },

            /**
             * Reads an AMF0 date from the byte array
             * @private
             */
            readAmf0Date: function() {
                var date = new Date(this.readDouble());
                // An AMF0 date type ends with a 16 bit integer time-zone, but
                // according to the spec time-zone is "reserved, not supported,
                // should be set to 0x000".
                pos += 2; // discard the time zone
                return date;
            },

            /**
             * Reads an AMF0 Object from the byte array
             * @private
             */
            readAmf0Object: function(obj) {
                var me = this,
                    key;

                obj = obj || {};

                // add the object to the objects array so that the AMF0 reference
                // type decoder can refer to it by index if needed.
                objects.push(obj);

                // An AMF0 object consists of a series of string keys and variable-
                // type values.  The end of the series is marked by an empty string
                // followed by the object-end marker (9).
                while ((key = me.readAmf0String()) || bytes[pos] !== 9) {
                    obj[key] = me.readValue();
                }

                // move the pointer past the object-end marker
                pos++;

                return obj;
            },

            /**
             * Reads an AMF0 string from the byte array
             * @private
             */
            readAmf0String: function() {
                // AMF0 strings begin with a 16 bit byte-length header.
                return this.readUtf8(this.readUInt(2));
            },

            readAmf0Xml: function() {
                return this.parseXml(this.readLongString());
            },

            readAmf3Array: function() {
                var me = this,
                    header = me.readUInt29(),
                    count, key, array, i;

                // AMF considers Arrays in two parts, the dense portion and the
                // associative portion. The binary representation of the associative
                // portion consists of name/value pairs (potentially none) terminated
                // by an empty string. The binary representation of the dense portion
                // is the size of the dense portion (potentially zero) followed by an
                // ordered list of values (potentially none).
                if (header & 1) {
                    // If the first (low) bit is a 1 read an array instance.  The
                    // remaining 1-28 bits are used to encode the length of the
                    // dense portion of the array.
                    count = (header >> 1);
                    // First read the associative portion of the array (if any).  If
                    // there is an associative portion, the array will be read as a
                    // javascript object, otherwise it will be a javascript array.
                    key = me.readAmf3String();
                    if (key) {
                        // First key is not an empty string - this is an associative
                        // array.  Read keys and values from the byte array until
                        // we get to an empty string key
                        array = {};
                        objects.push(array);
                        do {
                            array[key] = me.readValue();
                        } while((key = me.readAmf3String()));
                        // The dense portion of the array is then read into the
                        // associative object, keyed by ordinal index.
                        for (i = 0; i < count; i++) {
                            array[i] = me.readValue();
                        }
                    } else {
                        // First key is an empty string - this is an array with
                        // ordinal indices.
                        array = [];
                        objects.push(array);
                        for (i = 0; i < count; i++) {
                            array.push(me.readValue());
                        }
                    }
                } else {
                    // If the first (low) bit is a 0 read an array reference. The
                    // remaining 1-28 bits are used to encode the reference index
                    array = objects[header >> 1];
                }

                return array;
            },

            /**
             * Reads an AMF3 date from the byte array
             * @private
             */
            readAmf3Date: function() {
                var me = this,
                    header = me.readUInt29(),
                    date;

                if (header & 1) {
                    // If the first (low) bit is a 1, this is a date instance.
                    date = new Date(me.readDouble());
                    objects.push(date);
                } else {
                    // If the first (low) bit is a 0, this is a date reference.
                    // The remaining 1-28 bits encode the reference index
                    date = objects[header >> 1];
                }

                return date;
            },

            /**
             * Reads an AMF3 object from the byte array
             * @private
             */
            readAmf3Object: function() {
                var me = this,
                    header = me.readUInt29(),
                    members = [],
                    headerLast3Bits, memberCount, className,
                    dynamic, objectTraits, obj, key, klass, i;

                // There are 3 different types of object headers, distinguishable
                // by the 1-3 least significant bits.  All object instances have
                // a 1 in the low bit position, while references have a 0:
                //
                // 0    : object reference
                // 011  : traits
                // 01   : traits-ref
                // 111  : traits-ext
                if (header & 1) {
                    // first (low) bit of 1, denotes an encoded object instance
                    // The next string is the class name.
                    headerLast3Bits = (header & 0x07);
                    if (headerLast3Bits === 3) {
                        // If the 3 least significant bits of the header are "011"
                        // then trait information follows.
                        className = me.readAmf3String();
                        // A 1 in the header's 4th least significant byte position
                        // indicates that dynamic members may follow the sealed
                        // members.
                        dynamic = !!(header & 0x08);
                        // Shift off the 4 least significant bits, and the remaining
                        // 1-25 bits encode the number of sealed member names. Read
                        // as many strings from the byte array as member names.
                        memberCount = (header >> 4);
                        for (i = 0; i < memberCount; i++) {
                            members.push(me.readAmf3String());
                        }
                        objectTraits = {
                            className: className,
                            dynamic: dynamic,
                            members: members
                        };
                        // An objects traits are cached in the traits array enabling
                        // the traits for a given class to only be encoded once for
                        // a series of instances.
                        traits.push(objectTraits);
                    } else if ((header & 0x03) === 1) {
                        // If the 2 least significant bits are "01", then a traits
                        // reference follows.  The remaining 1-27 bits are used
                        // to encode the trait reference index.
                        objectTraits = traits[header >> 2];
                        className = objectTraits.className;
                        dynamic = objectTraits.dynamic;
                        members = objectTraits.members;
                        memberCount = members.length;
                    } else if (headerLast3Bits === 7) {
                        // if the 3 lease significant bits are "111" then
                        // externalizable trait data follows

                        // TODO: implement externalizable traits
                    }

                    if (className) {
                        klass = Ext.ClassManager.getByAlias('amf.' + className);
                        obj = klass ? new klass() : {$className: className};
                    } else {
                        obj = {};
                    }
                    objects.push(obj);

                    // read the sealed member values
                    for (i = 0; i < memberCount; i++) {
                        obj[members[i]] = me.readValue();
                    }

                    if (dynamic) {
                        // If the dynamic flag is set, dynamic members may follow
                        // the sealed members. Read key/value pairs until we
                        // encounter an empty string key signifying the end of the
                        // dynamic members.
                        while ((key = me.readAmf3String())) {
                            obj[key] = me.readValue();
                        }
                    }

                    // finally, check if we need to convert this class
                    if ((!klass) && this.converters[className]) {
                        obj = this.converters[className](obj);
                    }

                } else {
                    // If the first (low) bit of the header is a 0, this is an
                    // object reference. The remaining 1-28 significant bits are
                    // used to encode an object reference index.
                    obj = objects[header >> 1];
                }

                return obj;
            },

            /**
             * Reads an AMF3 string from the byte array
             * @private
             */
            readAmf3String: function() {
                var me = this,
                    header = me.readUInt29(),
                    value;

                if (header & 1) {
                    // If the first (low) bit is a 1, this is a string literal.
                    // Discard the low bit.  The remaining 1-28 bits are used to
                    // encode the string's byte-length.
                    value = me.readUtf8(header >> 1);
                    if (value) {
                        // the emtpy string is never encoded by reference
                        strings.push(value);
                    }
                    return value;
                } else {
                    // If the first (low) bit is a 0, this is a string reference.
                    // Discard the low bit, then look up and return the reference
                    // from the strings array using the remaining 1-28 bits as the
                    // index.
                    return strings[header >> 1];
                }
            },

            /**
             * Reads an AMF3 XMLDocument type or XML type from the byte array
             * @private
             */
            readAmf3Xml: function() {
                var me = this,
                    header = me.readUInt29(),
                    doc;

                if (header & 1) {
                    // If the first (low) bit is a 1, this is an xml instance. The
                    // remaining 1-28 bits encode the byte-length of the xml string.
                    doc = me.parseXml(me.readUtf8(header >> 1));
                    objects.push(doc);
                } else {
                    // if the first (low) bit is a 1, this is an xml reference. The
                    // remaining 1-28 bits encode the reference index.
                    doc = objects[header >> 1];
                }

                return doc;
            },

            /**
             * Reads an AMF0 boolean from the byte array
             * @private
             */
            readBoolean: function() {
                return !!bytes[pos++];
            },

            /**
             * Reads an AMF3 ByteArray type from the byte array
             * @private
             */
            readByteArray: function() {
                var header = this.readUInt29(),
                    byteArray, end;

                if (header & 1) {
                    // If the first (low) bit is a 1, this is a ByteArray instance.
                    // The remaining 1-28 bits encode the ByteArray's byte-length.
                    end = pos + (header >> 1);
                    // Depending on the browser, "bytes" may be either a Uint8Array
                    // or an Array.  Uint8Arrays don't have Array methods, so
                    // we have to use Array.prototype.slice to get the byteArray
                    byteArray = Array.prototype.slice.call(bytes, pos, end);
                    objects.push(byteArray);
                    // move the pointer to the first byte after the byteArray that
                    // was just read
                    pos = end;
                } else {
                    // if the first (low) bit is a 1, this is a ByteArray reference.
                    // The remaining 1-28 bits encode the reference index.
                    byteArray = objects[header >> 1];
                }

                return byteArray;
            },

            /**
             * Reads a IEEE 754 double-precision binary floating-point number
             * @private
             */
            readDouble: function() {
                var byte1 = bytes[pos++],
                    byte2 = bytes[pos++],
                    // the first bit of byte1 is the sign (0 = positive, 1 = negative.
                    // We read this bit by shifting the 7 least significant bits of
                    // byte1 off to the right.
                    sign = (byte1 >> 7) ? -1 : 1,
                    // the exponent takes up the next 11 bits.
                    exponent =
                        // extract the 7 least significant bits from byte1 and then
                        // shift them left by 4 bits to make room for the 4 remaining
                        // bits from byte 2
                        (((byte1 & 0x7F) << 4)
                         // add the 4 most significant bits from byte 2 to complete
                         // the exponent
                         | (byte2 >> 4)),
                    // the remaining 52 bits make up the significand. read the 4
                    // least significant bytes of byte 2 to begin the significand
                    significand = (byte2 & 0x0F),
                    // The most significant bit of the significand is always 1 for
                    // a normalized number, therefore it is not stored. This bit is
                    // referred to as the "hidden bit". The true bit width of the
                    // significand is 53 if you include the hidden bit. An exponent
                    // of 0 indicates that this is a subnormal number, and subnormal
                    // numbers always have a 0 hidden bit.
                    hiddenBit = exponent ? 1 : 0,
                    i = 6;

                // The operands of all bitwise operators in javascript are converted
                // to signed 32 bit integers.  It is therefore impossible to construct
                // the 52 bit significand by repeatedly shifting its bits and then
                // bitwise OR-ing the result with the the next byte. To work around
                // this issue, we repeatedly multiply the significand by 2^8 which
                // produces the same result as (significand << 8), then we add the
                // next byte, which has the same result as a bitwise OR.
                while (i--) {
                    significand = (significand * twoPow8) + bytes[pos++];
                }

                if (!exponent) {
                    if (!significand) {
                        // if both exponent and significand are 0, the number is 0
                        return 0;
                    }
                    // If the exponent is 0, but the significand is not 0, this
                    // is a subnormal number. Subnormal numbers are encoded with a
                    // biased exponent of 0, but are interpreted with the value of
                    // the smallest allowed exponent, which is one greater.
                    exponent = 1;
                }

                // 0x7FF (2047) is a special exponent value that represents infinity
                // if the significand is 0, and NaN if the significand is not 0
                if (exponent === 0x7FF) {
                    return significand ? NaN : (Infinity * sign);
                }

                return sign *
                    // The exponent is encoded using an offset binary
                    // representation with the zero offset being 0x3FF (1023),
                    // so we have to subtract 0x3FF to get the true exponent
                    Math.pow(2, exponent - 0x3FF) *
                    // convert the significand to its decimal value by multiplying
                    // it by 2^52 and then add the hidden bit
                    (hiddenBit + twoPowN52 * significand);
            },

            /**
             * Reads an AMF0 ECMA Array from the byte array
             * @private
             */
            readEcmaArray: function() {
                // An ecma array type is encoded exactly like an anonymous object
                // with the exception that it has a 32 bit "count" at the beginning.
                // We handle emca arrays by just throwing away the count and then
                // letting the object decoder handle the rest.
                pos += 4;
                return this.readAmf0Object();
            },

            /**
             * Returns false.  Used for reading the false type
             * @private
             */
            readFalse: function() {
                return false;
            },

            /**
             * Reads a long string (longer than 65535 bytes) from the byte array
             * @private
             */
            readLongString: function() {
                // long strings begin with a 32 bit byte-length header.
                return this.readUtf8(this.readUInt(4));
            },

            /**
             * Returns null.  Used for reading the null type
             * @private
             */
            readNull: function() {
                return null;
            },

            /**
             * Reads a reference from the byte array.  Reference types are used to
             * avoid duplicating data if the same instance of a complex object (which
             * is defined in AMF0 as an anonymous object, typed object, array, or
             * ecma-array) is included in the data more than once.
             * @private
             */
            readReference: function() {
                // a reference type contains a single 16 bit integer that represents
                // the index of an already deserialized object in the objects array
                return objects[this.readUInt(2)];
            },

            /**
             * Reads an AMF0 strict array (an array with ordinal indices)
             * @private
             */
            readStrictArray: function() {
                var me = this,
                    len = me.readUInt(4),
                    arr = [];

                objects.push(arr);

                while (len--) {
                    arr.push(me.readValue());
                }

                return arr;
            },

            /**
             * Returns true.  Used for reading the true type
             * @private
             */
            readTrue: function() {
                return true;
            },

            /**
             * Reads an AMF0 typed object from the byte array
             * @private
             */
            readTypedObject: function() {
                var me = this,
                    className = me.readAmf0String(),
                    klass, instance, modified;

                klass = Ext.ClassManager.getByAlias('amf.' + className);
                instance = klass ? new klass() : {$className: className}; // if there is no klass, mark the classname for easier parsing of returned results

                modified = me.readAmf0Object(instance);

                // check if we need to convert this class
                if ((!klass) && this.converters[className]) {
                    modified = this.converters[className](instance);
                }
                return modified;
            },

            /**
             * Reads an unsigned integer from the byte array
             * @private
             * @param {Number} byteCount the number of bytes to read, e.g. 2 to read
             * a 16 bit integer, 4 to read a 32 bit integer, etc.
             * @return {Number}
             */
            readUInt: function(byteCount) {
                var i = 1,
                    result;

                // read the first byte
                result = bytes[pos++];
                // if this is a multi-byte int, loop over the remaining bytes
                for (; i < byteCount; ++i) {
                    // shift the result 8 bits to the left and add the next byte.
                    result = (result << 8) | bytes[pos++];
                }

                return result;
            },

            /**
             * Reads an unsigned 29-bit integer from the byte array.
             * AMF 3 makes use of a special compact format for writing integers to
             * reduce the number of bytes required for encoding. As with a normal
             * 32-bit integer, up to 4 bytes are required to hold the value however
             * the high bit of the first 3 bytes are used as flags to determine
             * whether the next byte is part of the integer. With up to 3 bits of
             * the 32 bits being used as flags, only 29 significant bits remain for
             * encoding an integer. This means the largest unsigned integer value
             * that can be represented is 2^29-1.
             *
             *           (hex)         :                (binary)
             * 0x00000000 - 0x0000007F :  0xxxxxxx
             * 0x00000080 - 0x00003FFF :  1xxxxxxx 0xxxxxxx
             * 0x00004000 - 0x001FFFFF :  1xxxxxxx 1xxxxxxx 0xxxxxxx
             * 0x00200000 - 0x3FFFFFFF :  1xxxxxxx 1xxxxxxx 1xxxxxxx xxxxxxxx
             * @private
             * @return {Number}
             */
            readUInt29: function() {
                var value = bytes[pos++],
                    nextByte;

                if (value & 0x80) {
                    // if the high order bit of the first byte is a 1, the next byte
                    // is also part of this integer.
                    nextByte = bytes[pos++];
                    // remove the high order bit from both bytes before combining them
                    value = ((value & 0x7F) << 7) | (nextByte & 0x7F);
                    if (nextByte & 0x80) {
                        // if the high order byte of the 2nd byte is a 1, then
                        // there is a 3rd byte
                        nextByte = bytes[pos++];
                        // remove the high order bit from the 3rd byte before
                        // adding it to the value
                        value = (value << 7) | (nextByte & 0x7F);
                        if (nextByte & 0x80) {
                            // 4th byte is also part of the integer
                            nextByte = bytes[pos++];
                            // use all 8 bits of the 4th byte
                            value = (value << 8) | nextByte;
                        }

                    }
                }

                return value;
            },

            /**
             * Returns undefined.  Used for reading the undefined type
             * @private
             */
            readUndefined: Ext.emptyFn,

            /**
             * Returns undefined.  Used for reading the unsupported type
             * @private
             */
            readUnsupported: Ext.emptyFn,

            /**
             * Reads a UTF-8 string from the byte array
             * @private
             * @param {Number} byteLength The number of bytes to read
             * @return {String}
             */
            readUtf8: function(byteLength) {
                var end = pos + byteLength, // the string's end position
                    chars = [],
                    charCount = 0,
                    maxCharCount = 65535,
                    charArrayCount = 1,
                    result = [],
                    i = 0,
                    charArrays, byteCount, charCode;

                charArrays = [chars];

                // UTF-8 characters may be encoded using 1-4 bytes. The number of
                // bytes that a character consumes is determined by reading the
                // leading byte.  Values 0-127 in the leading byte indicate a single-
                // byte ASCII-compatible character. Values 192-223 (bytes with "110"
                // in the high-order position) indicate a 2-byte character, values
                // 224-239 (bytes with "1110" in the high-order position) indicate a
                // 3-byte character, and values 240-247 (bytes with "11110" in the
                // high-order position) indicate a 4-byte character. The remaining
                // bits of the leading byte hold bits of the encoded character, with
                // leading zeros if necessary.
                //
                // The continuation bytes all have "10" in the high-order position,
                // which means only the 6 least significant bits of continuation
                // bytes are available to hold the bits of the encoded character.
                //
                // The following table illustrates the binary format of UTF-8
                // characters:
                //
                // Bits     Byte 1      Byte 2      Byte 3      Byte 4
                // -----------------------------------------------------
                //  7       0xxxxxxx
                // 11       110xxxxx    10xxxxxx
                // 16       1110xxxx    10xxxxxx    10xxxxxx
                // 21       11110xxx    10xxxxxx    10xxxxxx    10xxxxxx
                while (pos < end) {
                    // read a byte from the byte array - if the byte's value is less
                    // than 128 we are dealing with a single byte character
                    charCode = bytes[pos++];
                    if (charCode > 127) {
                        // if the byte's value is greater than 127 we are dealing
                        // with a multi-byte character.
                        if (charCode > 239) {
                            // a leading-byte value greater than 239 means this is a
                            // 4-byte character
                            byteCount = 4;
                            // Use only the 3 least-significant bits of the leading
                            // byte of a 4-byte character. This is achieved by
                            // applying the following bit mask:
                            // (charCode & 0x07)
                            // which is equivalent to:
                            //     11110xxx (the byte)
                            // AND 00000111 (the mask)
                            charCode = (charCode & 0x07);
                        } else if (charCode > 223) {
                            // a leading-byte value greater than 223 but less than
                            // 240 means this is a 3-byte character
                            byteCount = 3;
                            // Use only the 4 least-significant bits of the leading
                            // byte of a 3-byte character. This is achieved by
                            // applying the following bit mask:
                            // (charCode & 0x0F)
                            // which is equivalent to:
                            //     1110xxxx (the byte)
                            // AND 00001111 (the mask)
                            charCode = (charCode & 0x0F);
                        } else {
                            // a leading-byte value less than 224 but (implicitly)
                            // greater than 191 means this is a 2-byte character
                            byteCount = 2;
                            // Use only the 5 least-significant bits of the first
                            // byte of a 2-byte character. This is achieved by
                            // applying the following bit mask:
                            // (charCode & 0x1F)
                            // which is equivalent to:
                            //     110xxxxx (the byte)
                            // AND 00011111 (the mask)
                            charCode = (charCode & 0x1F);
                        }

                        while (--byteCount) {
                            // get one continuation byte. then strip off the leading
                            // "10" by applying the following bit mask:
                            // (b & 0x3F)
                            // which is equialent to:
                            //     10xxxxxx (the byte)
                            // AND 00111111 (the mask)
                            // That leaves 6 remaining bits on the continuation byte
                            // which are concatenated onto the character's bits
                            charCode = ((charCode << 6) | (bytes[pos++] & 0x3F));
                        }
                    }

                    chars.push(charCode);

                    if (++charCount === maxCharCount) {
                        charArrays.push(chars = []);
                        charCount = 0;
                        charArrayCount ++;
                    }
                }

                // At this point we end up with an array of char arrays, each char
                // array being no longer than 65,535 characters, the fastest way to
                // turn these char arrays into strings is to pass them as the
                // arguments to fromCharCode (fortunately all currently supported
                // browsers can handle at least 65,535 function arguments).
                for (; i < charArrayCount; i++) {
                    // create a result array containing the strings converted from
                    // the individual character arrays.
                    result.push(String.fromCharCode.apply(String, charArrays[i]));
                }

                return result.join('');
            },

            /**
             * Reads an AMF "value-type" from the byte array.  Automatically detects
             * the data type by reading the "type marker" from the first byte after
             * the pointer.
             * @private
             */
            readValue: function() {
                var me = this,
                    marker = bytes[pos++];

                // With the introduction of AMF3, a special type marker was added to
                // AMF0 to signal a switch to AMF3 serialization. This allows a packet
                // to start out in AMF 0 and switch to AMF 3 on the first complex type
                // to take advantage of the more the efficient encoding of AMF 3.
                if (marker === 17) {
                    // change the version to AMF3 when we see a 17 marker
                    me.version = 3;
                    marker = bytes[pos++];
                }

                return me[me.typeMap[me.version][marker]]();
            },

            /**
             * Converters used in converting specific typed Flex classes to JavaScript usable form.
             * @private
             */

            converters: {
                'flex.messaging.io.ArrayCollection': function(obj) {
                    return obj.source || []; // array collections have a source var that contains the actual data
                }
            }

        };
    });