| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // |
| // Implementation of Socket and RawSocket for Mojo. |
| // |
| |
| patch class Socket { |
| /* patch */ static Future<Socket> connect(host, int port, {sourceAddress}) { |
| return RawSocket.connect(host, port, sourceAddress: sourceAddress).then( |
| (socket) => new _MojoSocket(socket)); |
| } |
| } |
| |
| patch class RawSocket { |
| /* patch */ static Future<RawSocket> connect( |
| host, int port, {sourceAddress}) { |
| return _MojoRawSocket.connect(host, port, sourceAddress); |
| } |
| } |
| |
| class _MojoRawSocket extends Stream<RawSocketEvent> implements RawSocket { |
| StreamController<RawSocketEvent> _controller; |
| final _tcpBoundSocket = new TcpBoundSocketProxy.unbound(); |
| final _tcpConnectedSocket = new TcpConnectedSocketProxy.unbound(); |
| // Constructing a new MojoDataPipe allocates two handles. All failure paths |
| // must be sure that these handles are closed so we do not leak any handles. |
| final _pipeOut = new MojoDataPipe(); |
| bool _outClosed = false; |
| // Constructing a new MojoDataPipe allocates two handles. All failure paths |
| // must be sure that these handles are closed so we do not leak any handles. |
| final _pipeIn = new MojoDataPipe(); |
| bool _inClosed = false; |
| bool _readEventsEnabled = true; |
| bool _writeEventsEnabled = true; |
| MojoEventStream _pipeOutEvents; |
| MojoEventStream _pipeInEvents; |
| InternetAddress _localAddress; |
| int _localPort; |
| InternetAddress _remoteAddress; |
| int _remotePort; |
| var _owner; |
| |
| bool _trace = false; |
| int _traceId; |
| |
| _tracePrint(String message) { |
| assert(_trace); |
| print('${_traceId}: $message'); |
| } |
| |
| _traceProxies() { |
| if (!_trace) { |
| return; |
| } |
| _tracePrint('_tcpBoundSocket handle = ${_tcpBoundSocket.handle}'); |
| _tracePrint('_tcpConnectedSocket handle = ${_tcpConnectedSocket.handle}'); |
| } |
| |
| _tracePipeIn() { |
| if (!_trace) { |
| return; |
| } |
| if (_pipeInEvents != null) { |
| _tracePrint('pipe in consumer handle = ${_pipeInEvents.handle}'); |
| } else { |
| _tracePrint('pipe in consumer handle ${_pipeIn.consumer.handle}'); |
| } |
| _tracePrint('pipe in producer handle = ${_pipeIn.producer.handle}'); |
| } |
| |
| _tracePipeOut() { |
| if (!_trace) { |
| return; |
| } |
| |
| _tracePrint('pipe out consumer handle = ${_pipeOut.consumer.handle}'); |
| if (_pipeOutEvents != null) { |
| _tracePrint('pipe out producer handle = ${_pipeOutEvents.handle}'); |
| } else { |
| _tracePrint('pipe out producer handle = ${_pipeOut.producer.handle}'); |
| } |
| } |
| |
| _tracePipes() { |
| if (!_trace) { |
| return; |
| } |
| _tracePipeIn(); |
| _tracePipeOut(); |
| } |
| |
| _traceLocalAddress() { |
| if (!_trace) { |
| return; |
| } |
| var a = (_localAddress == null) ? |
| '<no local address>' : _localAddress.toString(); |
| var p = (_localPort == null) ? |
| '<no port>' : _localPort.toString(); |
| _tracePrint('local: ${a}:${p}'); |
| } |
| |
| _traceRemoteAddress() { |
| if (!_trace) { |
| return; |
| } |
| var a = (_remoteAddress == null) ? |
| '<no remote address>' : _remoteAddress.toString(); |
| var p = (_remotePort == null) ? |
| '<no port>' : _remotePort.toString(); |
| _tracePrint('remote: ${a}:${p}'); |
| } |
| |
| _traceConnectedSocket() { |
| if (!_trace) { |
| return; |
| } |
| _tracePrint(_tcpConnectedSocket.toString()); |
| } |
| |
| _traceBoundSocket() { |
| if (!_trace) { |
| return; |
| } |
| _tracePrint(_tcpBoundSocket.toString()); |
| } |
| |
| static int _traceIdGenerator = 0; |
| static _enableTrace(_MojoRawSocket rawSocket) { |
| if (rawSocket._trace) { |
| return; |
| } |
| rawSocket._trace = true; |
| rawSocket._traceId = _traceIdGenerator++; |
| rawSocket._tracePrint('Tracing enabled for ${rawSocket._traceId}'); |
| rawSocket._traceLocalAddress(); |
| rawSocket._traceRemoteAddress(); |
| } |
| |
| _MojoRawSocket() { |
| _controller = new StreamController(sync: true, |
| onListen: _onSubscriptionStateChange, |
| onCancel: _onSubscriptionStateChange, |
| onPause: _onPauseStateChange, |
| onResume: _onPauseStateChange); |
| } |
| |
| static Future<_MojoRawSocket> _connect(NetAddress source, |
| NetAddress dest) async { |
| var rawSocket = new _MojoRawSocket(); |
| var networkService = _getNetworkService().ptr; |
| assert(networkService != null); |
| var response = |
| await networkService.createTcpBoundSocket(source, |
| rawSocket._tcpBoundSocket); |
| if (!_NetworkService._okay(response.result)) { |
| rawSocket.close(); |
| _NetworkService._throwOnError(response.result); |
| } |
| |
| rawSocket._traceBoundSocket(); |
| |
| rawSocket._localAddress = |
| _NetworkServiceCodec._fromNetAddress(response.boundTo); |
| rawSocket._localPort = |
| _NetworkServiceCodec._portFromNetAddress(response.boundTo); |
| |
| rawSocket._setupIn(); |
| rawSocket._setupOut(); |
| |
| // connect here. |
| response = |
| await rawSocket._tcpBoundSocket.ptr.connect( |
| dest, |
| rawSocket._pipeOut.consumer, |
| rawSocket._pipeIn.producer, |
| rawSocket._tcpConnectedSocket); |
| |
| rawSocket._remoteAddress = _NetworkServiceCodec._fromNetAddress(dest); |
| rawSocket._remotePort = _NetworkServiceCodec._portFromNetAddress(dest); |
| |
| if (!_NetworkService._okay(response.result)) { |
| rawSocket.close(); |
| _NetworkService._throwOnError(response.result); |
| } |
| |
| rawSocket._traceConnectedSocket(); |
| |
| return rawSocket; |
| } |
| |
| static Future<RawSocket> connect(host, int port, sourceAddress) async { |
| if (sourceAddress != null && sourceAddress is! _InternetAddress) { |
| if (sourceAddress is String) { |
| sourceAddress = new InternetAddress(sourceAddress); |
| } |
| } |
| var sourceNetAddress; |
| if (sourceAddress != null) { |
| sourceNetAddress = |
| _NetworkServiceCodec._fromInternetAddress(sourceAddress); |
| } else { |
| // TODO(johnmccutchan): Is it safe to assume IPv4? |
| sourceNetAddress = _NetworkService._localhostIpv4(); |
| } |
| if (host is _InternetAddress) { |
| var destinationNetAddress = |
| _NetworkServiceCodec._fromInternetAddress(host, port); |
| return _connect(sourceNetAddress, destinationNetAddress); |
| } else { |
| // TODO(johnmccutchan): Use host resolver and try all results. |
| // For now, connect to LOOPBACK_IPV4 with specified port. |
| var destinationNetAddress = _NetworkService._localhostIpv4(port); |
| return _connect(sourceNetAddress, destinationNetAddress); |
| } |
| } |
| |
| int available() { |
| return _pipeIn.consumer.query(); |
| } |
| |
| Future<_MojoRawSocket> close() async { |
| await _tcpBoundSocket.close(); |
| await _tcpConnectedSocket.close(); |
| _shutdownIn(); |
| _shutdownOut(); |
| return this; |
| } |
| |
| void destroy() { |
| _tcpConnectedSocket.close(immediate: true); |
| _tcpBoundSocket.close(immediate: true); |
| _shutdownIn(true); |
| _shutdownOut(true); |
| } |
| |
| bool setOption(SocketOption option, bool enabled) { |
| // TODO(johnmccutchan): Implement. |
| return false; |
| } |
| |
| _onInputData(List<int> event) { |
| if (_inClosed) { |
| return; |
| } |
| var signalsWatched = new MojoHandleSignals(event[0]); |
| var signalsReceived = new MojoHandleSignals(event[1]); |
| if (_trace) { |
| _tracePrint('<- IN: ${signalsReceived}'); |
| } |
| if (signalsReceived.isReadable) { |
| if (_trace) { |
| _tracePrint('<- READ'); |
| } |
| _controller.add(RawSocketEvent.READ); |
| } |
| if (signalsReceived.isPeerClosed) { |
| if (_trace) { |
| _tracePrint('<- READ_CLOSED'); |
| } |
| _controller.add(RawSocketEvent.READ_CLOSED); |
| // Once we are closed, stop reporting events. |
| _inClosed = true; |
| return; |
| } |
| } |
| |
| _onInputError(e, st) { |
| _controller.addError(e); |
| _onInputDone(); |
| } |
| |
| _onInputDone() { |
| if (_inClosed) { |
| return; |
| } |
| if (_trace) { |
| _tracePrint('<- READ_CLOSED (done)'); |
| } |
| _controller.add(RawSocketEvent.READ_CLOSED); |
| _inClosed = true; |
| } |
| |
| _onOutputData(List<int> event) { |
| if (_outClosed) { |
| return; |
| } |
| var signalsWatched = new MojoHandleSignals(event[0]); |
| var signalsReceived = new MojoHandleSignals(event[1]); |
| if (_trace) { |
| _tracePrint('<- OUT: ${signalsReceived}'); |
| } |
| if (signalsReceived.isPeerClosed) { |
| if (_trace) { |
| _tracePrint('<- CLOSED'); |
| } |
| _controller.add(RawSocketEvent.CLOSED); |
| // Once we are closed, stop reporting events. |
| _outClosed = true; |
| return; |
| } |
| if (signalsReceived.isWritable) { |
| if (_trace) { |
| _tracePrint('<- WRITE'); |
| } |
| _controller.add(RawSocketEvent.WRITE); |
| } |
| } |
| |
| _onOutputError(e, st) { |
| _controller.addError(e); |
| _onOutputDone(); |
| } |
| |
| _onOutputDone() { |
| if (_outClosed) { |
| return; |
| } |
| if (_trace) { |
| _tracePrint('<- CLOSED (done)'); |
| } |
| _controller.add(RawSocketEvent.CLOSED); |
| _outClosed = true; |
| } |
| |
| _setupIn() { |
| assert(_pipeInEvents == null); |
| _pipeInEvents = new MojoEventStream(_pipeIn.consumer.handle, |
| MojoHandleSignals.READABLE + |
| MojoHandleSignals.PEER_CLOSED); |
| _pipeInEvents.listen(_onInputData, |
| onError: _onInputError, |
| onDone: _onInputDone); |
| } |
| |
| _setupOut() { |
| assert(_pipeOutEvents == null); |
| _pipeOutEvents = new MojoEventStream(_pipeOut.producer.handle, |
| MojoHandleSignals.WRITABLE + |
| MojoHandleSignals.PEER_CLOSED); |
| _pipeOutEvents.listen(_onOutputData, |
| onError: _onOutputError, |
| onDone: _onOutputDone); |
| } |
| |
| _shutdownIn([bool force = false]) { |
| _inClosed = true; |
| if (_trace) { |
| _tracePrint('shutdown IN'); |
| _tracePipeIn(); |
| } |
| if (_pipeInEvents != null) { |
| if (force) { |
| _pipeInEvents.close(immediate: true); |
| } else { |
| _pipeInEvents.close(); |
| } |
| } else { |
| _pipeIn.consumer.handle.close(); |
| } |
| _pipeIn.producer.handle.close(); |
| _tracePipeIn(); |
| } |
| |
| _shutdownOut([bool force = false]) { |
| _outClosed = true; |
| if (_trace) { |
| _tracePrint('shutdown OUT'); |
| _tracePipeOut(); |
| } |
| if (_pipeOutEvents != null) { |
| if (force) { |
| _pipeOutEvents.close(immediate: true); |
| } else { |
| _pipeOutEvents.close(); |
| } |
| } else { |
| _pipeOut.producer.handle.close(); |
| } |
| _pipeOut.consumer.handle.close(); |
| _tracePipeOut(); |
| } |
| |
| shutdown(SocketDirection direction) { |
| if (direction == SocketDirection.RECEIVE) { |
| _shutdownIn(); |
| } else if (direction == SocketDirection.SEND) { |
| _shutdownOut(); |
| } else { |
| _shutdownIn(); |
| _shutdownOut(); |
| } |
| } |
| |
| List<int> read([int len]) { |
| var bytesAvailable = available(); |
| if (bytesAvailable == 0) { |
| return null; |
| } |
| if (len == null) { |
| len = bytesAvailable; |
| } else { |
| len = bytesAvailable < len ? bytesAvailable : len; |
| } |
| var bytes = new Uint8List(len); |
| var bytesRead = _pipeIn.consumer.read(bytes.buffer.asByteData(), len); |
| assert(bytesRead == len); |
| if (_trace) { |
| _tracePrint('read $bytesRead bytes.'); |
| } |
| if (!_controller.isPaused) { |
| _resume(); |
| } |
| return bytes; |
| } |
| |
| int write(List<int> buffer, [int offset = 0, int count]) { |
| if (buffer == null) { |
| return 0; |
| } |
| if (count == null) { |
| if (offset > buffer.length) { |
| throw new RangeError.value(offset); |
| } |
| count = buffer.length - offset; |
| } |
| if (offset < 0) { |
| throw new RangeError.value(offset); |
| } |
| if (count < 0) { |
| throw new RangeError.value(count); |
| } |
| if ((offset + count) > buffer.length) { |
| throw new RangeError.value(offset + count); |
| } |
| if (offset is! int || count is! int) { |
| throw new ArgumentError("Invalid arguments to write on Socket"); |
| } |
| if (count == 0) { |
| return; |
| } |
| var bytes; |
| if (buffer is Uint8List) { |
| bytes = buffer; |
| } else { |
| bytes = new Uint8List.fromList(buffer); |
| } |
| var byteData = new ByteData.view(bytes.buffer, offset); |
| var bytesWritten = _pipeOut.producer.write(byteData, count); |
| if (_trace) { |
| _tracePrint('wrote $bytesWritten bytes.'); |
| } |
| if (!_controller.isPaused) { |
| _resume(); |
| } |
| return bytesWritten; |
| } |
| |
| InternetAddress get address => _localAddress; |
| int get port => _localPort; |
| InternetAddress get remoteAddress => _remoteAddress; |
| int get remotePort => _remotePort; |
| |
| bool get readEventsEnabled => _readEventsEnabled; |
| void set readEventsEnabled(bool value) { |
| if (value != _readEventsEnabled) { |
| _readEventsEnabled = value; |
| if (_trace) { |
| _tracePrint('read events enabled: $_readEventsEnabled'); |
| } |
| if (!_controller.isPaused) { |
| _resume(); |
| } |
| } |
| } |
| |
| bool get writeEventsEnabled => _writeEventsEnabled; |
| void set writeEventsEnabled(bool value) { |
| if (value != _writeEventsEnabled) { |
| _writeEventsEnabled = value; |
| if (_trace) { |
| _tracePrint('write events enabled: $_writeEventsEnabled'); |
| } |
| if (!_controller.isPaused) { |
| _resume(); |
| } |
| } |
| } |
| |
| StreamSubscription<RawSocketEvent> listen(void onData(RawSocketEvent event), |
| {Function onError, |
| void onDone(), |
| bool cancelOnError}) { |
| return _controller.stream.listen(onData, onError: onError, onDone: onDone, |
| cancelOnError: cancelOnError); |
| } |
| |
| |
| static _enableReadEvents(MojoEventStream stream) { |
| if (stream == null) { |
| return; |
| } |
| stream.enableSignals(MojoHandleSignals.PEER_CLOSED + |
| MojoHandleSignals.READABLE); |
| } |
| |
| static _enableWriteEvents(MojoEventStream stream) { |
| if (stream == null) { |
| return; |
| } |
| stream.enableSignals(MojoHandleSignals.PEER_CLOSED + |
| MojoHandleSignals.WRITABLE); |
| } |
| |
| static _disableEvents(MojoEventStream stream) { |
| if (stream == null) { |
| return; |
| } |
| stream.enableSignals(MojoHandleSignals.PEER_CLOSED); |
| } |
| |
| _pause() { |
| _disableEvents(_pipeInEvents); |
| _disableEvents(_pipeOutEvents); |
| } |
| |
| void _resume() { |
| if (_pipeInEvents != null) { |
| if (_readEventsEnabled) { |
| _enableReadEvents(_pipeInEvents); |
| } else { |
| _disableEvents(_pipeInEvents); |
| } |
| } |
| |
| if (_pipeOutEvents != null) { |
| if (_writeEventsEnabled) { |
| _enableWriteEvents(_pipeOutEvents); |
| } else { |
| _disableEvents(_pipeOutEvents); |
| } |
| } |
| } |
| |
| void _onPauseStateChange() { |
| if (_controller.isPaused) { |
| _pause(); |
| } else { |
| _resume(); |
| } |
| } |
| |
| void _onSubscriptionStateChange() { |
| if (_controller.hasListener) { |
| _resume(); |
| } else { |
| _socket.close(); |
| } |
| } |
| } |
| |
| class _SocketStreamConsumer extends StreamConsumer<List<int>> { |
| StreamSubscription subscription; |
| final _MojoSocket 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; |
| try { |
| write(); |
| } catch (e) { |
| socket.destroy(); |
| stop(); |
| done(e); |
| } |
| }, |
| onError: (error, [stackTrace]) { |
| socket.destroy(); |
| done(error, stackTrace); |
| }, |
| onDone: () { |
| done(); |
| }, |
| cancelOnError: true); |
| } |
| return streamCompleter.future; |
| } |
| |
| Future<Socket> close() { |
| socket._consumerDone(); |
| return new Future.value(socket); |
| } |
| |
| void write() { |
| if (subscription == null) { |
| return; |
| } |
| if (buffer == 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(); |
| } |
| } |
| } |
| |
| void done([error, stackTrace]) { |
| if (streamCompleter != null) { |
| if (error != null) { |
| streamCompleter.completeError(error, stackTrace); |
| } else { |
| streamCompleter.complete(socket); |
| } |
| streamCompleter = null; |
| } |
| } |
| |
| void stop() { |
| if (subscription == null) { |
| return; |
| } |
| subscription.cancel(); |
| subscription = null; |
| paused = false; |
| socket._disableWriteEvent(); |
| } |
| } |
| |
| class _MojoSocket extends Stream<List<int>> implements Socket { |
| _MojoRawSocket _raw; |
| final int _port; |
| final InternetAddress _address; |
| final int _remotePort; |
| final InternetAddress _remoteAddress; |
| bool _closed = false; |
| StreamController _controller; |
| bool _controllerClosed = false; |
| _SocketStreamConsumer _consumer; |
| IOSink _sink; |
| var _subscription; |
| var _detachReady; |
| |
| |
| _MojoSocket(rawSocket) |
| : _raw = rawSocket, |
| _port = rawSocket.port, |
| _address = rawSocket.address, |
| _remotePort = rawSocket.remotePort, |
| _remoteAddress = rawSocket.remoteAddress { |
| _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; |
| } |
| |
| StreamSubscription<List<int>> listen(void onData(List<int> event), |
| {Function onError, |
| 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> flush() => _sink.flush(); |
| |
| 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; |
| } |
| _raw._shutdownIn(true); |
| _raw._shutdownOut(true); |
| _closeRawSocket(true); |
| _consumer.stop(); |
| _controllerClosed = true; |
| _controller.close(); |
| } |
| |
| bool setOption(SocketOption option, bool enabled) { |
| if (_raw == null) { |
| return false; |
| } |
| return _raw.setOption(option, enabled); |
| } |
| |
| int get port => _port; |
| InternetAddress get address => _address; |
| int get remotePort => _remotePort; |
| InternetAddress get remoteAddress => _remoteAddress; |
| |
| // 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(bool force) { |
| var tmp = _raw; |
| _raw = null; |
| _closed = true; |
| if (force) { |
| tmp.destroy(); |
| } else { |
| 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: |
| if (_raw != null) { |
| 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, stackTrace) { |
| if (!_controllerClosed) { |
| _controllerClosed = true; |
| _controller.addError(error, stackTrace); |
| _controller.close(); |
| } |
| _consumer.done(error, stackTrace); |
| } |
| |
| 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(); |
| } |
| } |
| } |
| |
| Map _toJSON(bool ref) => _raw._toJSON(ref); |
| void set _owner(owner) { _raw._owner = owner; } |
| } |