text: // @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: Ext.returnTrue,
  
          /**
           * 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;
          },
  
          /**
           * @method
           * Returns undefined.  Used for reading the undefined type
           * @private
           */
          readUndefined: Ext.emptyFn,
  
          /**
           * @method
           * 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
              }
          }
  
      };
  });