54f6939ffc
The required setting of the internet adddress of the peer for NSS is now using the internet address which have already been looked up in Dart code for normal secure socket connect. In the cases where SecureSocket.secure and RawSecureSocket.secure is used with a host name, the internet address of the already connected socket will be used. If that is not sufficient an IntetnetAddress from DNS lookup in Dart can be passed instead of just a host name. R=ajohnsen@google.com, whesse@google.com BUG=http://dartbug.com/11103 Review URL: https://codereview.chromium.org//17381012 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@24174 260f80e4-7a28-3924-810f-c04153c831b5
1081 lines
31 KiB
Dart
1081 lines
31 KiB
Dart
// Copyright (c) 2013, 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 RawServerSocket {
|
|
/* patch */ static Future<RawServerSocket> bind(address,
|
|
int port,
|
|
{int backlog: 0,
|
|
bool v6Only: false}) {
|
|
return _RawServerSocket.bind(address, port, backlog, v6Only);
|
|
}
|
|
}
|
|
|
|
|
|
patch class RawSocket {
|
|
/* patch */ static Future<RawSocket> connect(host, int port) {
|
|
return _RawSocket.connect(host, port);
|
|
}
|
|
}
|
|
|
|
|
|
patch class InternetAddress {
|
|
/* patch */ static InternetAddress get LOOPBACK_IP_V4 {
|
|
return _InternetAddress.LOOPBACK_IP_V4;
|
|
}
|
|
|
|
/* patch */ static InternetAddress get LOOPBACK_IP_V6 {
|
|
return _InternetAddress.LOOPBACK_IP_V6;
|
|
}
|
|
|
|
/* patch */ static InternetAddress get ANY_IP_V4 {
|
|
return _InternetAddress.ANY_IP_V4;
|
|
}
|
|
|
|
/* patch */ static InternetAddress get ANY_IP_V6 {
|
|
return _InternetAddress.ANY_IP_V6;
|
|
}
|
|
|
|
/* patch */ static Future<List<InternetAddress>> lookup(
|
|
String host, {InternetAddressType type: InternetAddressType.ANY}) {
|
|
return _NativeSocket.lookup(host, type: type);
|
|
}
|
|
}
|
|
|
|
class _InternetAddress implements InternetAddress {
|
|
static const int _ADDRESS_LOOPBACK_IP_V4 = 0;
|
|
static const int _ADDRESS_LOOPBACK_IP_V6 = 1;
|
|
static const int _ADDRESS_ANY_IP_V4 = 2;
|
|
static const int _ADDRESS_ANY_IP_V6 = 3;
|
|
|
|
static _InternetAddress LOOPBACK_IP_V4 =
|
|
new _InternetAddress.fixed(_ADDRESS_LOOPBACK_IP_V4);
|
|
static _InternetAddress LOOPBACK_IP_V6 =
|
|
new _InternetAddress.fixed(_ADDRESS_LOOPBACK_IP_V6);
|
|
static _InternetAddress ANY_IP_V4 =
|
|
new _InternetAddress.fixed(_ADDRESS_ANY_IP_V4);
|
|
static _InternetAddress ANY_IP_V6 =
|
|
new _InternetAddress.fixed(_ADDRESS_ANY_IP_V6);
|
|
|
|
final InternetAddressType type;
|
|
final String address;
|
|
final String host;
|
|
final Uint8List _sockaddr_storage;
|
|
|
|
_InternetAddress(InternetAddressType this.type,
|
|
String this.address,
|
|
String this.host,
|
|
List<int> this._sockaddr_storage);
|
|
|
|
factory _InternetAddress.fixed(int id) {
|
|
var sockaddr = _fixed(id);
|
|
switch (id) {
|
|
case _ADDRESS_LOOPBACK_IP_V4:
|
|
return new _InternetAddress(
|
|
InternetAddressType.IP_V4, "127.0.0.1", "localhost", sockaddr);
|
|
case _ADDRESS_LOOPBACK_IP_V6:
|
|
return new _InternetAddress(
|
|
InternetAddressType.IP_V6, "::1", "ip6-localhost", sockaddr);
|
|
case _ADDRESS_ANY_IP_V4:
|
|
return new _InternetAddress(
|
|
InternetAddressType.IP_V4, "0.0.0.0", "0.0.0.0", sockaddr);
|
|
case _ADDRESS_ANY_IP_V6:
|
|
return new _InternetAddress(
|
|
InternetAddressType.IP_V6, "::", "::", sockaddr);
|
|
default:
|
|
assert(false);
|
|
throw new ArgumentError();
|
|
}
|
|
}
|
|
|
|
// Create a clone of this _InternetAddress replacing the host.
|
|
_InternetAddress _cloneWithNewHost(String host) {
|
|
return new _InternetAddress(
|
|
type, address, host, new Uint8List.fromList(_sockaddr_storage));
|
|
}
|
|
|
|
String toString() {
|
|
return "InternetAddress('$address', ${type.name})";
|
|
}
|
|
|
|
static Uint8List _fixed(int id) native "InternetAddress_Fixed";
|
|
}
|
|
|
|
|
|
// The _NativeSocket class encapsulates an OS socket.
|
|
class _NativeSocket 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 READ_EVENT = 0;
|
|
static const int WRITE_EVENT = 1;
|
|
static const int ERROR_EVENT = 2;
|
|
static const int CLOSED_EVENT = 3;
|
|
static const int FIRST_EVENT = READ_EVENT;
|
|
static const int LAST_EVENT = CLOSED_EVENT;
|
|
static const int EVENT_COUNT = LAST_EVENT - FIRST_EVENT + 1;
|
|
|
|
static const int CLOSE_COMMAND = 8;
|
|
static const int SHUTDOWN_READ_COMMAND = 9;
|
|
static const int SHUTDOWN_WRITE_COMMAND = 10;
|
|
static const int FIRST_COMMAND = CLOSE_COMMAND;
|
|
static const int LAST_COMMAND = SHUTDOWN_WRITE_COMMAND;
|
|
|
|
// Type 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_SOCKET = 17;
|
|
static const int TYPE_NORMAL_SOCKET = 0;
|
|
static const int TYPE_LISTENING_SOCKET = 1 << LISTENING_SOCKET;
|
|
static const int TYPE_PIPE = 1 << PIPE_SOCKET;
|
|
|
|
// Native port messages.
|
|
static const HOST_NAME_LOOKUP = 0;
|
|
|
|
// Socket close state
|
|
bool isClosed = false;
|
|
bool isClosedRead = false;
|
|
bool isClosedWrite = false;
|
|
Completer closeCompleter = new Completer();
|
|
|
|
// Handlers and receive port for socket events from the event handler.
|
|
int eventMask = 0;
|
|
List eventHandlers;
|
|
ReceivePort eventPort;
|
|
|
|
// Indicates if native interrupts can be activated.
|
|
bool canActivateEvents = true;
|
|
|
|
// The type flags for this socket.
|
|
final int typeFlags;
|
|
|
|
// Holds the port of the socket, null if not known.
|
|
int localPort;
|
|
|
|
// Holds the address used to connect or bind the socket.
|
|
InternetAddress address;
|
|
|
|
// Native port for socket services.
|
|
static SendPort socketService;
|
|
|
|
static Future<List<InternetAddress>> lookup(
|
|
String host, {InternetAddressType type: InternetAddressType.ANY}) {
|
|
ensureSocketService();
|
|
return socketService.call([HOST_NAME_LOOKUP, host, type._value])
|
|
.then((response) {
|
|
if (isErrorResponse(response)) {
|
|
throw createError(response, "Failed host name lookup");
|
|
} else {
|
|
return response.skip(1).map((result) {
|
|
var type = new InternetAddressType._from(result[0]);
|
|
return new _InternetAddress(type, result[1], host, result[2]);
|
|
}).toList();
|
|
}
|
|
});
|
|
}
|
|
|
|
static Future<_NativeSocket> connect(host, int port) {
|
|
return new Future.value(host)
|
|
.then((host) {
|
|
if (host is _InternetAddress) return host;
|
|
return lookup(host)
|
|
.then((list) {
|
|
if (list.length == 0) {
|
|
throw createError(response, "Failed host name lookup");
|
|
}
|
|
return list[0];
|
|
});
|
|
})
|
|
.then((address) {
|
|
ensureSocketService();
|
|
var socket = new _NativeSocket.normal();
|
|
socket.address = address;
|
|
var result = socket.nativeCreateConnect(
|
|
address._sockaddr_storage, port);
|
|
if (result is OSError) {
|
|
throw createError(result, "Connection failed");
|
|
} else {
|
|
var completer = new Completer();
|
|
// Setup handlers for receiving the first write event which
|
|
// indicate that the socket is fully connected.
|
|
socket.setHandlers(
|
|
write: () {
|
|
socket.setListening(read: false, write: false);
|
|
completer.complete(socket);
|
|
},
|
|
error: (e) {
|
|
socket.close();
|
|
completer.completeError(createError(e, "Connection failed"));
|
|
}
|
|
);
|
|
socket.setListening(read: false, write: true);
|
|
return completer.future;
|
|
}
|
|
});
|
|
}
|
|
|
|
static Future<_NativeSocket> bind(host,
|
|
int port,
|
|
int backlog,
|
|
bool v6Only) {
|
|
return new Future.value(host)
|
|
.then((host) {
|
|
if (host is _InternetAddress) return host;
|
|
return lookup(host)
|
|
.then((list) {
|
|
if (list.length == 0) {
|
|
throw createError(response, "Failed host name lookup");
|
|
}
|
|
return list[0];
|
|
});
|
|
})
|
|
.then((address) {
|
|
var socket = new _NativeSocket.listen();
|
|
socket.address = address;
|
|
var result = socket.nativeCreateBindListen(address._sockaddr_storage,
|
|
port,
|
|
backlog,
|
|
v6Only);
|
|
if (result is OSError) {
|
|
throw new SocketException(
|
|
"Failed to create server socket", result);
|
|
}
|
|
if (port != 0) socket.localPort = port;
|
|
return socket;
|
|
});
|
|
}
|
|
|
|
_NativeSocket.normal() : typeFlags = TYPE_NORMAL_SOCKET {
|
|
eventHandlers = new List(EVENT_COUNT + 1);
|
|
_EventHandler._start();
|
|
}
|
|
|
|
_NativeSocket.listen() : typeFlags = TYPE_LISTENING_SOCKET {
|
|
eventHandlers = new List(EVENT_COUNT + 1);
|
|
_EventHandler._start();
|
|
}
|
|
|
|
_NativeSocket.pipe() : typeFlags = TYPE_PIPE {
|
|
eventHandlers = new List(EVENT_COUNT + 1);
|
|
_EventHandler._start();
|
|
}
|
|
|
|
int available() {
|
|
if (isClosed) return 0;
|
|
var result = nativeAvailable();
|
|
if (result is OSError) {
|
|
reportError(result, "Available failed");
|
|
return 0;
|
|
} else {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
List<int> read(int len) {
|
|
if (len != null && len <= 0) {
|
|
throw new ArgumentError("Illegal length $len");
|
|
}
|
|
if (isClosed) return null;
|
|
var result = nativeRead(len == null ? -1 : len);
|
|
if (result is OSError) {
|
|
reportError(result, "Read failed");
|
|
return null;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int write(List<int> buffer, int offset, int bytes) {
|
|
if (buffer is! List) throw new ArgumentError();
|
|
if (offset == null) offset = 0;
|
|
if (bytes == null) bytes = buffer.length;
|
|
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);
|
|
}
|
|
if (offset is! int || bytes is! int) {
|
|
throw new ArgumentError("Invalid arguments to write on Socket");
|
|
}
|
|
if (isClosed) return 0;
|
|
if (bytes == 0) return 0;
|
|
_BufferAndStart bufferAndStart =
|
|
_ensureFastAndSerializableByteData(buffer, offset, offset + bytes);
|
|
var result =
|
|
nativeWrite(bufferAndStart.buffer, bufferAndStart.start, bytes);
|
|
if (result is OSError) {
|
|
reportError(result, "Write failed");
|
|
result = 0;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
_NativeSocket accept() {
|
|
var socket = new _NativeSocket.normal();
|
|
if (nativeAccept(socket) != true) return null;
|
|
socket.localPort = localPort;
|
|
socket.address = address;
|
|
return socket;
|
|
}
|
|
|
|
int get port {
|
|
if (localPort != null) return localPort;
|
|
return localPort = nativeGetPort();
|
|
}
|
|
|
|
int get remotePort {
|
|
return nativeGetRemotePeer()[1];
|
|
}
|
|
|
|
String get remoteHost {
|
|
return nativeGetRemotePeer()[0];
|
|
}
|
|
|
|
// Multiplexes socket events to the socket handlers.
|
|
void multiplex(int events) {
|
|
canActivateEvents = false;
|
|
for (int i = FIRST_EVENT; i <= LAST_EVENT; i++) {
|
|
if (((events & (1 << i)) != 0)) {
|
|
if (i == CLOSED_EVENT &&
|
|
typeFlags != TYPE_LISTENING_SOCKET &&
|
|
!isClosed) {
|
|
isClosedRead = true;
|
|
}
|
|
|
|
var handler = eventHandlers[i];
|
|
assert(handler != null);
|
|
if (i == WRITE_EVENT) {
|
|
// If the event was disabled before we had a chance to fire the event,
|
|
// discard it. If we register again, we'll get a new one.
|
|
if ((eventMask & (1 << i)) == 0) continue;
|
|
// Unregister the out handler before executing it. There is
|
|
// no need to notify the eventhandler as handlers are
|
|
// disabled while the event is handled.
|
|
eventMask &= ~(1 << i);
|
|
}
|
|
|
|
// Don't call the in handler if there is no data available
|
|
// after all.
|
|
if (i == READ_EVENT &&
|
|
typeFlags != TYPE_LISTENING_SOCKET &&
|
|
available() == 0) {
|
|
continue;
|
|
}
|
|
if (i == ERROR_EVENT) {
|
|
reportError(nativeGetError(), "");
|
|
} else if (!isClosed) {
|
|
handler();
|
|
}
|
|
}
|
|
}
|
|
if (isClosedRead && isClosedWrite) close();
|
|
canActivateEvents = true;
|
|
activateHandlers();
|
|
}
|
|
|
|
void setHandlers({read: null, write: null, error: null, closed: null}) {
|
|
eventHandlers[READ_EVENT] = read;
|
|
eventHandlers[WRITE_EVENT] = write;
|
|
eventHandlers[ERROR_EVENT] = error;
|
|
eventHandlers[CLOSED_EVENT] = closed;
|
|
}
|
|
|
|
void setListening({read: true, write: true}) {
|
|
eventMask = (1 << CLOSED_EVENT) | (1 << ERROR_EVENT);
|
|
if (read) eventMask |= (1 << READ_EVENT);
|
|
if (write) eventMask |= (1 << WRITE_EVENT);
|
|
activateHandlers();
|
|
}
|
|
|
|
Future get closeFuture => closeCompleter.future;
|
|
|
|
void activateHandlers() {
|
|
if (canActivateEvents && !isClosed) {
|
|
// If we don't listen for either read or write, disconnect as we won't
|
|
// get close and error events anyway.
|
|
if ((eventMask & ((1 << READ_EVENT) | (1 << WRITE_EVENT))) == 0) {
|
|
if (eventPort != null) disconnectFromEventHandler();
|
|
} else {
|
|
int data = eventMask;
|
|
data |= typeFlags;
|
|
if (isClosedRead) data &= ~(1 << READ_EVENT);
|
|
if (isClosedWrite) data &= ~(1 << WRITE_EVENT);
|
|
sendToEventHandler(data);
|
|
}
|
|
}
|
|
}
|
|
|
|
void close() {
|
|
if (!isClosed) {
|
|
sendToEventHandler(1 << CLOSE_COMMAND);
|
|
isClosed = true;
|
|
closeCompleter.complete(this);
|
|
}
|
|
// Outside the if support closing sockets created but never
|
|
// assigned any actual socket.
|
|
disconnectFromEventHandler();
|
|
}
|
|
|
|
void shutdown(SocketDirection direction) {
|
|
if (!isClosed) {
|
|
switch (direction) {
|
|
case SocketDirection.RECEIVE:
|
|
shutdownRead();
|
|
break;
|
|
case SocketDirection.SEND:
|
|
shutdownWrite();
|
|
break;
|
|
case SocketDirection.BOTH:
|
|
close();
|
|
break;
|
|
default:
|
|
throw new ArgumentError(direction);
|
|
}
|
|
}
|
|
}
|
|
|
|
void shutdownWrite() {
|
|
if (!isClosed) {
|
|
if (isClosedRead) {
|
|
close();
|
|
} else {
|
|
sendToEventHandler(1 << SHUTDOWN_WRITE_COMMAND);
|
|
}
|
|
isClosedWrite = true;
|
|
}
|
|
}
|
|
|
|
void shutdownRead() {
|
|
if (!isClosed) {
|
|
if (isClosedWrite) {
|
|
close();
|
|
} else {
|
|
sendToEventHandler(1 << SHUTDOWN_READ_COMMAND);
|
|
}
|
|
isClosedRead = true;
|
|
}
|
|
}
|
|
|
|
void sendToEventHandler(int data) {
|
|
connectToEventHandler();
|
|
assert(!isClosed);
|
|
_EventHandler._sendData(this, eventPort, data);
|
|
}
|
|
|
|
void connectToEventHandler() {
|
|
if (eventPort == null) {
|
|
eventPort = new ReceivePort();
|
|
eventPort.receive ((var message, _) => multiplex(message));
|
|
}
|
|
}
|
|
|
|
void disconnectFromEventHandler() {
|
|
if (eventPort != null) {
|
|
eventPort.close();
|
|
eventPort = null;
|
|
}
|
|
}
|
|
|
|
static void ensureSocketService() {
|
|
if (socketService == null) {
|
|
socketService = _NativeSocket.newServicePort();
|
|
}
|
|
}
|
|
|
|
// Check whether this is an error response from a native port call.
|
|
static bool isErrorResponse(response) {
|
|
return response is List && response[0] != _SUCCESS_RESPONSE;
|
|
}
|
|
|
|
// Create the appropriate error/exception from different returned
|
|
// error objects.
|
|
static createError(error, String message) {
|
|
if (error is OSError) {
|
|
return new SocketException(message, error);
|
|
} else if (error is List) {
|
|
assert(isErrorResponse(error));
|
|
switch (error[0]) {
|
|
case _ILLEGAL_ARGUMENT_RESPONSE:
|
|
return new ArgumentError();
|
|
case _OSERROR_RESPONSE:
|
|
return new SocketException(
|
|
message, new OSError(error[2], error[1]));
|
|
default:
|
|
return new Exception("Unknown error");
|
|
}
|
|
} else {
|
|
return new SocketException(message);
|
|
}
|
|
}
|
|
|
|
void reportError(error, String message) {
|
|
var e = createError(error, message);
|
|
// Invoke the error handler if any.
|
|
if (eventHandlers[ERROR_EVENT] != null) {
|
|
eventHandlers[ERROR_EVENT](e);
|
|
}
|
|
// For all errors we close the socket
|
|
close();
|
|
}
|
|
|
|
bool setOption(SocketOption option, bool enabled) {
|
|
if (option is! SocketOption) throw new ArgumentError(options);
|
|
if (enabled is! bool) throw new ArgumentError(enabled);
|
|
return nativeSetOption(option._value, enabled);
|
|
}
|
|
|
|
nativeAvailable() native "Socket_Available";
|
|
nativeRead(int len) native "Socket_Read";
|
|
nativeWrite(List<int> buffer, int offset, int bytes)
|
|
native "Socket_WriteList";
|
|
nativeCreateConnect(List<int> addr,
|
|
int port) native "Socket_CreateConnect";
|
|
nativeCreateBindListen(List<int> addr, int port, int backlog, bool v6Only)
|
|
native "ServerSocket_CreateBindListen";
|
|
nativeAccept(_NativeSocket socket) native "ServerSocket_Accept";
|
|
int nativeGetPort() native "Socket_GetPort";
|
|
List nativeGetRemotePeer() native "Socket_GetRemotePeer";
|
|
OSError nativeGetError() native "Socket_GetError";
|
|
bool nativeSetOption(int option, bool enabled) native "Socket_SetOption";
|
|
|
|
static SendPort newServicePort() native "Socket_NewServicePort";
|
|
}
|
|
|
|
|
|
class _RawServerSocket extends Stream<RawSocket>
|
|
implements RawServerSocket {
|
|
final _NativeSocket _socket;
|
|
StreamController<RawSocket> _controller;
|
|
|
|
static Future<_RawServerSocket> bind(address,
|
|
int port,
|
|
int backlog,
|
|
bool v6Only) {
|
|
if (port < 0 || port > 0xFFFF)
|
|
throw new ArgumentError("Invalid port $port");
|
|
if (backlog < 0) throw new ArgumentError("Invalid backlog $backlog");
|
|
return _NativeSocket.bind(address, port, backlog, v6Only)
|
|
.then((socket) => new _RawServerSocket(socket));
|
|
}
|
|
|
|
_RawServerSocket(this._socket) {
|
|
_controller = new StreamController(sync: true,
|
|
onListen: _onSubscriptionStateChange,
|
|
onCancel: _onSubscriptionStateChange,
|
|
onPause: _onPauseStateChange,
|
|
onResume: _onPauseStateChange);
|
|
_socket.closeFuture.then((_) => _controller.close());
|
|
_socket.setHandlers(
|
|
read: () {
|
|
var socket = _socket.accept();
|
|
if (socket != null) _controller.add(new _RawSocket(socket));
|
|
},
|
|
error: (e) {
|
|
_controller.addError(e);
|
|
_controller.close();
|
|
}
|
|
);
|
|
}
|
|
|
|
StreamSubscription<RawSocket> listen(void onData(RawSocket event),
|
|
{void onError(Object error),
|
|
void onDone(),
|
|
bool cancelOnError}) {
|
|
return _controller.stream.listen(
|
|
onData,
|
|
onError: onError,
|
|
onDone: onDone,
|
|
cancelOnError: cancelOnError);
|
|
}
|
|
|
|
int get port => _socket.port;
|
|
|
|
void close() => _socket.close();
|
|
|
|
void _pause() {
|
|
_socket.setListening(read: false, write: false);
|
|
}
|
|
|
|
void _resume() {
|
|
_socket.setListening(read: true, write: false);
|
|
}
|
|
|
|
void _onSubscriptionStateChange() {
|
|
if (_controller.hasListener) {
|
|
_resume();
|
|
} else {
|
|
close();
|
|
}
|
|
}
|
|
|
|
void _onPauseStateChange() {
|
|
if (_controller.isPaused) {
|
|
_pause();
|
|
} else {
|
|
_resume();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
class _RawSocket extends Stream<RawSocketEvent>
|
|
implements RawSocket {
|
|
final _NativeSocket _socket;
|
|
StreamController<RawSocketEvent> _controller;
|
|
bool _readEventsEnabled = true;
|
|
bool _writeEventsEnabled = true;
|
|
|
|
static Future<RawSocket> connect(host, int port) {
|
|
return _NativeSocket.connect(host, port)
|
|
.then((socket) => new _RawSocket(socket));
|
|
}
|
|
|
|
_RawSocket(this._socket) {
|
|
_controller = new StreamController(sync: true,
|
|
onListen: _onSubscriptionStateChange,
|
|
onCancel: _onSubscriptionStateChange,
|
|
onPause: _onPauseStateChange,
|
|
onResume: _onPauseStateChange);
|
|
_socket.closeFuture.then((_) => _controller.close());
|
|
_socket.setHandlers(
|
|
read: () => _controller.add(RawSocketEvent.READ),
|
|
write: () {
|
|
// The write event handler is automatically disabled by the
|
|
// event handler when it fires.
|
|
_writeEventsEnabled = false;
|
|
_controller.add(RawSocketEvent.WRITE);
|
|
},
|
|
closed: () => _controller.add(RawSocketEvent.READ_CLOSED),
|
|
error: (e) {
|
|
_controller.addError(e);
|
|
close();
|
|
}
|
|
);
|
|
}
|
|
|
|
factory _RawSocket._writePipe(int fd) {
|
|
var native = new _NativeSocket.pipe();
|
|
native.isClosedRead = true;
|
|
if (fd != null) _getStdioHandle(native, fd);
|
|
return new _RawSocket(native);
|
|
}
|
|
|
|
factory _RawSocket._readPipe(int fd) {
|
|
var native = new _NativeSocket.pipe();
|
|
native.isClosedWrite = true;
|
|
if (fd != null) _getStdioHandle(native, fd);
|
|
return new _RawSocket(native);
|
|
}
|
|
|
|
StreamSubscription<RawSocketEvent> listen(void onData(RawSocketEvent event),
|
|
{void onError(Object error),
|
|
void onDone(),
|
|
bool cancelOnError}) {
|
|
return _controller.stream.listen(
|
|
onData,
|
|
onError: onError,
|
|
onDone: onDone,
|
|
cancelOnError: cancelOnError);
|
|
}
|
|
|
|
int available() => _socket.available();
|
|
|
|
List<int> read([int len]) => _socket.read(len);
|
|
|
|
int write(List<int> buffer, [int offset, int count]) =>
|
|
_socket.write(buffer, offset, count);
|
|
|
|
void close() => _socket.close();
|
|
|
|
void shutdown(SocketDirection direction) => _socket.shutdown(direction);
|
|
|
|
int get port => _socket.port;
|
|
|
|
int get remotePort => _socket.remotePort;
|
|
|
|
InternetAddress get address => _socket.address;
|
|
|
|
String get remoteHost => _socket.remoteHost;
|
|
|
|
bool get readEventsEnabled => _readEventsEnabled;
|
|
void set readEventsEnabled(bool value) {
|
|
if (value != _readEventsEnabled) {
|
|
_readEventsEnabled = value;
|
|
if (!_controller.isPaused) _resume();
|
|
}
|
|
}
|
|
|
|
bool get writeEventsEnabled => _writeEventsEnabled;
|
|
void set writeEventsEnabled(bool value) {
|
|
if (value != _writeEventsEnabled) {
|
|
_writeEventsEnabled = value;
|
|
if (!_controller.isPaused) _resume();
|
|
}
|
|
}
|
|
|
|
bool setOption(SocketOption option, bool enabled) =>
|
|
_socket.setOption(option, enabled);
|
|
|
|
_pause() {
|
|
_socket.setListening(read: false, write: false);
|
|
}
|
|
|
|
void _resume() {
|
|
_socket.setListening(read: _readEventsEnabled, write: _writeEventsEnabled);
|
|
}
|
|
|
|
void _onPauseStateChange() {
|
|
if (_controller.isPaused) {
|
|
_pause();
|
|
} else {
|
|
_resume();
|
|
}
|
|
}
|
|
|
|
void _onSubscriptionStateChange() {
|
|
if (_controller.hasListener) {
|
|
_resume();
|
|
} else {
|
|
close();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
patch class ServerSocket {
|
|
/* patch */ static Future<ServerSocket> bind(address,
|
|
int port,
|
|
{int backlog: 0,
|
|
bool v6Only: false}) {
|
|
return _ServerSocket.bind(address, port, backlog, v6Only);
|
|
}
|
|
}
|
|
|
|
class _ServerSocket extends Stream<Socket>
|
|
implements ServerSocket {
|
|
final _socket;
|
|
|
|
static Future<_ServerSocket> bind(address,
|
|
int port,
|
|
int backlog,
|
|
bool v6Only) {
|
|
return _RawServerSocket.bind(address, port, backlog, v6Only)
|
|
.then((socket) => new _ServerSocket(socket));
|
|
}
|
|
|
|
_ServerSocket(this._socket);
|
|
|
|
StreamSubscription<Socket> listen(void onData(Socket event),
|
|
{void onError(error),
|
|
void onDone(),
|
|
bool cancelOnError}) {
|
|
return _socket.map((rawSocket) => new _Socket(rawSocket)).listen(
|
|
onData,
|
|
onError: onError,
|
|
onDone: onDone,
|
|
cancelOnError: cancelOnError);
|
|
}
|
|
|
|
int get port => _socket.port;
|
|
|
|
void close() => _socket.close();
|
|
}
|
|
|
|
|
|
patch class Socket {
|
|
/* patch */ static Future<Socket> connect(host, int port) {
|
|
return RawSocket.connect(host, port).then(
|
|
(socket) => new _Socket(socket));
|
|
}
|
|
}
|
|
|
|
|
|
class _SocketStreamConsumer extends StreamConsumer<List<int>> {
|
|
StreamSubscription subscription;
|
|
final _Socket socket;
|
|
int offset;
|
|
List<int> buffer;
|
|
bool paused = false;
|
|
Completer streamCompleter;
|
|
|
|
_SocketStreamConsumer(this.socket);
|
|
|
|
Future<Socket> addStream(Stream<List<int>> stream) {
|
|
socket._ensureRawSocketSubscription();
|
|
streamCompleter = new Completer<Socket>();
|
|
if (socket._raw != null) {
|
|
subscription = stream.listen(
|
|
(data) {
|
|
assert(!paused);
|
|
assert(buffer == null);
|
|
buffer = data;
|
|
offset = 0;
|
|
write();
|
|
},
|
|
onError: (error) {
|
|
socket._consumerDone();
|
|
done(error);
|
|
},
|
|
onDone: () {
|
|
done();
|
|
},
|
|
cancelOnError: true);
|
|
}
|
|
return streamCompleter.future;
|
|
}
|
|
|
|
Future<Socket> close() {
|
|
socket._consumerDone();
|
|
return new Future.value(socket);
|
|
}
|
|
|
|
void write() {
|
|
try {
|
|
if (subscription == null) return;
|
|
assert(buffer != null);
|
|
// Write as much as possible.
|
|
offset += socket._write(buffer, offset, buffer.length - offset);
|
|
if (offset < buffer.length) {
|
|
if (!paused) {
|
|
paused = true;
|
|
subscription.pause();
|
|
}
|
|
socket._enableWriteEvent();
|
|
} else {
|
|
buffer = null;
|
|
if (paused) {
|
|
paused = false;
|
|
subscription.resume();
|
|
}
|
|
}
|
|
} catch (e) {
|
|
stop();
|
|
socket._consumerDone();
|
|
done(e);
|
|
}
|
|
}
|
|
|
|
void done([error]) {
|
|
if (streamCompleter != null) {
|
|
if (error != null) {
|
|
streamCompleter.completeError(error);
|
|
} else {
|
|
streamCompleter.complete(socket);
|
|
}
|
|
streamCompleter = null;
|
|
}
|
|
}
|
|
|
|
void stop() {
|
|
if (subscription == null) return;
|
|
subscription.cancel();
|
|
subscription = null;
|
|
paused = false;
|
|
socket._disableWriteEvent();
|
|
}
|
|
}
|
|
|
|
|
|
class _Socket extends Stream<List<int>> implements Socket {
|
|
RawSocket _raw; // Set to null when the raw socket is closed.
|
|
bool _closed = false; // Set to true when the raw socket is closed.
|
|
StreamController _controller;
|
|
bool _controllerClosed = false;
|
|
_SocketStreamConsumer _consumer;
|
|
IOSink _sink;
|
|
var _subscription;
|
|
var _detachReady;
|
|
|
|
_Socket(RawSocket this._raw) {
|
|
_controller = new StreamController<List<int>>(sync: true,
|
|
onListen: _onSubscriptionStateChange,
|
|
onCancel: _onSubscriptionStateChange,
|
|
onPause: _onPauseStateChange,
|
|
onResume: _onPauseStateChange);
|
|
_consumer = new _SocketStreamConsumer(this);
|
|
_sink = new IOSink(_consumer);
|
|
|
|
// Disable read events until there is a subscription.
|
|
_raw.readEventsEnabled = false;
|
|
|
|
// Disable write events until the consumer needs it for pending writes.
|
|
_raw.writeEventsEnabled = false;
|
|
}
|
|
|
|
factory _Socket._writePipe([int fd]) {
|
|
return new _Socket(new _RawSocket._writePipe(fd));
|
|
}
|
|
|
|
factory _Socket._readPipe([int fd]) {
|
|
return new _Socket(new _RawSocket._readPipe(fd));
|
|
}
|
|
|
|
_NativeSocket get _nativeSocket => _raw._socket;
|
|
|
|
StreamSubscription<List<int>> listen(void onData(List<int> event),
|
|
{void onError(error),
|
|
void onDone(),
|
|
bool cancelOnError}) {
|
|
return _controller.stream.listen(
|
|
onData,
|
|
onError: onError,
|
|
onDone: onDone,
|
|
cancelOnError: cancelOnError);
|
|
}
|
|
|
|
Encoding get encoding => _sink.encoding;
|
|
|
|
void set encoding(Encoding value) {
|
|
_sink.encoding = value;
|
|
}
|
|
|
|
void write(Object obj) => _sink.write(obj);
|
|
|
|
void writeln([Object obj = ""]) => _sink.writeln(obj);
|
|
|
|
void writeCharCode(int charCode) => _sink.writeCharCode(charCode);
|
|
|
|
void writeAll(Iterable objects, [sep = ""]) => _sink.writeAll(objects, sep);
|
|
|
|
void add(List<int> bytes) => _sink.add(bytes);
|
|
|
|
Future<Socket> addStream(Stream<List<int>> stream) {
|
|
return _sink.addStream(stream);
|
|
}
|
|
|
|
Future<Socket> close() => _sink.close();
|
|
|
|
Future<Socket> get done => _sink.done;
|
|
|
|
void destroy() {
|
|
// Destroy can always be called to get rid of a socket.
|
|
if (_raw == null) return;
|
|
_consumer.stop();
|
|
_closeRawSocket();
|
|
_controllerClosed = true;
|
|
_controller.close();
|
|
}
|
|
|
|
bool setOption(SocketOption option, bool enabled) {
|
|
if (_raw == null) return false;
|
|
return _raw.setOption(option, enabled);
|
|
}
|
|
|
|
int get port => _raw.port;
|
|
String get remoteHost => _raw.remoteHost;
|
|
int get remotePort => _raw.remotePort;
|
|
|
|
Future _detachRaw() {
|
|
_detachReady = new Completer();
|
|
_sink.close();
|
|
return _detachReady.future.then((_) {
|
|
assert(_consumer.buffer == null);
|
|
var raw = _raw;
|
|
_raw = null;
|
|
return [raw, _subscription];
|
|
});
|
|
}
|
|
|
|
// Ensure a subscription on the raw socket. Both the stream and the
|
|
// consumer needs a subscription as they share the error and done
|
|
// events from the raw socket.
|
|
void _ensureRawSocketSubscription() {
|
|
if (_subscription == null && _raw != null) {
|
|
_subscription = _raw.listen(_onData,
|
|
onError: _onError,
|
|
onDone: _onDone,
|
|
cancelOnError: true);
|
|
}
|
|
}
|
|
|
|
_closeRawSocket() {
|
|
var tmp = _raw;
|
|
_raw = null;
|
|
_closed = true;
|
|
tmp.close();
|
|
}
|
|
|
|
void _onSubscriptionStateChange() {
|
|
if (_controller.hasListener) {
|
|
_ensureRawSocketSubscription();
|
|
// Enable read events for providing data to subscription.
|
|
if (_raw != null) {
|
|
_raw.readEventsEnabled = true;
|
|
}
|
|
} else {
|
|
_controllerClosed = true;
|
|
if (_raw != null) {
|
|
_raw.shutdown(SocketDirection.RECEIVE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void _onPauseStateChange() {
|
|
if (_raw != null) {
|
|
_raw.readEventsEnabled = !_controller.isPaused;
|
|
}
|
|
}
|
|
|
|
void _onData(event) {
|
|
switch (event) {
|
|
case RawSocketEvent.READ:
|
|
var buffer = _raw.read();
|
|
if (buffer != null) _controller.add(buffer);
|
|
break;
|
|
case RawSocketEvent.WRITE:
|
|
_consumer.write();
|
|
break;
|
|
case RawSocketEvent.READ_CLOSED:
|
|
_controllerClosed = true;
|
|
_controller.close();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void _onDone() {
|
|
if (!_controllerClosed) {
|
|
_controllerClosed = true;
|
|
_controller.close();
|
|
}
|
|
_consumer.done();
|
|
}
|
|
|
|
void _onError(error) {
|
|
if (!_controllerClosed) {
|
|
_controllerClosed = true;
|
|
_controller.addError(error);
|
|
_controller.close();
|
|
}
|
|
_consumer.done(error);
|
|
}
|
|
|
|
int _write(List<int> data, int offset, int length) =>
|
|
_raw.write(data, offset, length);
|
|
|
|
void _enableWriteEvent() {
|
|
_raw.writeEventsEnabled = true;
|
|
}
|
|
|
|
void _disableWriteEvent() {
|
|
if (_raw != null) {
|
|
_raw.writeEventsEnabled = false;
|
|
}
|
|
}
|
|
|
|
void _consumerDone() {
|
|
if (_detachReady != null) {
|
|
_detachReady.complete(null);
|
|
} else {
|
|
if (_raw != null) {
|
|
_raw.shutdown(SocketDirection.SEND);
|
|
_disableWriteEvent();
|
|
}
|
|
}
|
|
}
|
|
}
|