]>
Commit | Line | Data |
---|---|---|
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 | |
24 | Ext.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 | }); |