| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| patch class ServerSocket { |
| /* patch */ factory ServerSocket(String bindAddress, int port, int backlog) { |
| return new _ServerSocket(bindAddress, port, backlog); |
| } |
| } |
| |
| |
| patch class Socket { |
| /* patch */ factory Socket(String host, int port) => new _Socket(host, port); |
| } |
| |
| |
| class _SocketBase extends NativeFieldWrapperClass1 { |
| // Bit flags used when communicating between the eventhandler and |
| // dart code. The EVENT flags are used to indicate events of |
| // interest when sending a message from dart code to the |
| // eventhandler. When receiving a message from the eventhandler the |
| // EVENT flags indicate the events that actually happened. The |
| // COMMAND flags are used to send commands from dart to the |
| // eventhandler. COMMAND flags are never received from the |
| // eventhandler. Additional flags are used to communicate other |
| // information. |
| static const int _IN_EVENT = 0; |
| static const int _OUT_EVENT = 1; |
| static const int _ERROR_EVENT = 2; |
| static const int _CLOSE_EVENT = 3; |
| |
| static const int _CLOSE_COMMAND = 8; |
| static const int _SHUTDOWN_READ_COMMAND = 9; |
| static const int _SHUTDOWN_WRITE_COMMAND = 10; |
| |
| // Flag send to the eventhandler providing additional information on |
| // the type of the file descriptor. |
| static const int _LISTENING_SOCKET = 16; |
| static const int _PIPE = 17; |
| |
| static const int _FIRST_EVENT = _IN_EVENT; |
| static const int _LAST_EVENT = _CLOSE_EVENT; |
| |
| static const int _FIRST_COMMAND = _CLOSE_COMMAND; |
| static const int _LAST_COMMAND = _SHUTDOWN_WRITE_COMMAND; |
| |
| _SocketBase () { |
| _handlerMap = new List.fixedLength(_LAST_EVENT + 1); |
| _handlerMask = 0; |
| _canActivateHandlers = true; |
| _closed = true; |
| _EventHandler._start(); |
| _hashCode = _nextHashCode; |
| _nextHashCode = (_nextHashCode + 1) & 0xFFFFFFF; |
| } |
| |
| // Multiplexes socket events to the socket handlers. |
| void _multiplex(int event_mask) { |
| _canActivateHandlers = false; |
| for (int i = _FIRST_EVENT; i <= _LAST_EVENT; i++) { |
| if (((event_mask & (1 << i)) != 0)) { |
| if ((i == _CLOSE_EVENT) && this is _Socket && !_closed) { |
| _closedRead = true; |
| if (_closedWrite) _close(); |
| } |
| |
| var eventHandler = _handlerMap[i]; |
| if (eventHandler != null || i == _ERROR_EVENT) { |
| // Unregister the out handler before executing it. There is |
| // no need to notify the eventhandler as handlers are |
| // disabled while the event is handled. |
| if (i == _OUT_EVENT) _setHandler(i, null, notifyEventhandler: false); |
| |
| // Don't call the in handler if there is no data available |
| // after all. |
| if ((i == _IN_EVENT) && (this is _Socket) && (available() == 0)) { |
| continue; |
| } |
| if (i == _ERROR_EVENT) { |
| _reportError(_getError(), ""); |
| close(); |
| } else { |
| eventHandler(); |
| } |
| } |
| } |
| } |
| _canActivateHandlers = true; |
| _activateHandlers(); |
| } |
| |
| void _setHandler(int event, |
| Function callback, |
| {bool notifyEventhandler: true}) { |
| if (callback == null) { |
| _handlerMask &= ~(1 << event); |
| } else { |
| _handlerMask |= (1 << event); |
| } |
| _handlerMap[event] = callback; |
| // If the socket is only for writing then close the receive port |
| // when not waiting for any events. |
| if (this is _Socket && |
| _closedRead && |
| _handlerMask == 0 && |
| _handler != null) { |
| _handler.close(); |
| _handler = null; |
| } else { |
| if (notifyEventhandler) _activateHandlers(); |
| } |
| } |
| |
| OSError _getError() native "Socket_GetError"; |
| |
| int _getPort() native "Socket_GetPort"; |
| |
| void set onError(void callback(e)) { |
| _setHandler(_ERROR_EVENT, callback); |
| } |
| |
| void _activateHandlers() { |
| if (_canActivateHandlers && !_closed) { |
| if (_handlerMask == 0) { |
| if (_handler != null) { |
| _handler.close(); |
| _handler = null; |
| } |
| return; |
| } |
| int data = _handlerMask; |
| if (_isListenSocket()) { |
| data |= (1 << _LISTENING_SOCKET); |
| } else { |
| if (_closedRead) { data &= ~(1 << _IN_EVENT); } |
| if (_closedWrite) { data &= ~(1 << _OUT_EVENT); } |
| if (_isPipe()) data |= (1 << _PIPE); |
| } |
| _sendToEventHandler(data); |
| } |
| } |
| |
| int get port { |
| if (_port == null) { |
| _port = _getPort(); |
| } |
| return _port; |
| } |
| |
| void close([bool halfClose = false]) { |
| if (!_closed) { |
| if (halfClose) { |
| _closeWrite(); |
| } else { |
| _close(); |
| } |
| } else if (_handler != null) { |
| // This is to support closing sockets created but never assigned |
| // any actual socket. |
| _handler.close(); |
| _handler = null; |
| } |
| } |
| |
| void _closeWrite() { |
| if (!_closed) { |
| if (_closedRead) { |
| _close(); |
| } else { |
| _sendToEventHandler(1 << _SHUTDOWN_WRITE_COMMAND); |
| } |
| _closedWrite = true; |
| } |
| } |
| |
| void _closeRead() { |
| if (!_closed) { |
| if (_closedWrite) { |
| _close(); |
| } else { |
| _sendToEventHandler(1 << _SHUTDOWN_READ_COMMAND); |
| } |
| _closedRead = true; |
| } |
| } |
| |
| void _close() { |
| if (!_closed) { |
| _sendToEventHandler(1 << _CLOSE_COMMAND); |
| _handler.close(); |
| _handler = null; |
| _closed = true; |
| } |
| } |
| |
| void _sendToEventHandler(int data) { |
| if (_handler == null) { |
| _handler = new ReceivePort(); |
| _handler.receive((var message, ignored) { _multiplex(message); }); |
| } |
| assert(!_closed); |
| _EventHandler._sendData(this, _handler, data); |
| } |
| |
| bool _reportError(error, String message) { |
| void doReportError(Exception e) { |
| // Invoke the socket error callback if any. |
| bool reported = false; |
| if (_handlerMap[_ERROR_EVENT] != null) { |
| _handlerMap[_ERROR_EVENT](e); |
| reported = true; |
| } |
| // Propagate the error to any additional listeners. |
| reported = reported || _propagateError(e); |
| if (!reported) throw e; |
| } |
| |
| // For all errors we close the socket, call the error handler and |
| // disable further calls of the error handler. |
| close(); |
| if (error is OSError) { |
| doReportError(new SocketIOException(message, error)); |
| } else if (error is List) { |
| assert(_isErrorResponse(error)); |
| switch (error[0]) { |
| case _ILLEGAL_ARGUMENT_RESPONSE: |
| doReportError(new ArgumentError()); |
| break; |
| case _OSERROR_RESPONSE: |
| doReportError(new SocketIOException( |
| message, new OSError(error[2], error[1]))); |
| break; |
| default: |
| doReportError(new Exception("Unknown error")); |
| break; |
| } |
| } else { |
| doReportError(new SocketIOException(message)); |
| } |
| } |
| |
| int get hashCode => _hashCode; |
| |
| bool _propagateError(Exception e) => false; |
| |
| bool _isListenSocket(); |
| bool _isPipe(); |
| |
| // Is this socket closed. |
| bool _closed; |
| |
| // Dedicated ReceivePort for socket events. |
| ReceivePort _handler; |
| |
| // Poll event to handler map. |
| List _handlerMap; |
| |
| // Indicates for which poll events the socket registered handlers. |
| int _handlerMask; |
| |
| // Indicates if native interrupts can be activated. |
| bool _canActivateHandlers; |
| |
| // Holds the port of the socket, null if not known. |
| int _port; |
| |
| // Hash code for the socket. Currently this is just a counter. |
| int _hashCode; |
| static int _nextHashCode = 0; |
| bool _closedRead = false; |
| bool _closedWrite = false; |
| } |
| |
| |
| class _ServerSocket extends _SocketBase implements ServerSocket { |
| // Constructor for server socket. First a socket object is allocated |
| // in which the native socket is stored. After that _createBind |
| // is called which creates a file descriptor and binds the given address |
| // and port to the socket. Null is returned if file descriptor creation or |
| // bind failed. |
| factory _ServerSocket(String bindAddress, int port, int backlog) { |
| _ServerSocket socket = new _ServerSocket._internal(); |
| var result = socket._createBindListen(bindAddress, port, backlog); |
| if (result is OSError) { |
| socket.close(); |
| throw new SocketIOException("Failed to create server socket", result); |
| } |
| socket._closed = false; |
| assert(result); |
| if (port != 0) { |
| socket._port = port; |
| } |
| return socket; |
| } |
| |
| _ServerSocket._internal(); |
| |
| _accept(Socket socket) native "ServerSocket_Accept"; |
| |
| _createBindListen(String bindAddress, int port, int backlog) |
| native "ServerSocket_CreateBindListen"; |
| |
| void set onConnection(void callback(Socket connection)) { |
| _clientConnectionHandler = callback; |
| _setHandler(_SocketBase._IN_EVENT, |
| _clientConnectionHandler != null ? _connectionHandler : null); |
| } |
| |
| void _connectionHandler() { |
| if (!_closed) { |
| _Socket socket = new _Socket._internal(); |
| var result = _accept(socket); |
| if (result is OSError) { |
| _reportError(result, "Accept failed"); |
| } else if (result) { |
| socket._closed = false; |
| _clientConnectionHandler(socket); |
| } else { |
| // Temporary failure accepting the connection. Ignoring |
| // temporary failures lets us retry when we wake up with data |
| // on the listening socket again. |
| } |
| } |
| } |
| |
| bool _isListenSocket() => true; |
| bool _isPipe() => false; |
| |
| var _clientConnectionHandler; |
| } |
| |
| |
| class _Socket extends _SocketBase implements Socket { |
| static const HOST_NAME_LOOKUP = 0; |
| |
| // Constructs a new socket. During the construction an asynchronous |
| // host name lookup is initiated. The returned socket is not yet |
| // connected but ready for registration of callbacks. |
| factory _Socket(String host, int port) { |
| Socket socket = new _Socket._internal(); |
| _ensureSocketService(); |
| List request = new List.fixedLength(2); |
| request[0] = HOST_NAME_LOOKUP; |
| request[1] = host; |
| _socketService.call(request).then((response) { |
| if (socket._isErrorResponse(response)) { |
| socket._reportError(response, "Failed host name lookup"); |
| } else{ |
| var result = socket._createConnect(response, port); |
| if (result is OSError) { |
| socket.close(); |
| socket._reportError(result, "Connection failed"); |
| } else { |
| socket._closed = false; |
| socket._activateHandlers(); |
| } |
| } |
| }); |
| return socket; |
| } |
| |
| _Socket._internal(); |
| _Socket._internalReadOnly() : _pipe = true { super._closedWrite = true; } |
| _Socket._internalWriteOnly() : _pipe = true { super._closedRead = true; } |
| |
| int available() { |
| if (!_closed) { |
| var result = _available(); |
| if (result is OSError) { |
| _reportError(result, "Available failed"); |
| return 0; |
| } else { |
| return result; |
| } |
| } |
| throw new |
| SocketIOException("Error: available failed - invalid socket handle"); |
| } |
| |
| _available() native "Socket_Available"; |
| |
| List<int> read([int len]) { |
| if (len != null && len <= 0) { |
| throw new SocketIOException("Illegal length $len"); |
| } |
| var result = _read(len == null ? -1 : len); |
| if (result is OSError) { |
| _reportError(result, "Read failed"); |
| return null; |
| } |
| return result; |
| } |
| |
| _read(int len) native "Socket_Read"; |
| |
| int readList(List<int> buffer, int offset, int bytes) { |
| if (!_closed) { |
| if (bytes == 0) { |
| return 0; |
| } |
| if (offset < 0) { |
| throw new RangeError.value(offset); |
| } |
| if (bytes < 0) { |
| throw new RangeError.value(bytes); |
| } |
| if ((offset + bytes) > buffer.length) { |
| throw new RangeError.value(offset + bytes); |
| } |
| var result = _readList(buffer, offset, bytes); |
| if (result is OSError) { |
| _reportError(result, "Read failed"); |
| return -1; |
| } |
| return result; |
| } |
| throw new |
| SocketIOException("Error: readList failed - invalid socket handle"); |
| } |
| |
| _readList(List<int> buffer, int offset, int bytes) native "Socket_ReadList"; |
| |
| int writeList(List<int> buffer, int offset, int bytes) { |
| if (buffer is! List || offset is! int || bytes is! int) { |
| throw new ArgumentError( |
| "Invalid arguments to writeList on Socket"); |
| } |
| if (!_closed) { |
| if (bytes == 0) { |
| return 0; |
| } |
| if (offset < 0) { |
| throw new RangeError.value(offset); |
| } |
| if (bytes < 0) { |
| throw new RangeError.value(bytes); |
| } |
| if ((offset + bytes) > buffer.length) { |
| throw new RangeError.value(offset + bytes); |
| } |
| _BufferAndOffset bufferAndOffset = |
| _ensureFastAndSerializableBuffer(buffer, offset, bytes); |
| var result = |
| _writeList(bufferAndOffset.buffer, bufferAndOffset.offset, bytes); |
| if (result is OSError) { |
| _reportError(result, "Write failed"); |
| // If writing fails we return 0 as the number of bytes and |
| // report the error on the error handler. |
| result = 0; |
| } |
| return result; |
| } |
| throw new SocketIOException("writeList failed - invalid socket handle"); |
| } |
| |
| _writeList(List<int> buffer, int offset, int bytes) native "Socket_WriteList"; |
| |
| bool _isErrorResponse(response) { |
| return response is List && response[0] != _SUCCESS_RESPONSE; |
| } |
| |
| bool _createConnect(String host, int port) native "Socket_CreateConnect"; |
| |
| void set onWrite(void callback()) { |
| if (_outputStream != null) throw new StreamException( |
| "Cannot set write handler when output stream is used"); |
| _clientWriteHandler = callback; |
| _updateOutHandler(); |
| } |
| |
| void set onConnect(void callback()) { |
| if (_seenFirstOutEvent) { |
| throw new StreamException( |
| "Cannot set connect handler when already connected"); |
| } |
| _clientConnectHandler = callback; |
| _updateOutHandler(); |
| } |
| |
| void set onData(void callback()) { |
| if (_inputStream != null) throw new StreamException( |
| "Cannot set data handler when input stream is used"); |
| _onData = callback; |
| } |
| |
| void set onClosed(void callback()) { |
| if (_inputStream != null) throw new StreamException( |
| "Cannot set close handler when input stream is used"); |
| _onClosed = callback; |
| } |
| |
| bool _isListenSocket() => false; |
| |
| bool _isPipe() => _pipe; |
| |
| InputStream get inputStream { |
| if (_inputStream == null) { |
| if (_handlerMap[_SocketBase._IN_EVENT] != null || |
| _handlerMap[_SocketBase._CLOSE_EVENT] != null) { |
| throw new StreamException( |
| "Cannot get input stream when socket handlers are used"); |
| } |
| _inputStream = new _SocketInputStream(this); |
| } |
| return _inputStream; |
| } |
| |
| OutputStream get outputStream { |
| if (_outputStream == null) { |
| if (_clientWriteHandler != null) { |
| throw new StreamException( |
| "Cannot get output stream when socket handlers are used"); |
| } |
| _outputStream = new _SocketOutputStream(this); |
| } |
| return _outputStream; |
| } |
| |
| void set _onWrite(void callback()) { |
| _setHandler(_SocketBase._OUT_EVENT, callback); |
| } |
| |
| void set _onData(void callback()) { |
| _setHandler(_SocketBase._IN_EVENT, callback); |
| } |
| |
| void set _onClosed(void callback()) { |
| _setHandler(_SocketBase._CLOSE_EVENT, callback); |
| } |
| |
| bool _propagateError(Exception e) { |
| bool reported = false; |
| if (_inputStream != null) { |
| reported = reported || _inputStream._onSocketError(e); |
| } |
| if (_outputStream != null) { |
| reported = reported || _outputStream._onSocketError(e); |
| } |
| return reported; |
| } |
| |
| void _updateOutHandler() { |
| void firstWriteHandler() { |
| assert(!_seenFirstOutEvent); |
| _seenFirstOutEvent = true; |
| |
| // From now on the write handler is only the client write |
| // handler (connect handler cannot be called again). Change this |
| // before calling any handlers as handlers can change the |
| // handlers. |
| if (_clientWriteHandler == null) _onWrite = _clientWriteHandler; |
| |
| // First out event is socket connected event. |
| if (_clientConnectHandler != null) _clientConnectHandler(); |
| _clientConnectHandler = null; |
| |
| // Always (even for the first out event) call the write handler. |
| if (_clientWriteHandler != null) _clientWriteHandler(); |
| } |
| |
| if (_clientConnectHandler == null && _clientWriteHandler == null) { |
| _onWrite = null; |
| } else { |
| if (_seenFirstOutEvent) { |
| _onWrite = _clientWriteHandler; |
| } else { |
| _onWrite = firstWriteHandler; |
| } |
| } |
| } |
| |
| int get remotePort { |
| if (_remotePort == null) { |
| remoteHost; |
| } |
| return _remotePort; |
| } |
| |
| String get remoteHost { |
| if (_remoteHost == null) { |
| List peer = _getRemotePeer(); |
| _remoteHost = peer[0]; |
| _remotePort = peer[1]; |
| } |
| return _remoteHost; |
| } |
| |
| List _getRemotePeer() native "Socket_GetRemotePeer"; |
| |
| static SendPort _newServicePort() native "Socket_NewServicePort"; |
| |
| static void _ensureSocketService() { |
| if (_socketService == null) { |
| _socketService = _Socket._newServicePort(); |
| } |
| } |
| |
| bool _seenFirstOutEvent = false; |
| bool _pipe = false; |
| Function _clientConnectHandler; |
| Function _clientWriteHandler; |
| _SocketInputStream _inputStream; |
| _SocketOutputStream _outputStream; |
| String _remoteHost; |
| int _remotePort; |
| static SendPort _socketService; |
| } |