]> git.proxmox.com Git - extjs.git/blame - extjs/packages/amf/src/data/amf/Packet.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / amf / src / data / amf / Packet.js
CommitLineData
6527f429
DM
1// @tag enterprise\r
2/**\r
3 * @class Ext.data.amf.Packet\r
4 * This class represents an Action Message Format (AMF) Packet. It contains all\r
5 * the logic required to decode an AMF Packet from a byte array.\r
6 * To decode a Packet, first construct a Packet:\r
7 *\r
8 * var packet = Ext.create('Ext.data.amf.Packet');\r
9 *\r
10 * Then use the decode method to decode an AMF byte array:\r
11 *\r
12 * packet.decode(bytes);\r
13 *\r
14 * where "bytes" is a Uint8Array or an array of numbers representing the binary\r
15 * AMF data.\r
16 *\r
17 * To access the decoded data use the #version, #headers, and #messages properties:\r
18 *\r
19 * console.log(packet.version, packet.headers, packet.messages);\r
20 *\r
21 * For more information on working with AMF data please refer to the\r
22 * [AMF Guide](#/guide/amf).\r
23 */\r
24Ext.define('Ext.data.amf.Packet', function() {\r
25 var twoPowN52 = Math.pow(2, -52),\r
26 twoPow8 = Math.pow(2, 8),\r
27 pos = 0,\r
28 bytes, strings, objects, traits;\r
29\r
30 return {\r
31 /**\r
32 * @property {Array} headers\r
33 * @readonly\r
34 * The decoded headers. Each header has the following properties:\r
35 *\r
36 * - `name`: String\r
37 * The header name. Typically identifies a remote operation or method to\r
38 * be invoked by this context header.\r
39 * - `mustUnderstand`: Boolean\r
40 * If `true` this flag instructs the endpoint to abort and generate an\r
41 * error if the header is not understood.\r
42 * - `byteLength`: Number\r
43 * If the byte-length of a header is known it can be specified to optimize\r
44 * memory allocation at the remote endpoint.\r
45 * - `value`: Mixed\r
46 * The header value\r
47 */\r
48\r
49 /**\r
50 * @property {Array} messages\r
51 * @readonly\r
52 * The decoded messages. Each message has the following properties:\r
53 *\r
54 * - `targetURI`: String\r
55 * Describes which operation, function, or method is to be remotely\r
56 * invoked.\r
57 * - `responseURI`: String\r
58 * A unique operation name\r
59 * - `byteLength`: Number\r
60 * Optional byte-length of the message body\r
61 * - `body`: Mixed\r
62 * The message body\r
63 */\r
64\r
65 /**\r
66 * @property {Number} version\r
67 * @readonly\r
68 * The AMF version number (0 or 3)\r
69 */\r
70\r
71 /**\r
72 * Mapping of AMF data types to the names of the methods responsible for\r
73 * reading them.\r
74 * @private\r
75 */\r
76 typeMap: {\r
77 // AMF0 mapping\r
78 0: {\r
79 0: 'readDouble',\r
80 1: 'readBoolean',\r
81 2: 'readAmf0String',\r
82 3: 'readAmf0Object',\r
83 5: 'readNull',\r
84 6: 'readUndefined',\r
85 7: 'readReference',\r
86 8: 'readEcmaArray',\r
87 10: 'readStrictArray',\r
88 11: 'readAmf0Date',\r
89 12: 'readLongString',\r
90 13: 'readUnsupported',\r
91 15: 'readAmf0Xml',\r
92 16: 'readTypedObject'\r
93 },\r
94 // AMF3 mapping\r
95 3: {\r
96 0: 'readUndefined',\r
97 1: 'readNull',\r
98 2: 'readFalse',\r
99 3: 'readTrue',\r
100 4: 'readUInt29',\r
101 5: 'readDouble',\r
102 6: 'readAmf3String',\r
103 7: 'readAmf3Xml',\r
104 8: 'readAmf3Date',\r
105 9: 'readAmf3Array',\r
106 10: 'readAmf3Object',\r
107 11: 'readAmf3Xml',\r
108 12: 'readByteArray'\r
109 }\r
110 },\r
111\r
112 /**\r
113 * Decodes an AMF btye array and sets the decoded data as the\r
114 * Packet's #version, #headers, and #messages properties\r
115 * @param {Array} byteArray A byte array containing the encoded AMF data.\r
116 * @return {Ext.data.amf.Packet} this AMF Packet\r
117 */\r
118 decode: function(byteArray) {\r
119 var me = this,\r
120 headers = me.headers = [],\r
121 messages = me.messages = [],\r
122 headerCount, messageCount;\r
123\r
124 pos = 0;\r
125\r
126 bytes = me.bytes = byteArray;\r
127\r
128 // The strings array holds references to all of the deserialized\r
129 // AMF3 strings for a given header value or message body so that\r
130 // repeat instances of the same string can be deserialized by\r
131 // reference\r
132 strings = me.strings = [];\r
133\r
134 // The objects array holds references to deserialized objects so\r
135 // that repeat occurrences of the same object instance in the byte\r
136 // array can be deserialized by reference.\r
137 // If version is AMF0 this array holds anonymous objects, typed\r
138 // objects, arrays, and ecma-arrays.\r
139 // If version is AMF3 this array holds instances of Object, Array, XML,\r
140 // XMLDocument, ByteArray, Date, and instances of user defined Classes\r
141 objects = me.objects = [];\r
142\r
143 // The traits array holds references to the "traits" (the\r
144 // characteristics of objects that define a strong type such as the\r
145 // class name and public member names) of deserialized AMF3 objects\r
146 // so that if they are repeated they can be deserialized by reference.\r
147 traits = me.traits = [];\r
148\r
149 // The first two bytes of an AMF packet contain the AMF version\r
150 // as an unsigned 16 bit integer.\r
151 me.version = me.readUInt(2);\r
152\r
153 // the next 2 bytes contain the header count\r
154 for (headerCount = me.readUInt(2); headerCount--;) {\r
155 headers.push({\r
156 name: me.readAmf0String(),\r
157 mustUnderstand: me.readBoolean(),\r
158 byteLength: me.readUInt(4),\r
159 value: me.readValue()\r
160 });\r
161 // reset references (reference indices are local to each header)\r
162 strings = me.strings = [];\r
163 objects = me.objects = [];\r
164 traits = me.traits = [];\r
165 }\r
166\r
167 // The 2 bytes immediately after the header contain the message count.\r
168 for (messageCount = me.readUInt(2); messageCount--;) {\r
169 messages.push({\r
170 targetURI: me.readAmf0String(),\r
171 responseURI: me.readAmf0String(),\r
172 byteLength: me.readUInt(4),\r
173 body: me.readValue()\r
174 });\r
175 // reset references (reference indices are local to each message)\r
176 strings = me.strings = [];\r
177 objects = me.objects = [];\r
178 traits = me.traits = [];\r
179 }\r
180\r
181 // reset the pointer\r
182 pos = 0;\r
183 // null the bytes array and reference arrays to free up memory.\r
184 bytes = strings = objects = traits =\r
185 me.bytes = me.strings = me.objects = me.traits = null;\r
186\r
187 return me;\r
188 },\r
189\r
190\r
191 /**\r
192 * Decodes an AMF3 byte array and that has one value and returns it.\r
193 * Note: Destroys previously stored data in this Packet.\r
194 * @param {Array} byteArray A byte array containing the encoded AMF data.\r
195 * @return {Object} the decoded object\r
196 */\r
197 decodeValue: function(byteArray) {\r
198 var me = this;\r
199\r
200 bytes = me.bytes = byteArray;\r
201\r
202 // reset the pointer\r
203 pos = 0;\r
204\r
205 // The first two bytes of an AMF packet contain the AMF version\r
206 // as an unsigned 16 bit integer.\r
207 me.version = 3;\r
208\r
209 // The strings array holds references to all of the deserialized\r
210 // AMF3 strings for a given header value or message body so that\r
211 // repeat instances of the same string can be deserialized by\r
212 // reference\r
213 strings = me.strings = [];\r
214\r
215 // The objects array holds references to deserialized objects so\r
216 // that repeat occurrences of the same object instance in the byte\r
217 // array can be deserialized by reference.\r
218 // If version is AMF0 this array holds anonymous objects, typed\r
219 // objects, arrays, and ecma-arrays.\r
220 // If version is AMF3 this array holds instances of Object, Array, XML,\r
221 // XMLDocument, ByteArray, Date, and instances of user defined Classes\r
222 objects = me.objects = [];\r
223\r
224 // The traits array holds references to the "traits" (the\r
225 // characteristics of objects that define a strong type such as the\r
226 // class name and public member names) of deserialized AMF3 objects\r
227 // so that if they are repeated they can be deserialized by reference.\r
228 traits = me.traits = [];\r
229\r
230 // read one value and return it\r
231 return me.readValue();\r
232 },\r
233\r
234\r
235\r
236 /**\r
237 * Parses an xml string and returns an xml document\r
238 * @private\r
239 * @param {String} xml\r
240 */\r
241 parseXml: function(xml) {\r
242 var doc;\r
243\r
244 if (window.DOMParser) {\r
245 doc = (new DOMParser()).parseFromString(xml, "text/xml");\r
246 } else {\r
247 doc = new ActiveXObject("Microsoft.XMLDOM");\r
248 doc.loadXML(xml);\r
249 }\r
250\r
251 return doc;\r
252 },\r
253\r
254 /**\r
255 * Reads an AMF0 date from the byte array\r
256 * @private\r
257 */\r
258 readAmf0Date: function() {\r
259 var date = new Date(this.readDouble());\r
260 // An AMF0 date type ends with a 16 bit integer time-zone, but\r
261 // according to the spec time-zone is "reserved, not supported,\r
262 // should be set to 0x000".\r
263 pos += 2; // discard the time zone\r
264 return date;\r
265 },\r
266\r
267 /**\r
268 * Reads an AMF0 Object from the byte array\r
269 * @private\r
270 */\r
271 readAmf0Object: function(obj) {\r
272 var me = this,\r
273 key;\r
274\r
275 obj = obj || {};\r
276\r
277 // add the object to the objects array so that the AMF0 reference\r
278 // type decoder can refer to it by index if needed.\r
279 objects.push(obj);\r
280\r
281 // An AMF0 object consists of a series of string keys and variable-\r
282 // type values. The end of the series is marked by an empty string\r
283 // followed by the object-end marker (9).\r
284 while ((key = me.readAmf0String()) || bytes[pos] !== 9) {\r
285 obj[key] = me.readValue();\r
286 }\r
287\r
288 // move the pointer past the object-end marker\r
289 pos++;\r
290\r
291 return obj;\r
292 },\r
293\r
294 /**\r
295 * Reads an AMF0 string from the byte array\r
296 * @private\r
297 */\r
298 readAmf0String: function() {\r
299 // AMF0 strings begin with a 16 bit byte-length header.\r
300 return this.readUtf8(this.readUInt(2));\r
301 },\r
302\r
303 readAmf0Xml: function() {\r
304 return this.parseXml(this.readLongString());\r
305 },\r
306\r
307 readAmf3Array: function() {\r
308 var me = this,\r
309 header = me.readUInt29(),\r
310 count, key, array, i;\r
311\r
312 // AMF considers Arrays in two parts, the dense portion and the\r
313 // associative portion. The binary representation of the associative\r
314 // portion consists of name/value pairs (potentially none) terminated\r
315 // by an empty string. The binary representation of the dense portion\r
316 // is the size of the dense portion (potentially zero) followed by an\r
317 // ordered list of values (potentially none).\r
318 if (header & 1) {\r
319 // If the first (low) bit is a 1 read an array instance. The\r
320 // remaining 1-28 bits are used to encode the length of the\r
321 // dense portion of the array.\r
322 count = (header >> 1);\r
323 // First read the associative portion of the array (if any). If\r
324 // there is an associative portion, the array will be read as a\r
325 // javascript object, otherwise it will be a javascript array.\r
326 key = me.readAmf3String();\r
327 if (key) {\r
328 // First key is not an empty string - this is an associative\r
329 // array. Read keys and values from the byte array until\r
330 // we get to an empty string key\r
331 array = {};\r
332 objects.push(array);\r
333 do {\r
334 array[key] = me.readValue();\r
335 } while((key = me.readAmf3String()));\r
336 // The dense portion of the array is then read into the\r
337 // associative object, keyed by ordinal index.\r
338 for (i = 0; i < count; i++) {\r
339 array[i] = me.readValue();\r
340 }\r
341 } else {\r
342 // First key is an empty string - this is an array with\r
343 // ordinal indices.\r
344 array = [];\r
345 objects.push(array);\r
346 for (i = 0; i < count; i++) {\r
347 array.push(me.readValue());\r
348 }\r
349 }\r
350 } else {\r
351 // If the first (low) bit is a 0 read an array reference. The\r
352 // remaining 1-28 bits are used to encode the reference index\r
353 array = objects[header >> 1];\r
354 }\r
355\r
356 return array;\r
357 },\r
358\r
359 /**\r
360 * Reads an AMF3 date from the byte array\r
361 * @private\r
362 */\r
363 readAmf3Date: function() {\r
364 var me = this,\r
365 header = me.readUInt29(),\r
366 date;\r
367\r
368 if (header & 1) {\r
369 // If the first (low) bit is a 1, this is a date instance.\r
370 date = new Date(me.readDouble());\r
371 objects.push(date);\r
372 } else {\r
373 // If the first (low) bit is a 0, this is a date reference.\r
374 // The remaining 1-28 bits encode the reference index\r
375 date = objects[header >> 1];\r
376 }\r
377\r
378 return date;\r
379 },\r
380\r
381 /**\r
382 * Reads an AMF3 object from the byte array\r
383 * @private\r
384 */\r
385 readAmf3Object: function() {\r
386 var me = this,\r
387 header = me.readUInt29(),\r
388 members = [],\r
389 headerLast3Bits, memberCount, className,\r
390 dynamic, objectTraits, obj, key, klass, i;\r
391\r
392 // There are 3 different types of object headers, distinguishable\r
393 // by the 1-3 least significant bits. All object instances have\r
394 // a 1 in the low bit position, while references have a 0:\r
395 //\r
396 // 0 : object reference\r
397 // 011 : traits\r
398 // 01 : traits-ref\r
399 // 111 : traits-ext\r
400 if (header & 1) {\r
401 // first (low) bit of 1, denotes an encoded object instance\r
402 // The next string is the class name.\r
403 headerLast3Bits = (header & 0x07);\r
404 if (headerLast3Bits === 3) {\r
405 // If the 3 least significant bits of the header are "011"\r
406 // then trait information follows.\r
407 className = me.readAmf3String();\r
408 // A 1 in the header's 4th least significant byte position\r
409 // indicates that dynamic members may follow the sealed\r
410 // members.\r
411 dynamic = !!(header & 0x08);\r
412 // Shift off the 4 least significant bits, and the remaining\r
413 // 1-25 bits encode the number of sealed member names. Read\r
414 // as many strings from the byte array as member names.\r
415 memberCount = (header >> 4);\r
416 for (i = 0; i < memberCount; i++) {\r
417 members.push(me.readAmf3String());\r
418 }\r
419 objectTraits = {\r
420 className: className,\r
421 dynamic: dynamic,\r
422 members: members\r
423 };\r
424 // An objects traits are cached in the traits array enabling\r
425 // the traits for a given class to only be encoded once for\r
426 // a series of instances.\r
427 traits.push(objectTraits);\r
428 } else if ((header & 0x03) === 1) {\r
429 // If the 2 least significant bits are "01", then a traits\r
430 // reference follows. The remaining 1-27 bits are used\r
431 // to encode the trait reference index.\r
432 objectTraits = traits[header >> 2];\r
433 className = objectTraits.className;\r
434 dynamic = objectTraits.dynamic;\r
435 members = objectTraits.members;\r
436 memberCount = members.length;\r
437 } else if (headerLast3Bits === 7) {\r
438 // if the 3 lease significant bits are "111" then\r
439 // externalizable trait data follows\r
440\r
441 // TODO: implement externalizable traits\r
442 }\r
443\r
444 if (className) {\r
445 klass = Ext.ClassManager.getByAlias('amf.' + className);\r
446 obj = klass ? new klass() : {$className: className};\r
447 } else {\r
448 obj = {};\r
449 }\r
450 objects.push(obj);\r
451\r
452 // read the sealed member values\r
453 for (i = 0; i < memberCount; i++) {\r
454 obj[members[i]] = me.readValue();\r
455 }\r
456\r
457 if (dynamic) {\r
458 // If the dynamic flag is set, dynamic members may follow\r
459 // the sealed members. Read key/value pairs until we\r
460 // encounter an empty string key signifying the end of the\r
461 // dynamic members.\r
462 while ((key = me.readAmf3String())) {\r
463 obj[key] = me.readValue();\r
464 }\r
465 }\r
466\r
467 // finally, check if we need to convert this class\r
468 if ((!klass) && this.converters[className]) {\r
469 obj = this.converters[className](obj);\r
470 }\r
471\r
472 } else {\r
473 // If the first (low) bit of the header is a 0, this is an\r
474 // object reference. The remaining 1-28 significant bits are\r
475 // used to encode an object reference index.\r
476 obj = objects[header >> 1];\r
477 }\r
478\r
479 return obj;\r
480 },\r
481\r
482 /**\r
483 * Reads an AMF3 string from the byte array\r
484 * @private\r
485 */\r
486 readAmf3String: function() {\r
487 var me = this,\r
488 header = me.readUInt29(),\r
489 value;\r
490\r
491 if (header & 1) {\r
492 // If the first (low) bit is a 1, this is a string literal.\r
493 // Discard the low bit. The remaining 1-28 bits are used to\r
494 // encode the string's byte-length.\r
495 value = me.readUtf8(header >> 1);\r
496 if (value) {\r
497 // the emtpy string is never encoded by reference\r
498 strings.push(value);\r
499 }\r
500 return value;\r
501 } else {\r
502 // If the first (low) bit is a 0, this is a string reference.\r
503 // Discard the low bit, then look up and return the reference\r
504 // from the strings array using the remaining 1-28 bits as the\r
505 // index.\r
506 return strings[header >> 1];\r
507 }\r
508 },\r
509\r
510 /**\r
511 * Reads an AMF3 XMLDocument type or XML type from the byte array\r
512 * @private\r
513 */\r
514 readAmf3Xml: function() {\r
515 var me = this,\r
516 header = me.readUInt29(),\r
517 doc;\r
518\r
519 if (header & 1) {\r
520 // If the first (low) bit is a 1, this is an xml instance. The\r
521 // remaining 1-28 bits encode the byte-length of the xml string.\r
522 doc = me.parseXml(me.readUtf8(header >> 1));\r
523 objects.push(doc);\r
524 } else {\r
525 // if the first (low) bit is a 1, this is an xml reference. The\r
526 // remaining 1-28 bits encode the reference index.\r
527 doc = objects[header >> 1];\r
528 }\r
529\r
530 return doc;\r
531 },\r
532\r
533 /**\r
534 * Reads an AMF0 boolean from the byte array\r
535 * @private\r
536 */\r
537 readBoolean: function() {\r
538 return !!bytes[pos++];\r
539 },\r
540\r
541 /**\r
542 * Reads an AMF3 ByteArray type from the byte array\r
543 * @private\r
544 */\r
545 readByteArray: function() {\r
546 var header = this.readUInt29(),\r
547 byteArray, end;\r
548\r
549 if (header & 1) {\r
550 // If the first (low) bit is a 1, this is a ByteArray instance.\r
551 // The remaining 1-28 bits encode the ByteArray's byte-length.\r
552 end = pos + (header >> 1);\r
553 // Depending on the browser, "bytes" may be either a Uint8Array\r
554 // or an Array. Uint8Arrays don't have Array methods, so\r
555 // we have to use Array.prototype.slice to get the byteArray\r
556 byteArray = Array.prototype.slice.call(bytes, pos, end);\r
557 objects.push(byteArray);\r
558 // move the pointer to the first byte after the byteArray that\r
559 // was just read\r
560 pos = end;\r
561 } else {\r
562 // if the first (low) bit is a 1, this is a ByteArray reference.\r
563 // The remaining 1-28 bits encode the reference index.\r
564 byteArray = objects[header >> 1];\r
565 }\r
566\r
567 return byteArray;\r
568 },\r
569\r
570 /**\r
571 * Reads a IEEE 754 double-precision binary floating-point number\r
572 * @private\r
573 */\r
574 readDouble: function() {\r
575 var byte1 = bytes[pos++],\r
576 byte2 = bytes[pos++],\r
577 // the first bit of byte1 is the sign (0 = positive, 1 = negative.\r
578 // We read this bit by shifting the 7 least significant bits of\r
579 // byte1 off to the right.\r
580 sign = (byte1 >> 7) ? -1 : 1,\r
581 // the exponent takes up the next 11 bits.\r
582 exponent =\r
583 // extract the 7 least significant bits from byte1 and then\r
584 // shift them left by 4 bits to make room for the 4 remaining\r
585 // bits from byte 2\r
586 (((byte1 & 0x7F) << 4)\r
587 // add the 4 most significant bits from byte 2 to complete\r
588 // the exponent\r
589 | (byte2 >> 4)),\r
590 // the remaining 52 bits make up the significand. read the 4\r
591 // least significant bytes of byte 2 to begin the significand\r
592 significand = (byte2 & 0x0F),\r
593 // The most significant bit of the significand is always 1 for\r
594 // a normalized number, therefore it is not stored. This bit is\r
595 // referred to as the "hidden bit". The true bit width of the\r
596 // significand is 53 if you include the hidden bit. An exponent\r
597 // of 0 indicates that this is a subnormal number, and subnormal\r
598 // numbers always have a 0 hidden bit.\r
599 hiddenBit = exponent ? 1 : 0,\r
600 i = 6;\r
601\r
602 // The operands of all bitwise operators in javascript are converted\r
603 // to signed 32 bit integers. It is therefore impossible to construct\r
604 // the 52 bit significand by repeatedly shifting its bits and then\r
605 // bitwise OR-ing the result with the the next byte. To work around\r
606 // this issue, we repeatedly multiply the significand by 2^8 which\r
607 // produces the same result as (significand << 8), then we add the\r
608 // next byte, which has the same result as a bitwise OR.\r
609 while (i--) {\r
610 significand = (significand * twoPow8) + bytes[pos++];\r
611 }\r
612\r
613 if (!exponent) {\r
614 if (!significand) {\r
615 // if both exponent and significand are 0, the number is 0\r
616 return 0;\r
617 }\r
618 // If the exponent is 0, but the significand is not 0, this\r
619 // is a subnormal number. Subnormal numbers are encoded with a\r
620 // biased exponent of 0, but are interpreted with the value of\r
621 // the smallest allowed exponent, which is one greater.\r
622 exponent = 1;\r
623 }\r
624\r
625 // 0x7FF (2047) is a special exponent value that represents infinity\r
626 // if the significand is 0, and NaN if the significand is not 0\r
627 if (exponent === 0x7FF) {\r
628 return significand ? NaN : (Infinity * sign);\r
629 }\r
630\r
631 return sign *\r
632 // The exponent is encoded using an offset binary\r
633 // representation with the zero offset being 0x3FF (1023),\r
634 // so we have to subtract 0x3FF to get the true exponent\r
635 Math.pow(2, exponent - 0x3FF) *\r
636 // convert the significand to its decimal value by multiplying\r
637 // it by 2^52 and then add the hidden bit\r
638 (hiddenBit + twoPowN52 * significand);\r
639 },\r
640\r
641 /**\r
642 * Reads an AMF0 ECMA Array from the byte array\r
643 * @private\r
644 */\r
645 readEcmaArray: function() {\r
646 // An ecma array type is encoded exactly like an anonymous object\r
647 // with the exception that it has a 32 bit "count" at the beginning.\r
648 // We handle emca arrays by just throwing away the count and then\r
649 // letting the object decoder handle the rest.\r
650 pos += 4;\r
651 return this.readAmf0Object();\r
652 },\r
653\r
654 /**\r
655 * Returns false. Used for reading the false type\r
656 * @private\r
657 */\r
658 readFalse: function() {\r
659 return false;\r
660 },\r
661\r
662 /**\r
663 * Reads a long string (longer than 65535 bytes) from the byte array\r
664 * @private\r
665 */\r
666 readLongString: function() {\r
667 // long strings begin with a 32 bit byte-length header.\r
668 return this.readUtf8(this.readUInt(4));\r
669 },\r
670\r
671 /**\r
672 * Returns null. Used for reading the null type\r
673 * @private\r
674 */\r
675 readNull: function() {\r
676 return null;\r
677 },\r
678\r
679 /**\r
680 * Reads a reference from the byte array. Reference types are used to\r
681 * avoid duplicating data if the same instance of a complex object (which\r
682 * is defined in AMF0 as an anonymous object, typed object, array, or\r
683 * ecma-array) is included in the data more than once.\r
684 * @private\r
685 */\r
686 readReference: function() {\r
687 // a reference type contains a single 16 bit integer that represents\r
688 // the index of an already deserialized object in the objects array\r
689 return objects[this.readUInt(2)];\r
690 },\r
691\r
692 /**\r
693 * Reads an AMF0 strict array (an array with ordinal indices)\r
694 * @private\r
695 */\r
696 readStrictArray: function() {\r
697 var me = this,\r
698 len = me.readUInt(4),\r
699 arr = [];\r
700\r
701 objects.push(arr);\r
702\r
703 while (len--) {\r
704 arr.push(me.readValue());\r
705 }\r
706\r
707 return arr;\r
708 },\r
709\r
710 /**\r
711 * Returns true. Used for reading the true type\r
712 * @private\r
713 */\r
714 readTrue: Ext.returnTrue,\r
715\r
716 /**\r
717 * Reads an AMF0 typed object from the byte array\r
718 * @private\r
719 */\r
720 readTypedObject: function() {\r
721 var me = this,\r
722 className = me.readAmf0String(),\r
723 klass, instance, modified;\r
724\r
725 klass = Ext.ClassManager.getByAlias('amf.' + className);\r
726 instance = klass ? new klass() : {$className: className}; // if there is no klass, mark the classname for easier parsing of returned results\r
727\r
728 modified = me.readAmf0Object(instance);\r
729\r
730 // check if we need to convert this class\r
731 if ((!klass) && this.converters[className]) {\r
732 modified = this.converters[className](instance);\r
733 }\r
734 return modified;\r
735 },\r
736\r
737 /**\r
738 * Reads an unsigned integer from the byte array\r
739 * @private\r
740 * @param {Number} byteCount the number of bytes to read, e.g. 2 to read\r
741 * a 16 bit integer, 4 to read a 32 bit integer, etc.\r
742 * @return {Number}\r
743 */\r
744 readUInt: function(byteCount) {\r
745 var i = 1,\r
746 result;\r
747\r
748 // read the first byte\r
749 result = bytes[pos++];\r
750 // if this is a multi-byte int, loop over the remaining bytes\r
751 for (; i < byteCount; ++i) {\r
752 // shift the result 8 bits to the left and add the next byte.\r
753 result = (result << 8) | bytes[pos++];\r
754 }\r
755\r
756 return result;\r
757 },\r
758\r
759 /**\r
760 * Reads an unsigned 29-bit integer from the byte array.\r
761 * AMF 3 makes use of a special compact format for writing integers to\r
762 * reduce the number of bytes required for encoding. As with a normal\r
763 * 32-bit integer, up to 4 bytes are required to hold the value however\r
764 * the high bit of the first 3 bytes are used as flags to determine\r
765 * whether the next byte is part of the integer. With up to 3 bits of\r
766 * the 32 bits being used as flags, only 29 significant bits remain for\r
767 * encoding an integer. This means the largest unsigned integer value\r
768 * that can be represented is 2^29-1.\r
769 *\r
770 * (hex) : (binary)\r
771 * 0x00000000 - 0x0000007F : 0xxxxxxx\r
772 * 0x00000080 - 0x00003FFF : 1xxxxxxx 0xxxxxxx\r
773 * 0x00004000 - 0x001FFFFF : 1xxxxxxx 1xxxxxxx 0xxxxxxx\r
774 * 0x00200000 - 0x3FFFFFFF : 1xxxxxxx 1xxxxxxx 1xxxxxxx xxxxxxxx\r
775 * @private\r
776 * @return {Number}\r
777 */\r
778 readUInt29: function() {\r
779 var value = bytes[pos++],\r
780 nextByte;\r
781\r
782 if (value & 0x80) {\r
783 // if the high order bit of the first byte is a 1, the next byte\r
784 // is also part of this integer.\r
785 nextByte = bytes[pos++];\r
786 // remove the high order bit from both bytes before combining them\r
787 value = ((value & 0x7F) << 7) | (nextByte & 0x7F);\r
788 if (nextByte & 0x80) {\r
789 // if the high order byte of the 2nd byte is a 1, then\r
790 // there is a 3rd byte\r
791 nextByte = bytes[pos++];\r
792 // remove the high order bit from the 3rd byte before\r
793 // adding it to the value\r
794 value = (value << 7) | (nextByte & 0x7F);\r
795 if (nextByte & 0x80) {\r
796 // 4th byte is also part of the integer\r
797 nextByte = bytes[pos++];\r
798 // use all 8 bits of the 4th byte\r
799 value = (value << 8) | nextByte;\r
800 }\r
801\r
802 }\r
803 }\r
804\r
805 return value;\r
806 },\r
807\r
808 /**\r
809 * Returns undefined. Used for reading the undefined type\r
810 * @private\r
811 */\r
812 readUndefined: Ext.emptyFn,\r
813\r
814 /**\r
815 * Returns undefined. Used for reading the unsupported type\r
816 * @private\r
817 */\r
818 readUnsupported: Ext.emptyFn,\r
819\r
820 /**\r
821 * Reads a UTF-8 string from the byte array\r
822 * @private\r
823 * @param {Number} byteLength The number of bytes to read\r
824 * @return {String}\r
825 */\r
826 readUtf8: function(byteLength) {\r
827 var end = pos + byteLength, // the string's end position\r
828 chars = [],\r
829 charCount = 0,\r
830 maxCharCount = 65535,\r
831 charArrayCount = 1,\r
832 result = [],\r
833 i = 0,\r
834 charArrays, byteCount, charCode;\r
835\r
836 charArrays = [chars];\r
837\r
838 // UTF-8 characters may be encoded using 1-4 bytes. The number of\r
839 // bytes that a character consumes is determined by reading the\r
840 // leading byte. Values 0-127 in the leading byte indicate a single-\r
841 // byte ASCII-compatible character. Values 192-223 (bytes with "110"\r
842 // in the high-order position) indicate a 2-byte character, values\r
843 // 224-239 (bytes with "1110" in the high-order position) indicate a\r
844 // 3-byte character, and values 240-247 (bytes with "11110" in the\r
845 // high-order position) indicate a 4-byte character. The remaining\r
846 // bits of the leading byte hold bits of the encoded character, with\r
847 // leading zeros if necessary.\r
848 //\r
849 // The continuation bytes all have "10" in the high-order position,\r
850 // which means only the 6 least significant bits of continuation\r
851 // bytes are available to hold the bits of the encoded character.\r
852 //\r
853 // The following table illustrates the binary format of UTF-8\r
854 // characters:\r
855 //\r
856 // Bits Byte 1 Byte 2 Byte 3 Byte 4\r
857 // -----------------------------------------------------\r
858 // 7 0xxxxxxx\r
859 // 11 110xxxxx 10xxxxxx\r
860 // 16 1110xxxx 10xxxxxx 10xxxxxx\r
861 // 21 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx\r
862 while (pos < end) {\r
863 // read a byte from the byte array - if the byte's value is less\r
864 // than 128 we are dealing with a single byte character\r
865 charCode = bytes[pos++];\r
866 if (charCode > 127) {\r
867 // if the byte's value is greater than 127 we are dealing\r
868 // with a multi-byte character.\r
869 if (charCode > 239) {\r
870 // a leading-byte value greater than 239 means this is a\r
871 // 4-byte character\r
872 byteCount = 4;\r
873 // Use only the 3 least-significant bits of the leading\r
874 // byte of a 4-byte character. This is achieved by\r
875 // applying the following bit mask:\r
876 // (charCode & 0x07)\r
877 // which is equivalent to:\r
878 // 11110xxx (the byte)\r
879 // AND 00000111 (the mask)\r
880 charCode = (charCode & 0x07);\r
881 } else if (charCode > 223) {\r
882 // a leading-byte value greater than 223 but less than\r
883 // 240 means this is a 3-byte character\r
884 byteCount = 3;\r
885 // Use only the 4 least-significant bits of the leading\r
886 // byte of a 3-byte character. This is achieved by\r
887 // applying the following bit mask:\r
888 // (charCode & 0x0F)\r
889 // which is equivalent to:\r
890 // 1110xxxx (the byte)\r
891 // AND 00001111 (the mask)\r
892 charCode = (charCode & 0x0F);\r
893 } else {\r
894 // a leading-byte value less than 224 but (implicitly)\r
895 // greater than 191 means this is a 2-byte character\r
896 byteCount = 2;\r
897 // Use only the 5 least-significant bits of the first\r
898 // byte of a 2-byte character. This is achieved by\r
899 // applying the following bit mask:\r
900 // (charCode & 0x1F)\r
901 // which is equivalent to:\r
902 // 110xxxxx (the byte)\r
903 // AND 00011111 (the mask)\r
904 charCode = (charCode & 0x1F);\r
905 }\r
906\r
907 while (--byteCount) {\r
908 // get one continuation byte. then strip off the leading\r
909 // "10" by applying the following bit mask:\r
910 // (b & 0x3F)\r
911 // which is equialent to:\r
912 // 10xxxxxx (the byte)\r
913 // AND 00111111 (the mask)\r
914 // That leaves 6 remaining bits on the continuation byte\r
915 // which are concatenated onto the character's bits\r
916 charCode = ((charCode << 6) | (bytes[pos++] & 0x3F));\r
917 }\r
918 }\r
919\r
920 chars.push(charCode);\r
921\r
922 if (++charCount === maxCharCount) {\r
923 charArrays.push(chars = []);\r
924 charCount = 0;\r
925 charArrayCount ++;\r
926 }\r
927 }\r
928\r
929 // At this point we end up with an array of char arrays, each char\r
930 // array being no longer than 65,535 characters, the fastest way to\r
931 // turn these char arrays into strings is to pass them as the\r
932 // arguments to fromCharCode (fortunately all currently supported\r
933 // browsers can handle at least 65,535 function arguments).\r
934 for (; i < charArrayCount; i++) {\r
935 // create a result array containing the strings converted from\r
936 // the individual character arrays.\r
937 result.push(String.fromCharCode.apply(String, charArrays[i]));\r
938 }\r
939\r
940 return result.join('');\r
941 },\r
942\r
943 /**\r
944 * Reads an AMF "value-type" from the byte array. Automatically detects\r
945 * the data type by reading the "type marker" from the first byte after\r
946 * the pointer.\r
947 * @private\r
948 */\r
949 readValue: function() {\r
950 var me = this,\r
951 marker = bytes[pos++];\r
952\r
953 // With the introduction of AMF3, a special type marker was added to\r
954 // AMF0 to signal a switch to AMF3 serialization. This allows a packet\r
955 // to start out in AMF 0 and switch to AMF 3 on the first complex type\r
956 // to take advantage of the more the efficient encoding of AMF 3.\r
957 if (marker === 17) {\r
958 // change the version to AMF3 when we see a 17 marker\r
959 me.version = 3;\r
960 marker = bytes[pos++];\r
961 }\r
962\r
963 return me[me.typeMap[me.version][marker]]();\r
964 },\r
965\r
966 /**\r
967 * Converters used in converting specific typed Flex classes to JavaScript usable form.\r
968 * @private\r
969 */\r
970\r
971 converters: {\r
972 'flex.messaging.io.ArrayCollection': function(obj) {\r
973 return obj.source || []; // array collections have a source var that contains the actual data\r
974 }\r
975 }\r
976\r
977 };\r
978});