tweetcast/client/lib/websocket-js/flash-src/src/net/gimite/websocket/WebSocket.as
changeset 311 13702105c5ee
parent 310 526d3e411736
child 312 f8336354d107
child 314 0f1e6ce19b6d
equal deleted inserted replaced
310:526d3e411736 311:13702105c5ee
     1 // Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
       
     2 // License: New BSD License
       
     3 // Reference: http://dev.w3.org/html5/websockets/
       
     4 // Reference: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
       
     5 
       
     6 package net.gimite.websocket {
       
     7 
       
     8 import com.adobe.net.proxies.RFC2817Socket;
       
     9 import com.gsolo.encryption.SHA1;
       
    10 import com.hurlant.crypto.tls.TLSConfig;
       
    11 import com.hurlant.crypto.tls.TLSEngine;
       
    12 import com.hurlant.crypto.tls.TLSSecurityParameters;
       
    13 import com.hurlant.crypto.tls.TLSSocket;
       
    14 
       
    15 import flash.display.*;
       
    16 import flash.errors.*;
       
    17 import flash.events.*;
       
    18 import flash.external.*;
       
    19 import flash.net.*;
       
    20 import flash.system.*;
       
    21 import flash.utils.*;
       
    22 
       
    23 import mx.controls.*;
       
    24 import mx.core.*;
       
    25 import mx.events.*;
       
    26 import mx.utils.*;
       
    27 
       
    28 public class WebSocket extends EventDispatcher {
       
    29   
       
    30   private static const WEB_SOCKET_GUID:String = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
       
    31   
       
    32   private static const CONNECTING:int = 0;
       
    33   private static const OPEN:int = 1;
       
    34   private static const CLOSING:int = 2;
       
    35   private static const CLOSED:int = 3;
       
    36   
       
    37   private static const OPCODE_CONTINUATION:int = 0x00;
       
    38   private static const OPCODE_TEXT:int = 0x01;
       
    39   private static const OPCODE_BINARY:int = 0x02;
       
    40   private static const OPCODE_CLOSE:int = 0x08;
       
    41   private static const OPCODE_PING:int = 0x09;
       
    42   private static const OPCODE_PONG:int = 0x0a;
       
    43   
       
    44   private static const STATUS_NORMAL_CLOSURE:int = 1000;
       
    45   private static const STATUS_NO_CODE:int = 1005;
       
    46   private static const STATUS_CLOSED_ABNORMALLY:int = 1006;
       
    47   private static const STATUS_CONNECTION_ERROR:int = 5000;
       
    48   
       
    49   private var id:int;
       
    50   private var url:String;
       
    51   private var scheme:String;
       
    52   private var host:String;
       
    53   private var port:uint;
       
    54   private var path:String;
       
    55   private var origin:String;
       
    56   private var requestedProtocols:Array;
       
    57   private var cookie:String;
       
    58   private var headers:String;
       
    59   
       
    60   private var rawSocket:Socket;
       
    61   private var tlsSocket:TLSSocket;
       
    62   private var tlsConfig:TLSConfig;
       
    63   private var socket:Socket;
       
    64   
       
    65   private var acceptedProtocol:String;
       
    66   private var expectedDigest:String;
       
    67   
       
    68   private var buffer:ByteArray = new ByteArray();
       
    69   private var headerState:int = 0;
       
    70   private var readyState:int = CONNECTING;
       
    71   
       
    72   private var logger:IWebSocketLogger;
       
    73   private var base64Encoder:Base64Encoder = new Base64Encoder();
       
    74   
       
    75   public function WebSocket(
       
    76       id:int, url:String, protocols:Array, origin:String,
       
    77       proxyHost:String, proxyPort:int,
       
    78       cookie:String, headers:String,
       
    79       logger:IWebSocketLogger) {
       
    80     this.logger = logger;
       
    81     this.id = id;
       
    82     this.url = url;
       
    83     var m:Array = url.match(/^(\w+):\/\/([^\/:]+)(:(\d+))?(\/.*)?(\?.*)?$/);
       
    84     if (!m) fatal("SYNTAX_ERR: invalid url: " + url);
       
    85     this.scheme = m[1];
       
    86     this.host = m[2];
       
    87     var defaultPort:int = scheme == "wss" ? 443 : 80;
       
    88     this.port = parseInt(m[4]) || defaultPort;
       
    89     this.path = (m[5] || "/") + (m[6] || "");
       
    90     this.origin = origin;
       
    91     this.requestedProtocols = protocols;
       
    92     this.cookie = cookie;
       
    93     // if present and not the empty string, headers MUST end with \r\n
       
    94     // headers should be zero or more complete lines, for example
       
    95     // "Header1: xxx\r\nHeader2: yyyy\r\n"
       
    96     this.headers = headers;
       
    97     
       
    98     if (proxyHost != null && proxyPort != 0){
       
    99       if (scheme == "wss") {
       
   100         fatal("wss with proxy is not supported");
       
   101       }
       
   102       var proxySocket:RFC2817Socket = new RFC2817Socket();
       
   103       proxySocket.setProxyInfo(proxyHost, proxyPort);
       
   104       proxySocket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
       
   105       rawSocket = socket = proxySocket;
       
   106     } else {
       
   107       rawSocket = new Socket();
       
   108       if (scheme == "wss") {
       
   109         tlsConfig= new TLSConfig(TLSEngine.CLIENT,
       
   110             null, null, null, null, null,
       
   111             TLSSecurityParameters.PROTOCOL_VERSION);
       
   112         tlsConfig.trustAllCertificates = true;
       
   113         tlsConfig.ignoreCommonNameMismatch = true;
       
   114         tlsSocket = new TLSSocket();
       
   115         tlsSocket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
       
   116         socket = tlsSocket;
       
   117       } else {
       
   118         rawSocket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
       
   119         socket = rawSocket;
       
   120       }
       
   121     }
       
   122     rawSocket.addEventListener(Event.CLOSE, onSocketClose);
       
   123     rawSocket.addEventListener(Event.CONNECT, onSocketConnect);
       
   124     rawSocket.addEventListener(IOErrorEvent.IO_ERROR, onSocketIoError);
       
   125     rawSocket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSocketSecurityError);
       
   126     rawSocket.connect(host, port);
       
   127   }
       
   128   
       
   129   /**
       
   130    * @return  This WebSocket's ID.
       
   131    */
       
   132   public function getId():int {
       
   133     return this.id;
       
   134   }
       
   135   
       
   136   /**
       
   137    * @return this WebSocket's readyState.
       
   138    */
       
   139   public function getReadyState():int {
       
   140     return this.readyState;
       
   141   }
       
   142 
       
   143   public function getAcceptedProtocol():String {
       
   144     return this.acceptedProtocol;
       
   145   }
       
   146   
       
   147   public function send(encData:String):int {
       
   148     var data:String;
       
   149     try {
       
   150       data = decodeURIComponent(encData);
       
   151     } catch (ex:URIError) {
       
   152       logger.error("SYNTAX_ERR: URIError in send()");
       
   153       return 0;
       
   154     }
       
   155     logger.log("send: " + data);
       
   156     var dataBytes:ByteArray = new ByteArray();
       
   157     dataBytes.writeUTFBytes(data);
       
   158     if (readyState == OPEN) {
       
   159       // TODO: binary API support
       
   160       var frame:WebSocketFrame = new WebSocketFrame();
       
   161       frame.opcode = OPCODE_TEXT;
       
   162       frame.payload = dataBytes;
       
   163       if (sendFrame(frame)) {
       
   164         return -1;
       
   165       } else {
       
   166         return dataBytes.length;
       
   167       }
       
   168     } else if (readyState == CLOSING || readyState == CLOSED) {
       
   169       return dataBytes.length;
       
   170     } else {
       
   171       fatal("invalid state");
       
   172       return 0;
       
   173     }
       
   174   }
       
   175   
       
   176   public function close(
       
   177       code:int = STATUS_NO_CODE, reason:String = "", origin:String = "client"):void {
       
   178     if (code != STATUS_NORMAL_CLOSURE &&
       
   179         code != STATUS_NO_CODE &&
       
   180         code != STATUS_CONNECTION_ERROR) {
       
   181       logger.error(StringUtil.substitute(
       
   182           "Fail connection by {0}: code={1} reason={2}", origin, code, reason));
       
   183     }
       
   184     var closeConnection:Boolean =
       
   185         code == STATUS_CONNECTION_ERROR || origin == "server";
       
   186     try {
       
   187       if (readyState == OPEN && code != STATUS_CONNECTION_ERROR) {
       
   188         var frame:WebSocketFrame = new WebSocketFrame();
       
   189         frame.opcode = OPCODE_CLOSE;
       
   190         frame.payload = new ByteArray();
       
   191         if (origin == "client" && code != STATUS_NO_CODE) {
       
   192           frame.payload.writeShort(code);
       
   193           frame.payload.writeUTFBytes(reason);
       
   194         }
       
   195         sendFrame(frame);
       
   196       }
       
   197       if (closeConnection) {
       
   198         socket.close();
       
   199       }
       
   200     } catch (ex:Error) {
       
   201       logger.error("Error: " + ex.message);
       
   202     }
       
   203     if (closeConnection) {
       
   204       logger.log("closed");
       
   205       var fireErrorEvent:Boolean = readyState != CONNECTING && code == STATUS_CONNECTION_ERROR;
       
   206       readyState = CLOSED;
       
   207       if (fireErrorEvent) {
       
   208         dispatchEvent(new WebSocketEvent("error"));
       
   209       } else {
       
   210         var wasClean:Boolean = code != STATUS_CLOSED_ABNORMALLY && code != STATUS_CONNECTION_ERROR;
       
   211         var eventCode:int = code == STATUS_CONNECTION_ERROR ? STATUS_CLOSED_ABNORMALLY : code;
       
   212         dispatchCloseEvent(wasClean, eventCode, reason);
       
   213       }
       
   214     } else {
       
   215       logger.log("closing");
       
   216       readyState = CLOSING;
       
   217     }
       
   218   }
       
   219   
       
   220   private function onSocketConnect(event:Event):void {
       
   221     logger.log("connected");
       
   222 
       
   223     if (scheme == "wss") {
       
   224       logger.log("starting SSL/TLS");
       
   225       tlsSocket.startTLS(rawSocket, host, tlsConfig);
       
   226     }
       
   227     
       
   228     var defaultPort:int = scheme == "wss" ? 443 : 80;
       
   229     var hostValue:String = host + (port == defaultPort ? "" : ":" + port);
       
   230     var key:String = generateKey();
       
   231 
       
   232     SHA1.b64pad = "=";
       
   233     expectedDigest = SHA1.b64_sha1(key + WEB_SOCKET_GUID);
       
   234 
       
   235     var opt:String = "";
       
   236     if (requestedProtocols.length > 0) {
       
   237       opt += "Sec-WebSocket-Protocol: " + requestedProtocols.join(",") + "\r\n";
       
   238     }
       
   239     // if caller passes additional headers they must end with "\r\n"
       
   240     if (headers) opt += headers;
       
   241     
       
   242     var req:String = StringUtil.substitute(
       
   243       "GET {0} HTTP/1.1\r\n" +
       
   244       "Host: {1}\r\n" +
       
   245       "Upgrade: websocket\r\n" +
       
   246       "Connection: Upgrade\r\n" +
       
   247       "Sec-WebSocket-Key: {2}\r\n" +
       
   248       "Sec-WebSocket-Origin: {3}\r\n" +
       
   249       "Sec-WebSocket-Version: 8\r\n" +
       
   250       "Cookie: {4}\r\n" +
       
   251       "{5}" +
       
   252       "\r\n",
       
   253       path, hostValue, key, origin, cookie, opt);
       
   254     logger.log("request header:\n" + req);
       
   255     socket.writeUTFBytes(req);
       
   256     socket.flush();
       
   257   }
       
   258 
       
   259   private function onSocketClose(event:Event):void {
       
   260     logger.log("closed");
       
   261     readyState = CLOSED;
       
   262     dispatchCloseEvent(false, STATUS_CLOSED_ABNORMALLY, "");
       
   263   }
       
   264 
       
   265   private function onSocketIoError(event:IOErrorEvent):void {
       
   266     var message:String;
       
   267     if (readyState == CONNECTING) {
       
   268       message = "cannot connect to Web Socket server at " + url + " (IoError: " + event.text + ")";
       
   269     } else {
       
   270       message =
       
   271           "error communicating with Web Socket server at " + url +
       
   272           " (IoError: " + event.text + ")";
       
   273     }
       
   274     onConnectionError(message);
       
   275   }
       
   276 
       
   277   private function onSocketSecurityError(event:SecurityErrorEvent):void {
       
   278     var message:String;
       
   279     if (readyState == CONNECTING) {
       
   280       message =
       
   281           "cannot connect to Web Socket server at " + url + " (SecurityError: " + event.text + ")\n" +
       
   282           "make sure the server is running and Flash socket policy file is correctly placed";
       
   283     } else {
       
   284       message =
       
   285           "error communicating with Web Socket server at " + url +
       
   286           " (SecurityError: " + event.text + ")";
       
   287     }
       
   288     onConnectionError(message);
       
   289   }
       
   290   
       
   291   private function onConnectionError(message:String):void {
       
   292     if (readyState == CLOSED) return;
       
   293     logger.error(message);
       
   294     close(STATUS_CONNECTION_ERROR);
       
   295   }
       
   296 
       
   297   private function onSocketData(event:ProgressEvent):void {
       
   298     var pos:int = buffer.length;
       
   299     socket.readBytes(buffer, pos);
       
   300     for (; pos < buffer.length; ++pos) {
       
   301       if (headerState < 4) {
       
   302         // try to find "\r\n\r\n"
       
   303         if ((headerState == 0 || headerState == 2) && buffer[pos] == 0x0d) {
       
   304           ++headerState;
       
   305         } else if ((headerState == 1 || headerState == 3) && buffer[pos] == 0x0a) {
       
   306           ++headerState;
       
   307         } else {
       
   308           headerState = 0;
       
   309         }
       
   310         if (headerState == 4) {
       
   311           var headerStr:String = readUTFBytes(buffer, 0, pos + 1);
       
   312           logger.log("response header:\n" + headerStr);
       
   313           if (!validateHandshake(headerStr)) return;
       
   314           removeBufferBefore(pos + 1);
       
   315           pos = -1;
       
   316           readyState = OPEN;
       
   317           this.dispatchEvent(new WebSocketEvent("open"));
       
   318         }
       
   319       } else {
       
   320         var frame:WebSocketFrame = parseFrame();
       
   321         if (frame) {
       
   322           removeBufferBefore(frame.length);
       
   323           pos = -1;
       
   324           if (frame.rsv != 0) {
       
   325             close(1002, "RSV must be 0.");
       
   326           } else if (frame.opcode >= 0x08 && frame.opcode <= 0x0f && frame.payload.length >= 126) {
       
   327             close(1004, "Payload of control frame must be less than 126 bytes.");
       
   328           } else {
       
   329             switch (frame.opcode) {
       
   330               case OPCODE_CONTINUATION:
       
   331                 close(1003, "Received continuation frame, which is not implemented.");
       
   332                 break;
       
   333               case OPCODE_TEXT:
       
   334                 var data:String = readUTFBytes(frame.payload, 0, frame.payload.length);
       
   335                 try {
       
   336                   this.dispatchEvent(new WebSocketEvent("message", encodeURIComponent(data)));
       
   337                 } catch (ex:URIError) {
       
   338                   close(1007, "URIError while encoding the received data.");
       
   339                 }
       
   340                 break;
       
   341               case OPCODE_BINARY:
       
   342                 close(1003, "Received binary data, which is not supported.");
       
   343                 break;
       
   344               case OPCODE_CLOSE:
       
   345                 // Extracts code and reason string.
       
   346                 var code:int = STATUS_NO_CODE;
       
   347                 var reason:String = "";
       
   348                 if (frame.payload.length >= 2) {
       
   349                   frame.payload.endian = Endian.BIG_ENDIAN;
       
   350                   frame.payload.position = 0;
       
   351                   code = frame.payload.readUnsignedShort();
       
   352                   reason = readUTFBytes(frame.payload, 2, frame.payload.length - 2);
       
   353                 }
       
   354                 logger.log("received closing frame");
       
   355                 close(code, reason, "server");
       
   356                 break;
       
   357               case OPCODE_PING:
       
   358                 sendPong(frame.payload);
       
   359                 break;
       
   360               case OPCODE_PONG:
       
   361                 break;
       
   362               default:
       
   363                 close(1002, "Received unknown opcode: " + frame.opcode);
       
   364                 break;
       
   365             }
       
   366           }
       
   367         }
       
   368       }
       
   369     }
       
   370   }
       
   371   
       
   372   private function validateHandshake(headerStr:String):Boolean {
       
   373     var lines:Array = headerStr.split(/\r\n/);
       
   374     if (!lines[0].match(/^HTTP\/1.1 101 /)) {
       
   375       onConnectionError("bad response: " + lines[0]);
       
   376       return false;
       
   377     }
       
   378     var header:Object = {};
       
   379     var lowerHeader:Object = {};
       
   380     for (var i:int = 1; i < lines.length; ++i) {
       
   381       if (lines[i].length == 0) continue;
       
   382       var m:Array = lines[i].match(/^(\S+): (.*)$/);
       
   383       if (!m) {
       
   384         onConnectionError("failed to parse response header line: " + lines[i]);
       
   385         return false;
       
   386       }
       
   387       header[m[1].toLowerCase()] = m[2];
       
   388       lowerHeader[m[1].toLowerCase()] = m[2].toLowerCase();
       
   389     }
       
   390     if (lowerHeader["upgrade"] != "websocket") {
       
   391       onConnectionError("invalid Upgrade: " + header["Upgrade"]);
       
   392       return false;
       
   393     }
       
   394     if (lowerHeader["connection"] != "upgrade") {
       
   395       onConnectionError("invalid Connection: " + header["Connection"]);
       
   396       return false;
       
   397     }
       
   398     if (!lowerHeader["sec-websocket-accept"]) {
       
   399       onConnectionError(
       
   400         "The WebSocket server speaks old WebSocket protocol, " +
       
   401         "which is not supported by web-socket-js. " +
       
   402         "It requires WebSocket protocol HyBi 10. " +
       
   403         "Try newer version of the server if available.");
       
   404       return false;
       
   405     }
       
   406     var replyDigest:String = header["sec-websocket-accept"]
       
   407     if (replyDigest != expectedDigest) {
       
   408       onConnectionError("digest doesn't match: " + replyDigest + " != " + expectedDigest);
       
   409       return false;
       
   410     }
       
   411     if (requestedProtocols.length > 0) {
       
   412       acceptedProtocol = header["sec-websocket-protocol"];
       
   413       if (requestedProtocols.indexOf(acceptedProtocol) < 0) {
       
   414         onConnectionError("protocol doesn't match: '" +
       
   415           acceptedProtocol + "' not in '" + requestedProtocols.join(",") + "'");
       
   416         return false;
       
   417       }
       
   418     }
       
   419     return true;
       
   420   }
       
   421 
       
   422   private function sendPong(payload:ByteArray):Boolean {
       
   423     var frame:WebSocketFrame = new WebSocketFrame();
       
   424     frame.opcode = OPCODE_PONG;
       
   425     frame.payload = payload;
       
   426     return sendFrame(frame);
       
   427   }
       
   428   
       
   429   private function sendFrame(frame:WebSocketFrame):Boolean {
       
   430     
       
   431     var plength:uint = frame.payload.length;
       
   432     
       
   433     // Generates a mask.
       
   434     var mask:ByteArray = new ByteArray();
       
   435     for (var i:int = 0; i < 4; i++) {
       
   436       mask.writeByte(randomInt(0, 255));
       
   437     }
       
   438     
       
   439     var header:ByteArray = new ByteArray();
       
   440     // FIN + RSV + opcode
       
   441     header.writeByte((frame.fin ? 0x80 : 0x00) | (frame.rsv << 4) | frame.opcode);
       
   442     if (plength <= 125) {
       
   443       header.writeByte(0x80 | plength);  // Masked + length
       
   444     } else if (plength > 125 && plength < 65536) {
       
   445       header.writeByte(0x80 | 126);  // Masked + 126
       
   446       header.writeShort(plength);
       
   447     } else if (plength >= 65536 && plength < 4294967296) {
       
   448       header.writeByte(0x80 | 127);  // Masked + 127
       
   449       header.writeUnsignedInt(0);  // zero high order bits
       
   450       header.writeUnsignedInt(plength);
       
   451     } else {
       
   452       fatal("Send frame size too large");
       
   453     }
       
   454     header.writeBytes(mask);
       
   455     
       
   456     var maskedPayload:ByteArray = new ByteArray();
       
   457     maskedPayload.length = frame.payload.length;
       
   458     for (i = 0; i < frame.payload.length; i++) {
       
   459       maskedPayload[i] = mask[i % 4] ^ frame.payload[i];
       
   460     }
       
   461 
       
   462     try {
       
   463       socket.writeBytes(header);
       
   464       socket.writeBytes(maskedPayload);
       
   465       socket.flush();
       
   466     } catch (ex:IOError) {
       
   467       logger.error("IOError while sending frame");
       
   468       // TODO Fire close event if it hasn't
       
   469       readyState = CLOSED;
       
   470       return false;
       
   471     }
       
   472     return true;
       
   473     
       
   474   }
       
   475 
       
   476   private function parseFrame():WebSocketFrame {
       
   477     
       
   478     var frame:WebSocketFrame = new WebSocketFrame();
       
   479     var hlength:uint = 0;
       
   480     var plength:uint = 0;
       
   481     
       
   482     hlength = 2;
       
   483     if (buffer.length < hlength) {
       
   484       return null;
       
   485     }
       
   486 
       
   487     frame.fin = (buffer[0] & 0x80) != 0;
       
   488     frame.rsv = (buffer[0] & 0x70) >> 4;
       
   489     frame.opcode  = buffer[0] & 0x0f;
       
   490     plength = buffer[1] & 0x7f;
       
   491 
       
   492     if (plength == 126) {
       
   493       
       
   494       hlength = 4;
       
   495       if (buffer.length < hlength) {
       
   496         return null;
       
   497       }
       
   498       buffer.endian = Endian.BIG_ENDIAN;
       
   499       buffer.position = 2;
       
   500       plength = buffer.readUnsignedShort();
       
   501       
       
   502     } else if (plength == 127) {
       
   503       
       
   504       hlength = 10;
       
   505       if (buffer.length < hlength) {
       
   506         return null;
       
   507       }
       
   508       buffer.endian = Endian.BIG_ENDIAN;
       
   509       buffer.position = 2;
       
   510       // Protocol allows 64-bit length, but we only handle 32-bit
       
   511       var big:uint = buffer.readUnsignedInt(); // Skip high 32-bits
       
   512       plength = buffer.readUnsignedInt(); // Low 32-bits
       
   513       if (big != 0) {
       
   514         fatal("Frame length exceeds 4294967295. Bailing out!");
       
   515         return null;
       
   516       }
       
   517       
       
   518     }
       
   519 
       
   520     if (buffer.length < hlength + plength) {
       
   521       return null;
       
   522     }
       
   523     
       
   524     frame.length = hlength + plength;
       
   525     frame.payload = new ByteArray();
       
   526     buffer.position = hlength;
       
   527     buffer.readBytes(frame.payload, 0, plength);
       
   528     return frame;
       
   529     
       
   530   }
       
   531   
       
   532   private function dispatchCloseEvent(wasClean:Boolean, code:int, reason:String):void {
       
   533     var event:WebSocketEvent = new WebSocketEvent("close");
       
   534     event.wasClean = wasClean;
       
   535     event.code = code;
       
   536     event.reason = reason;
       
   537     dispatchEvent(event);
       
   538   }
       
   539   
       
   540   private function removeBufferBefore(pos:int):void {
       
   541     if (pos == 0) return;
       
   542     var nextBuffer:ByteArray = new ByteArray();
       
   543     buffer.position = pos;
       
   544     buffer.readBytes(nextBuffer);
       
   545     buffer = nextBuffer;
       
   546   }
       
   547   
       
   548   private function generateKey():String {
       
   549     var vals:ByteArray = new ByteArray();
       
   550     vals.length = 16;
       
   551     for (var i:int = 0; i < vals.length; ++i) {
       
   552         vals[i] = randomInt(0, 127);
       
   553     }
       
   554     base64Encoder.reset();
       
   555     base64Encoder.encodeBytes(vals);
       
   556     return base64Encoder.toString();
       
   557   }
       
   558   
       
   559   private function readUTFBytes(buffer:ByteArray, start:int, numBytes:int):String {
       
   560     buffer.position = start;
       
   561     var data:String = "";
       
   562     for(var i:int = start; i < start + numBytes; ++i) {
       
   563       // Workaround of a bug of ByteArray#readUTFBytes() that bytes after "\x00" is discarded.
       
   564       if (buffer[i] == 0x00) {
       
   565         data += buffer.readUTFBytes(i - buffer.position) + "\x00";
       
   566         buffer.position = i + 1;
       
   567       }
       
   568     }
       
   569     data += buffer.readUTFBytes(start + numBytes - buffer.position);
       
   570     return data;
       
   571   }
       
   572   
       
   573   private function randomInt(min:uint, max:uint):uint {
       
   574     return min + Math.floor(Math.random() * (Number(max) - min + 1));
       
   575   }
       
   576   
       
   577   private function fatal(message:String):void {
       
   578     logger.error(message);
       
   579     throw message;
       
   580   }
       
   581 
       
   582 }
       
   583 
       
   584 }