Index: sdk/lib/io/websocket_impl.dart |
diff --git a/sdk/lib/io/websocket_impl.dart b/sdk/lib/io/websocket_impl.dart |
index 48eeca131d3f6ce888b612521836f4200e59a72b..69a7f0eb69f36793cff1db27e937694c2ec5d299 100644 |
--- a/sdk/lib/io/websocket_impl.dart |
+++ b/sdk/lib/io/websocket_impl.dart |
@@ -6,10 +6,11 @@ part of dart.io; |
const String _webSocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; |
+// Matches _WebSocketOpcode. |
class _WebSocketMessageType { |
static const int NONE = 0; |
- static const int BINARY = 1; |
- static const int TEXT = 2; |
+ static const int TEXT = 1; |
+ static const int BINARY = 2; |
} |
@@ -51,31 +52,26 @@ class _WebSocketProtocolTransformer implements StreamTransformer, EventSink { |
static const int CLOSED = 5; |
static const int FAILURE = 6; |
- int _state; |
- bool _fin; |
- int _opcode; |
- int _len; |
- bool _masked; |
- int _maskingKey; |
- int _remainingLenBytes; |
- int _remainingMaskingKeyBytes; |
- int _remainingPayloadBytes; |
- int _unmaskingIndex; |
- |
- int _currentMessageType; |
- List<int> _controlPayload; |
- StreamController _controller; |
- |
+ int _state = START; |
+ bool _fin = false; |
+ int _opcode = -1; |
+ int _len = -1; |
+ bool _masked = false; |
+ int _remainingLenBytes = -1; |
+ int _remainingMaskingKeyBytes = 4; |
+ int _remainingPayloadBytes = -1; |
+ int _unmaskingIndex = 0; |
+ int _currentMessageType = _WebSocketMessageType.NONE; |
int closeCode = WebSocketStatus.NO_STATUS_RECEIVED; |
String closeReason = ""; |
- bool _serverSide; |
EventSink _eventSink; |
- _WebSocketProtocolTransformer([this._serverSide = false]) { |
- _prepareForNextFrame(); |
- _currentMessageType = _WebSocketMessageType.NONE; |
- } |
+ final bool _serverSide; |
+ final List _maskingBytes = new List(4); |
+ final BytesBuilder _payload = new BytesBuilder(); |
+ |
+ _WebSocketProtocolTransformer([this._serverSide = false]); |
Stream bind(Stream stream) { |
return new Stream.eventTransformed( |
@@ -101,163 +97,106 @@ class _WebSocketProtocolTransformer implements StreamTransformer, EventSink { |
int count = buffer.length; |
int index = 0; |
int lastIndex = count; |
- try { |
- if (_state == CLOSED) { |
- throw new WebSocketException("Data on closed connection"); |
- } |
- if (_state == FAILURE) { |
- throw new WebSocketException("Data on failed connection"); |
- } |
- while ((index < lastIndex) && _state != CLOSED && _state != FAILURE) { |
- int byte = buffer[index]; |
- switch (_state) { |
- case START: |
- _fin = (byte & 0x80) != 0; |
- if ((byte & 0x70) != 0) { |
- // The RSV1, RSV2 bits RSV3 most be all zero. |
- throw new WebSocketException("Protocol error"); |
- } |
- _opcode = (byte & 0xF); |
- switch (_opcode) { |
- case _WebSocketOpcode.CONTINUATION: |
+ if (_state == CLOSED) { |
+ throw new WebSocketException("Data on closed connection"); |
+ } |
+ if (_state == FAILURE) { |
+ throw new WebSocketException("Data on failed connection"); |
+ } |
+ while ((index < lastIndex) && _state != CLOSED && _state != FAILURE) { |
+ int byte = buffer[index]; |
+ if (_state <= LEN_REST) { |
+ if (_state == START) { |
+ _fin = (byte & 0x80) != 0; |
+ if ((byte & 0x70) != 0) { |
+ // The RSV1, RSV2 bits RSV3 must be all zero. |
+ throw new WebSocketException("Protocol error"); |
+ } |
+ _opcode = (byte & 0xF); |
+ if (_opcode <= _WebSocketOpcode.BINARY) { |
+ if (_opcode == _WebSocketOpcode.CONTINUATION) { |
if (_currentMessageType == _WebSocketMessageType.NONE) { |
throw new WebSocketException("Protocol error"); |
} |
- break; |
- |
- case _WebSocketOpcode.TEXT: |
- if (_currentMessageType != _WebSocketMessageType.NONE) { |
- throw new WebSocketException("Protocol error"); |
- } |
- _currentMessageType = _WebSocketMessageType.TEXT; |
- _controller = new StreamController(sync: true); |
- _controller.stream |
- .transform(UTF8.decoder) |
- .fold(new StringBuffer(), (buffer, str) => buffer..write(str)) |
- .then((buffer) { |
- _eventSink.add(buffer.toString()); |
- }, onError: _eventSink.addError); |
- break; |
- |
- case _WebSocketOpcode.BINARY: |
+ } else { |
+ assert(_opcode == _WebSocketOpcode.TEXT || |
+ _opcode == _WebSocketOpcode.BINARY); |
if (_currentMessageType != _WebSocketMessageType.NONE) { |
throw new WebSocketException("Protocol error"); |
} |
- _currentMessageType = _WebSocketMessageType.BINARY; |
- _controller = new StreamController(sync: true); |
- _controller.stream |
- .fold(new BytesBuilder(), (buffer, data) => buffer..add(data)) |
- .then((buffer) { |
- _eventSink.add(buffer.takeBytes()); |
- }, onError: _eventSink.addError); |
- break; |
- |
- case _WebSocketOpcode.CLOSE: |
- case _WebSocketOpcode.PING: |
- case _WebSocketOpcode.PONG: |
- // Control frames cannot be fragmented. |
- if (!_fin) throw new WebSocketException("Protocol error"); |
- break; |
- |
- default: |
- throw new WebSocketException("Protocol error"); |
- } |
- _state = LEN_FIRST; |
- break; |
- |
- case LEN_FIRST: |
- _masked = (byte & 0x80) != 0; |
- _len = byte & 0x7F; |
- if (_isControlFrame() && _len > 125) { |
- throw new WebSocketException("Protocol error"); |
- } |
- if (_len < 126) { |
- _lengthDone(); |
- } else if (_len == 126) { |
- _len = 0; |
- _remainingLenBytes = 2; |
- _state = LEN_REST; |
- } else if (_len == 127) { |
- _len = 0; |
- _remainingLenBytes = 8; |
- _state = LEN_REST; |
- } |
- break; |
- |
- case LEN_REST: |
- _len = _len << 8 | byte; |
- _remainingLenBytes--; |
- if (_remainingLenBytes == 0) { |
- _lengthDone(); |
- } |
- break; |
- |
- case MASK: |
- _maskingKey = _maskingKey << 8 | byte; |
- _remainingMaskingKeyBytes--; |
- if (_remainingMaskingKeyBytes == 0) { |
- _maskDone(); |
- } |
- break; |
- |
- case PAYLOAD: |
- // The payload is not handled one byte at a time but in blocks. |
- int payload; |
- if (lastIndex - index <= _remainingPayloadBytes) { |
- payload = lastIndex - index; |
- } else { |
- payload = _remainingPayloadBytes; |
+ _currentMessageType = _opcode; |
} |
- _remainingPayloadBytes -= payload; |
- |
- // Unmask payload if masked. |
- if (_masked) { |
- for (int i = 0; i < payload; i++) { |
- int maskingByte = |
- ((_maskingKey >> ((3 - _unmaskingIndex) * 8)) & 0xFF); |
- buffer[index + i] = buffer[index + i] ^ maskingByte; |
- _unmaskingIndex = (_unmaskingIndex + 1) % 4; |
- } |
+ } else if (_opcode >= _WebSocketOpcode.CLOSE && |
+ _opcode <= _WebSocketOpcode.PONG) { |
+ // Control frames cannot be fragmented. |
+ if (!_fin) throw new WebSocketException("Protocol error"); |
+ } else { |
+ throw new WebSocketException("Protocol error"); |
+ } |
+ _state = LEN_FIRST; |
+ } else if (_state == LEN_FIRST) { |
+ _masked = (byte & 0x80) != 0; |
+ _len = byte & 0x7F; |
+ if (_isControlFrame() && _len > 125) { |
+ throw new WebSocketException("Protocol error"); |
+ } |
+ if (_len == 126) { |
+ _len = 0; |
+ _remainingLenBytes = 2; |
+ _state = LEN_REST; |
+ } else if (_len == 127) { |
+ _len = 0; |
+ _remainingLenBytes = 8; |
+ _state = LEN_REST; |
+ } else { |
+ assert(_len < 126); |
+ _lengthDone(); |
+ } |
+ } else { |
+ assert(_state == LEN_REST); |
+ _len = _len << 8 | byte; |
+ _remainingLenBytes--; |
+ if (_remainingLenBytes == 0) { |
+ _lengthDone(); |
+ } |
+ } |
+ } else { |
+ if (_state == MASK) { |
+ _maskingBytes[4 - _remainingMaskingKeyBytes--] = byte; |
+ if (_remainingMaskingKeyBytes == 0) { |
+ _maskDone(); |
+ } |
+ } else { |
+ assert(_state == PAYLOAD); |
+ // The payload is not handled one byte at a time but in blocks. |
+ int payload = min(lastIndex - index, _remainingPayloadBytes); |
+ _remainingPayloadBytes -= payload; |
+ // Unmask payload if masked. |
+ if (_masked) { |
+ for (int i = index; i < index + payload; i++) { |
+ buffer[i] ^= _maskingBytes[_unmaskingIndex++ & 3]; |
} |
- |
- if (_isControlFrame()) { |
- if (payload > 0) { |
- // Allocate a buffer for collecting the control frame |
- // payload if any. |
- if (_controlPayload == null) { |
- _controlPayload = new List<int>(); |
- } |
- _controlPayload.addAll(buffer.sublist(index, index + payload)); |
- index += payload; |
- } |
- |
- if (_remainingPayloadBytes == 0) { |
- _controlFrameEnd(); |
- } |
- } else { |
- if (_currentMessageType != _WebSocketMessageType.TEXT && |
- _currentMessageType != _WebSocketMessageType.BINARY) { |
- throw new WebSocketException("Protocol error"); |
- } |
- _controller.add( |
- new Uint8List.view(buffer.buffer, index, payload)); |
- index += payload; |
- if (_remainingPayloadBytes == 0) { |
- _messageFrameEnd(); |
- } |
+ } |
+ // Control frame and data frame share _payload builder. |
+ _payload.add(new Uint8List.view(buffer.buffer, index, payload)); |
+ index += payload; |
+ if (_isControlFrame()) { |
+ if (_remainingPayloadBytes == 0) _controlFrameEnd(); |
+ } else { |
+ if (_currentMessageType != _WebSocketMessageType.TEXT && |
+ _currentMessageType != _WebSocketMessageType.BINARY) { |
+ throw new WebSocketException("Protocol error"); |
} |
+ if (_remainingPayloadBytes == 0) _messageFrameEnd(); |
+ } |
- // Hack - as we always do index++ below. |
- index--; |
- break; |
+ // Hack - as we always do index++ below. |
+ index--; |
} |
- |
- // Move to the next byte. |
- index++; |
} |
- } catch (e, stackTrace) { |
- _state = FAILURE; |
- _eventSink.addError(e, stackTrace); |
+ |
+ // Move to the next byte. |
+ index++; |
} |
} |
@@ -267,7 +206,6 @@ class _WebSocketProtocolTransformer implements StreamTransformer, EventSink { |
throw new WebSocketException("Received masked frame from server"); |
} |
_state = MASK; |
- _remainingMaskingKeyBytes = 4; |
} else { |
if (_serverSide) { |
throw new WebSocketException("Received unmasked frame from client"); |
@@ -312,13 +250,12 @@ class _WebSocketProtocolTransformer implements StreamTransformer, EventSink { |
if (_fin) { |
switch (_currentMessageType) { |
case _WebSocketMessageType.TEXT: |
- _controller.close(); |
+ _eventSink.add(UTF8.decode(_payload.takeBytes())); |
break; |
case _WebSocketMessageType.BINARY: |
- _controller.close(); |
+ _eventSink.add(_payload.takeBytes()); |
break; |
} |
- _controller = null; |
_currentMessageType = _WebSocketMessageType.NONE; |
} |
_prepareForNextFrame(); |
@@ -328,16 +265,17 @@ class _WebSocketProtocolTransformer implements StreamTransformer, EventSink { |
switch (_opcode) { |
case _WebSocketOpcode.CLOSE: |
closeCode = WebSocketStatus.NO_STATUS_RECEIVED; |
- if (_controlPayload.length > 0) { |
- if (_controlPayload.length == 1) { |
+ if (_payload.length > 0) { |
+ var bytes = _payload.takeBytes(); |
+ if (bytes.length == 1) { |
throw new WebSocketException("Protocol error"); |
} |
- closeCode = _controlPayload[0] << 8 | _controlPayload[1]; |
+ closeCode = bytes[0] << 8 | bytes[1]; |
if (closeCode == WebSocketStatus.NO_STATUS_RECEIVED) { |
throw new WebSocketException("Protocol error"); |
} |
- if (_controlPayload.length > 2) { |
- closeReason = UTF8.decode(_controlPayload.sublist(2)); |
+ if (bytes.length > 2) { |
+ closeReason = UTF8.decode(bytes.sublist(2)); |
} |
} |
_state = CLOSED; |
@@ -345,11 +283,11 @@ class _WebSocketProtocolTransformer implements StreamTransformer, EventSink { |
break; |
case _WebSocketOpcode.PING: |
- _eventSink.add(new _WebSocketPing(_controlPayload)); |
+ _eventSink.add(new _WebSocketPing(_payload.takeBytes())); |
break; |
case _WebSocketOpcode.PONG: |
- _eventSink.add(new _WebSocketPong(_controlPayload)); |
+ _eventSink.add(new _WebSocketPong(_payload.takeBytes())); |
break; |
} |
_prepareForNextFrame(); |
@@ -363,16 +301,13 @@ class _WebSocketProtocolTransformer implements StreamTransformer, EventSink { |
void _prepareForNextFrame() { |
if (_state != CLOSED && _state != FAILURE) _state = START; |
- _fin = null; |
- _opcode = null; |
- _len = null; |
- _masked = null; |
- _maskingKey = 0; |
- _remainingLenBytes = null; |
- _remainingMaskingKeyBytes = null; |
- _remainingPayloadBytes = null; |
+ _fin = false; |
+ _opcode = -1; |
+ _len = -1; |
+ _remainingLenBytes = -1; |
+ _remainingMaskingKeyBytes = 4; |
+ _remainingPayloadBytes = -1; |
_unmaskingIndex = 0; |
- _controlPayload = null; |
} |
} |
@@ -571,7 +506,7 @@ class _WebSocketOutgoingTransformer implements StreamTransformer, EventSink { |
} else if (dataLength > 125) { |
headerSize += 2; |
} |
- List<int> header = new List<int>(headerSize); |
+ Uint8List header = new Uint8List(headerSize); |
int index = 0; |
// Set FIN and opcode. |
header[index++] = 0x80 | opcode; |
@@ -605,7 +540,7 @@ class _WebSocketOutgoingTransformer implements StreamTransformer, EventSink { |
} |
if (data is Uint8List) { |
for (int i = 0; i < data.length; i++) { |
- list[i] = data[i] ^ maskBytes[i % 4]; |
+ list[i] = data[i] ^ maskBytes[i & 3]; |
} |
} else { |
for (int i = 0; i < data.length; i++) { |
@@ -614,7 +549,7 @@ class _WebSocketOutgoingTransformer implements StreamTransformer, EventSink { |
"List element is not a byte value " |
"(value ${data[i]} at index $i)"); |
} |
- list[i] = data[i] ^ maskBytes[i % 4]; |
+ list[i] = data[i] ^ maskBytes[i & 3]; |
} |
} |
data = list; |
@@ -786,7 +721,7 @@ class _WebSocketImpl extends Stream implements WebSocket { |
Random random = new Random(); |
// Generate 16 random bytes. |
- List<int> nonceData = new List<int>(16); |
+ Uint8List nonceData = new Uint8List(16); |
for (int i = 0; i < 16; i++) { |
nonceData[i] = random.nextInt(256); |
} |