Permalink
Browse files

Merge pull request #3 from rejemy/master

RFC6455 conformance for AS3WebSocket. Closes GH Issue #2
  • Loading branch information...
2 parents a178e60 + 1305aa4 commit 183e7e2e506ad60f1cfe330bc8b063787324139f @theturtle32 committed Apr 10, 2012
@@ -3,6 +3,7 @@ package com.worlize.websocket
import com.adobe.crypto.SHA1;
import com.adobe.net.URI;
import com.adobe.net.URIEncodingBitmap;
+ import com.adobe.utils.StringUtil;
import com.hurlant.crypto.tls.TLSConfig;
import com.hurlant.crypto.tls.TLSEngine;
import com.hurlant.crypto.tls.TLSSecurityParameters;
@@ -40,12 +41,14 @@ package com.worlize.websocket
private var _readyState:int;
private var _uri:URI;
- private var _protocol:String;
+ private var _protocols:Array;
+ private var _serverProtocol:String;
private var _host:String;
private var _port:uint;
private var _resource:String;
private var _secure:Boolean;
private var _origin:String;
+ private var _pseudoMasking:Boolean = false;
private var rawSocket:Socket;
private var socket:Socket;
@@ -84,11 +87,22 @@ package com.worlize.websocket
trace(text);
};
- public function WebSocket(uri:String, origin:String, protocol:String = null, timeout:uint = 10000)
+ public function WebSocket(uri:String, origin:String, protocols:* = null, timeout:uint = 10000)
{
super(null);
_uri = new URI(uri);
- _protocol = protocol;
+
+ if (protocols is String) {
+ _protocols = [protocols];
+ }
+ else {
+ _protocols = protocols;
+ }
+ if (_protocols) {
+ for (var i:int=0; i<_protocols.length; i++) {
+ _protocols[i] = StringUtil.trim(_protocols[i]);
+ }
+ }
_origin = origin;
this.timeout = timeout;
this.handshakeTimeout = timeout;
@@ -138,18 +152,22 @@ package com.worlize.websocket
}
private function validateProtocol():void {
- if (_protocol) {
+ if (_protocols) {
var separators:Array = [
"(", ")", "<", ">", "@",
",", ";", ":", "\\", "\"",
"/", "[", "]", "?", "=",
"{", "}", " ", String.fromCharCode(9)
];
- for (var i:int = 0; i < _protocol.length; i++) {
- var charCode:int = _protocol.charCodeAt(i);
- var char:String = _protocol.charAt(i);
- if (charCode < 0x21 || charCode > 0x7E || separators.indexOf(char) !== -1) {
- throw new WebSocketError("Illegal character '" + String.fromCharCode(char) + "' in subprotocol.");
+
+ for (var p:int = 0; p < _protocols.length; p++) {
+ var protocol:String = _protocols[p];
+ for (var i:int = 0; i < protocol.length; i++) {
+ var charCode:int = protocol.charCodeAt(i);
+ var char:String = protocol.charAt(i);
+ if (charCode < 0x21 || charCode > 0x7E || separators.indexOf(char) !== -1) {
+ throw new WebSocketError("Illegal character '" + String.fromCharCode(char) + "' in subprotocol.");
+ }
}
}
}
@@ -227,8 +245,12 @@ package com.worlize.websocket
return uri;
}
- public function get protocol():String {
- return _protocol;
+ public function get protocols():Array {
+ return _protocols;
+ }
+
+ public function get serverProtocol():String {
+ return _serverProtocol;
}
public function get host():String {
@@ -251,6 +273,18 @@ package com.worlize.websocket
return readyState === WebSocketState.OPEN;
}
+ // Pseudo masking is useful for speeding up wbesocket usage in a controlled environment,
+ // such as a self-contained AIR app for mobile where the client can be resonably sure of
+ // not intending to screw up proxies by confusing them with HTTP commands in the frame body
+ // Probably not a good idea to enable if being used on the web in general cases.
+ public function set pseudoMasking(val:Boolean):void {
+ _pseudoMasking = val;
+ }
+
+ public function get pseudoMasking():Boolean {
+ return _pseudoMasking;
+ }
+
private function verifyConnectionForSend():void {
if (_readyState === WebSocketState.CONNECTING) {
throw new WebSocketError("Invalid State: Cannot send data before connected.");
@@ -330,6 +364,7 @@ package com.worlize.websocket
private function sendFrame(frame:WebSocketFrame, force:Boolean = false):void {
frame.mask = true;
+ frame.pseudoMask = _pseudoMasking;
var buffer:ByteArray = new ByteArray();
frame.send(buffer);
sendData(buffer);
@@ -625,10 +660,13 @@ package com.worlize.websocket
text += "Upgrade: websocket\r\n";
text += "Connection: Upgrade\r\n";
text += "Sec-WebSocket-Key: " + base64nonce + "\r\n";
- text += "Sec-Websocket-Origin: " + _origin + "\r\n";
- text += "Sec-WebSocket-Version: 8\r\n";
- if (protocol) {
- text += "Sec-WebSocket-Protocol: " + protocol + "\r\n";
+ if (_origin) {
+ text += "Origin: " + _origin + "\r\n";
+ }
+ text += "Sec-WebSocket-Version: 13\r\n";
+ if (_protocols) {
+ var protosList:String = _protocols.join(", ");
+ text += "Sec-WebSocket-Protocol: " + protosList + "\r\n";
}
// TODO: Handle Extensions
text += "\r\n";
@@ -786,6 +824,15 @@ package com.worlize.websocket
keyValidated = true;
}
}
+ else if(lcName === 'sec-websocket-protocol') {
+ if (_protocols) {
+ for each (var protocol:String in _protocols) {
+ if (protocol == header.value) {
+ _serverProtocol = protocol;
+ }
+ }
+ }
+ }
}
}
catch(e:Error) {
@@ -806,6 +853,11 @@ package com.worlize.websocket
return;
}
+ if (_protocols && !_serverProtocol) {
+ failHandshake("The server can not respond in any of our requested protocols");
+ return;
+ }
+
if (debug) {
logger("Server Extensions: " + serverExtensions.join(' | '));
}
@@ -2,11 +2,19 @@ package com.worlize.websocket
{
public final class WebSocketCloseStatus
{
- // http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-06#section-7.4
+ // http://tools.ietf.org/html/rfc6455#section-7.4
public static const NORMAL:int = 1000;
public static const GOING_AWAY:int = 1001;
public static const PROTOCOL_ERROR:int = 1002;
public static const UNPROCESSABLE_INPUT:int = 1003;
- public static const MESSAGE_TOO_LARGE:int = 1004;
+ public static const UNDEFINED:int = 1004;
+ public static const NO_CODE:int = 1005;
+ public static const NO_CLOSE:int = 1006;
+ public static const BAD_PAYLOAD:int = 1007;
+ public static const POLICY_VIOLATION:int = 1008;
+ public static const MESSAGE_TOO_LARGE:int = 1009;
+ public static const REQUIRED_EXTENSION:int = 1010;
+ public static const SERVER_ERROR:int = 1011;
+ public static const FAILED_TLS_HANDSHAKE:int = 1015;
}
}
@@ -30,5 +30,6 @@ package com.worlize.websocket
// for an acknowledgement to come back before giving up and just
// closing the socket.
public var closeTimeout:uint = 5000;
+
}
}
@@ -15,6 +15,7 @@ package com.worlize.websocket
public var rsv3:Boolean;
public var opcode:int;
public var mask:Boolean;
+ public var pseudoMask:Boolean;
private var _length:int;
public var binaryPayload:ByteArray;
public var closeStatus:int;
@@ -30,6 +31,8 @@ package com.worlize.websocket
private static const COMPLETE:int = 4;
private var parseState:int = 0; // Initialize as NEW_FRAME
+ private static var _tempMaskBytes:Vector.<uint> = new Vector.<uint>(4);
+
public function get length():int {
return _length;
}
@@ -139,17 +142,15 @@ package com.worlize.websocket
}
public function send(output:IDataOutput):void {
- var frameHeader:ByteArray = new ByteArray();
- frameHeader.endian = Endian.BIG_ENDIAN;
- if (this.mask) {
+ var maskKey:uint;
+ if (this.mask && !this.pseudoMask) {
// Generate a mask key
- var maskKey:uint = Math.ceil(Math.random()*0xFFFFFFFF);
- var maskBytes:Vector.<uint> = new Vector.<uint>(4);
- maskBytes[0] = (maskKey >> 24) & 0xFF;
- maskBytes[1] = (maskKey >> 16) & 0xFF;
- maskBytes[2] = (maskKey >> 8) & 0xFF;
- maskBytes[3] = maskKey & 0xFF;
+ maskKey = Math.ceil(Math.random()*0xFFFFFFFF);
+ _tempMaskBytes[0] = (maskKey >> 24) & 0xFF;
+ _tempMaskBytes[1] = (maskKey >> 16) & 0xFF;
+ _tempMaskBytes[2] = (maskKey >> 8) & 0xFF;
+ _tempMaskBytes[3] = maskKey & 0xFF;
}
var data:ByteArray;
@@ -222,14 +223,28 @@ package com.worlize.websocket
}
if (this.mask) {
- // write the mask key to the output
- output.writeUnsignedInt(maskKey);
- // Mask and send the payload
- var i:uint,
- j:int = 0;
- for (i = 0; i < _length; i ++) {
- output.writeByte(data.readByte() ^ maskBytes[j]);
- j = (j + 1) & 3;
+
+ if (this.pseudoMask) {
+ output.writeUnsignedInt(0);
+ output.writeBytes(data, 0, data.length);
+ }
+ else {
+ // write the mask key to the output
+ output.writeUnsignedInt(maskKey);
+ // Mask and send the payload
+
+ var j:int = 0;
+
+ var remaining:uint = data.bytesAvailable;
+ while (remaining >= 4) {
+ output.writeUnsignedInt(data.readUnsignedInt() ^ maskKey);
+ remaining -= 4;
+ }
+ while (remaining > 0) {
+ output.writeByte(data.readByte() ^ _tempMaskBytes[j]);
+ j += 1;
+ remaining -= 1;
+ }
}
}
else {
@@ -6,12 +6,14 @@ package com.worlize.websocket
public static const CONTINUATION:int = 0x00;
public static const TEXT_FRAME:int = 0x01;
public static const BINARY_FRAME:int = 0x02;
- // 0x03 - 0x07 = Reserved for further control frames
+ public static const EXT_DATA:int = 0x03;
+ // 0x04 - 0x07 = Reserved for further control frames
// Control opcodes
public static const CONNECTION_CLOSE:int = 0x08;
public static const PING:int = 0x09;
public static const PONG:int = 0x0A;
- // 0x0B - 0x0F = Reserved for further control frames
+ public static const EXT_CONTROL:int = 0x0B;
+ // 0x0C - 0x0F = Reserved for further control frames
}
}
View
@@ -1,11 +1,11 @@
ActionScript 3 WebSocket Client
===============================
-*This is an AS3 implementation of a client library of the WebSocket protocol, as specified in the -10 draft.*
+*This is an AS3 implementation of a client library of the WebSocket protocol, as specified in the rfc6455 standard.*
Explanation
-----------
-THIS CLIENT WILL NOT WORK with draft-75 or draft-76/-00 servers that are deployed on the internet. It is only for the most recent -10 draft. Once the next draft is released I will update this client to work only with that version, and so on, until the final version of the protocol is ratified by the IETF. I will keep a version tracking each of the IETF drafts in its own branch, with master tracking the latest.
+THIS CLIENT WILL NOT WORK with draft-75 or draft-76/-00 servers that are deployed on the internet. It is only for the most recent rfc6455 standard. I will keep a version tracking each of the IETF drafts in its own branch, with master tracking the latest.
I intend to keep this library updated to the latest draft of the IETF WebSocket protocol when new versions are released. I built this library because I wanted to be able to make use of the latest draft of the protocol, but no browser implements it yet.
@@ -24,7 +24,7 @@ Download
Features
--------
-- Based on -10 draft of the WebSocket protocol
+- Based on the rfc6455 standard WebSocket protocol
- wss:// TLS support w/ hurlant as3crypto library
- Learn more here: [as3crypto on Google Code](http://code.google.com/p/as3crypto/)
- Can send and receive fragmented messages

0 comments on commit 183e7e2

Please sign in to comment.