blob: 46e83151be846b57ac8712bf50c9217e07466923 [file] [log] [blame]
// 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.
part of "common_patch.dart";
@patch
class RawServerSocket {
@patch
static Future<RawServerSocket> bind(
address,
int port, {
int backlog = 0,
bool v6Only = false,
bool shared = false,
}) {
return _RawServerSocket.bind(address, port, backlog, v6Only, shared);
}
}
@patch
class RawSocket {
@patch
static Future<RawSocket> connect(
dynamic host,
int port, {
dynamic sourceAddress,
int sourcePort = 0,
Duration? timeout,
}) {
return _RawSocket.connect(host, port, sourceAddress, sourcePort, timeout);
}
@patch
static Future<ConnectionTask<RawSocket>> startConnect(
dynamic host,
int port, {
dynamic sourceAddress,
int sourcePort = 0,
}) {
return _RawSocket.startConnect(host, port, sourceAddress, sourcePort);
}
}
@patch
class RawSocketOption {
static final List<int?> _optionsCache = List<int?>.filled(
_RawSocketOptions.values.length,
null,
);
@patch
static int _getOptionValue(int key) {
if (key > _RawSocketOptions.values.length) {
throw ArgumentError.value(key, 'key');
}
return _optionsCache[key] ??= _getNativeOptionValue(key);
}
@pragma("vm:external-name", "RawSocketOption_GetOptionValue")
external static int _getNativeOptionValue(int key);
}
@patch
class InternetAddress {
@patch
static InternetAddress get loopbackIPv4 {
return _InternetAddress.loopbackIPv4;
}
@patch
static InternetAddress get loopbackIPv6 {
return _InternetAddress.loopbackIPv6;
}
@patch
static InternetAddress get anyIPv4 {
return _InternetAddress.anyIPv4;
}
@patch
static InternetAddress get anyIPv6 {
return _InternetAddress.anyIPv6;
}
@patch
factory InternetAddress(String address, {InternetAddressType? type}) {
return _InternetAddress.fromString(address, type: type);
}
@patch
factory InternetAddress.fromRawAddress(
Uint8List rawAddress, {
InternetAddressType? type,
}) {
return _InternetAddress.fromRawAddress(rawAddress, type: type);
}
@patch
static Future<List<InternetAddress>> lookup(
String host, {
InternetAddressType type = InternetAddressType.any,
}) {
return _NativeSocket.lookup(host, type: type);
}
@patch
static InternetAddress _cloneWithNewHost(
InternetAddress address,
String host,
) {
return (address as _InternetAddress)._cloneWithNewHost(host);
}
@patch
static InternetAddress? tryParse(String address) {
return _InternetAddress.tryParse(address);
}
}
@patch
class NetworkInterface {
@patch
static bool get listSupported => true;
@patch
static Future<List<NetworkInterface>> list({
bool includeLoopback = false,
bool includeLinkLocal = false,
InternetAddressType type = InternetAddressType.any,
}) {
return _NativeSocket.listInterfaces(
includeLoopback: includeLoopback,
includeLinkLocal: includeLinkLocal,
type: type,
);
}
}
void _throwOnBadPort(int port) {
// TODO(40614): Remove once non-nullability is sound.
ArgumentError.checkNotNull(port, "port");
if ((port < 0) || (port > 0xFFFF)) {
throw ArgumentError("Invalid port $port");
}
}
void _throwOnBadTtl(int ttl) {
// TODO(40614): Remove once non-nullability is sound.
ArgumentError.checkNotNull(ttl, "ttl");
if (ttl < 1 || ttl > 255) {
throw ArgumentError('Invalid ttl $ttl');
}
}
class _InternetAddress implements InternetAddress {
static const int _addressLoopbackIPv4 = 0;
static const int _addressLoopbackIPv6 = 1;
static const int _addressAnyIPv4 = 2;
static const int _addressAnyIPv6 = 3;
static const int _IPv4AddrLength = 4;
static const int _IPv6AddrLength = 16;
static _InternetAddress loopbackIPv4 = _InternetAddress.fixed(
_addressLoopbackIPv4,
);
static _InternetAddress loopbackIPv6 = _InternetAddress.fixed(
_addressLoopbackIPv6,
);
static _InternetAddress anyIPv4 = _InternetAddress.fixed(_addressAnyIPv4);
static _InternetAddress anyIPv6 = _InternetAddress.fixed(_addressAnyIPv6);
final String address;
final String? _host;
final Uint8List _in_addr;
final int _scope_id;
final InternetAddressType type;
String get host => _host ?? address;
Uint8List get rawAddress => Uint8List.fromList(_in_addr);
bool get isLoopback {
switch (type) {
case InternetAddressType.IPv4:
return _in_addr[0] == 127;
case InternetAddressType.IPv6:
for (int i = 0; i < _IPv6AddrLength - 1; i++) {
if (_in_addr[i] != 0) return false;
}
return _in_addr[_IPv6AddrLength - 1] == 1;
case InternetAddressType.unix:
return false;
}
throw UnsupportedError("Unexpected address type $type");
}
bool get isLinkLocal {
switch (type) {
case InternetAddressType.IPv4:
// Checking for 169.254.0.0/16.
return _in_addr[0] == 169 && _in_addr[1] == 254;
case InternetAddressType.IPv6:
// Checking for fe80::/10.
return _in_addr[0] == 0xFE && (_in_addr[1] & 0xB0) == 0x80;
case InternetAddressType.unix:
return false;
}
throw UnsupportedError("Unexpected address type $type");
}
bool get isMulticast {
switch (type) {
case InternetAddressType.IPv4:
// Checking for 224.0.0.0 through 239.255.255.255.
return _in_addr[0] >= 224 && _in_addr[0] < 240;
case InternetAddressType.IPv6:
// Checking for ff00::/8.
return _in_addr[0] == 0xFF;
case InternetAddressType.unix:
return false;
}
throw UnsupportedError("Unexpected address type $type");
}
Future<InternetAddress> reverse() {
if (type == InternetAddressType.unix) {
return Future.value(this);
}
return _NativeSocket.reverseLookup(this);
}
_InternetAddress(
this.type,
this.address,
this._host,
this._in_addr, [
this._scope_id = 0,
]);
static Object _parseAddressString(
String address, {
InternetAddressType? type,
}) {
// TODO(40614): Remove once non-nullability is sound.
ArgumentError.checkNotNull(address, 'address');
if (type == InternetAddressType.unix) {
var rawAddress = FileSystemEntity._toUtf8Array(address);
return _InternetAddress(
InternetAddressType.unix,
address,
null,
rawAddress,
);
} else {
int index = address.indexOf('%');
String originalAddress = address;
String? scopeID;
if (index > 0) {
scopeID = address.substring(index, address.length);
address = address.substring(0, index);
}
var inAddr = _parse(address);
if (inAddr == null) {
return ArgumentError('Invalid internet address $address');
}
InternetAddressType type = inAddr.length == _IPv4AddrLength
? InternetAddressType.IPv4
: InternetAddressType.IPv6;
if (scopeID != null && scopeID.length > 0) {
if (type != InternetAddressType.IPv6) {
return ArgumentError.value(
address,
'address',
'IPv4 addresses cannot have a scope ID',
);
}
final scopeID = _parseScopedLinkLocalAddress(originalAddress);
if (scopeID is int) {
return _InternetAddress(
InternetAddressType.IPv6,
originalAddress,
null,
inAddr,
scopeID,
);
} else {
return ArgumentError.value(
address,
'address',
'Invalid IPv6 address with scope ID',
);
}
}
return _InternetAddress(type, originalAddress, null, inAddr, 0);
}
}
factory _InternetAddress.fromString(
String address, {
InternetAddressType? type,
}) {
final parsedAddress = _parseAddressString(address, type: type);
if (parsedAddress is _InternetAddress) {
return parsedAddress;
} else {
assert(parsedAddress is ArgumentError);
throw parsedAddress;
}
}
static _InternetAddress? tryParse(String address) {
checkNotNullable(address, "address");
final parsedAddress = _parseAddressString(address);
if (parsedAddress is _InternetAddress) {
return parsedAddress;
} else {
assert(parsedAddress is ArgumentError);
return null;
}
}
factory _InternetAddress.fromRawAddress(
Uint8List rawAddress, {
InternetAddressType? type,
}) {
if (type == InternetAddressType.unix) {
ArgumentError.checkNotNull(rawAddress, 'rawAddress');
var rawPath = FileSystemEntity._toNullTerminatedUtf8Array(rawAddress);
var address = FileSystemEntity._toStringFromUtf8Array(rawAddress);
return _InternetAddress(InternetAddressType.unix, address, null, rawPath);
} else {
int type = -1;
if (rawAddress.length == _IPv4AddrLength) {
type = 0;
} else {
if (rawAddress.length != _IPv6AddrLength) {
throw ArgumentError("Invalid internet address ${rawAddress}");
}
type = 1;
}
var address = _rawAddrToString(rawAddress);
return _InternetAddress(
InternetAddressType._from(type),
address,
null,
rawAddress,
);
}
}
factory _InternetAddress.fixed(int id) {
switch (id) {
case _addressLoopbackIPv4:
var in_addr = Uint8List(_IPv4AddrLength);
in_addr[0] = 127;
in_addr[_IPv4AddrLength - 1] = 1;
return _InternetAddress(
InternetAddressType.IPv4,
"127.0.0.1",
null,
in_addr,
);
case _addressLoopbackIPv6:
var in_addr = Uint8List(_IPv6AddrLength);
in_addr[_IPv6AddrLength - 1] = 1;
return _InternetAddress(InternetAddressType.IPv6, "::1", null, in_addr);
case _addressAnyIPv4:
var in_addr = Uint8List(_IPv4AddrLength);
return _InternetAddress(
InternetAddressType.IPv4,
"0.0.0.0",
"0.0.0.0",
in_addr,
);
case _addressAnyIPv6:
var in_addr = Uint8List(_IPv6AddrLength);
return _InternetAddress(InternetAddressType.IPv6, "::", "::", in_addr);
default:
assert(false);
throw ArgumentError();
}
}
// Create a clone of this _InternetAddress replacing the host.
_InternetAddress _cloneWithNewHost(String host) {
return _InternetAddress(
type,
address,
host,
Uint8List.fromList(_in_addr),
_scope_id,
);
}
bool operator ==(other) {
if (!(other is _InternetAddress)) return false;
if (other.type != type) return false;
if (type == InternetAddressType.unix) {
return address == other.address;
}
bool equals = true;
for (int i = 0; i < _in_addr.length && equals; i++) {
equals = other._in_addr[i] == _in_addr[i];
}
return equals;
}
int get hashCode {
if (type == InternetAddressType.unix) {
return address.hashCode;
}
int result = 1;
for (int i = 0; i < _in_addr.length; i++) {
result = (result * 31 + _in_addr[i]) & 0x3FFFFFFF;
}
return result;
}
String toString() {
return "InternetAddress('$address', ${type.name})";
}
@pragma("vm:external-name", "InternetAddress_RawAddrToString")
external static String _rawAddrToString(Uint8List address);
@pragma("vm:external-name", "InternetAddress_ParseScopedLinkLocalAddress")
external static dynamic /* int | OSError */ _parseScopedLinkLocalAddress(
String address,
);
@pragma("vm:external-name", "InternetAddress_Parse")
external static Uint8List? _parse(String address);
}
class _NetworkInterface implements NetworkInterface {
final String name;
final int index;
final List<InternetAddress> addresses = [];
_NetworkInterface(this.name, this.index);
String toString() {
return "NetworkInterface('$name', $addresses)";
}
}
// The NativeFieldWrapperClass1 cannot be used with a mixin, due to missing
// implicit constructor.
base class _NativeSocketNativeWrapper extends NativeFieldWrapperClass1 {}
/// Returns error code that corresponds to EINPROGRESS OS error.
@pragma("vm:external-name", "OSError_inProgressErrorCode")
external int get _inProgressErrorCode;
// The _NativeSocket class encapsulates an OS socket.
base class _NativeSocket extends _NativeSocketNativeWrapper
with _ServiceObject {
// 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 readEvent = 0;
static const int writeEvent = 1;
static const int errorEvent = 2;
static const int closedEvent = 3;
static const int destroyedEvent = 4;
static const int firstEvent = readEvent;
static const int lastEvent = destroyedEvent;
static const int eventCount = lastEvent - firstEvent + 1;
static const int closeCommand = 8;
static const int shutdownReadCommand = 9;
static const int shutdownWriteCommand = 10;
// The lower bits of returnTokenCommand messages contains the number
// of tokens returned.
static const int returnTokenCommand = 11;
static const int setEventMaskCommand = 12;
static const int firstCommand = closeCommand;
static const int lastCommand = setEventMaskCommand;
// Type flag send to the eventhandler providing additional
// information on the type of the file descriptor.
static const int listeningSocket = 16;
static const int pipeSocket = 17;
static const int typeNormalSocket = 0;
static const int typeListeningSocket = 1 << listeningSocket;
static const int typePipe = 1 << pipeSocket;
static const int typeTypeMask = typeListeningSocket | pipeSocket;
// Protocol flags.
// Keep in sync with SocketType enum in socket.h.
static const int tcpSocket = 18;
static const int udpSocket = 19;
static const int internalSocket = 20;
static const int internalSignalSocket = 21;
static const int typeTcpSocket = 1 << tcpSocket;
static const int typeUdpSocket = 1 << udpSocket;
static const int typeInternalSocket = 1 << internalSocket;
static const int typeInternalSignalSocket = 1 << internalSignalSocket;
static const int typeProtocolMask =
typeTcpSocket |
typeUdpSocket |
typeInternalSocket |
typeInternalSignalSocket;
// Native port messages.
static const hostNameLookupMessage = 0;
static const listInterfacesMessage = 1;
static const reverseLookupMessage = 2;
// Protocol flags.
static const int protocolIPv4 = 1 << 0;
static const int protocolIPv6 = 1 << 1;
static const int normalTokenBatchSize = 8;
static const int listeningTokenBatchSize = 2;
static const Duration _retryDuration = Duration(milliseconds: 250);
static const Duration _retryDurationLoopback = Duration(milliseconds: 25);
// Socket close state
bool isClosed = false;
bool isClosing = false;
bool isClosedRead = false;
bool closedReadEventSent = false;
bool isClosedWrite = false;
Completer closeCompleter = Completer.sync();
// Handlers and receive port for socket events from the event handler.
void Function()? readEventHandler;
void Function()? writeEventHandler;
void Function(Object e, StackTrace? st)? errorEventHandler;
void Function()? closedEventHandler;
void Function()? destroyedEventHandler;
RawReceivePort? eventPort;
bool flagsSent = false;
// The type flags for this socket.
final int typeFlags;
// Holds the port of the socket, 0 if not known.
int localPort = 0;
// Holds the address used to connect or bind the socket.
late InternetAddress localAddress;
// The size of data that is ready to be read, for TCP sockets.
// This might be out-of-date when Read is called.
// The number of pending connections, for Listening sockets.
int available = 0;
// Only used for UDP sockets.
bool _availableDatagram = false;
// The number of incoming connections for Listening socket.
int connections = 0;
// The count of received event from eventhandler.
int tokens = 0;
bool sendReadEvents = false;
bool readEventIssued = false;
bool sendWriteEvents = false;
bool writeEventIssued = false;
bool writeAvailable = false;
// The owner object is the object that the Socket is being used by, e.g.
// a HttpServer, a WebSocket connection, a process pipe, etc.
Object? owner;
static Future<List<InternetAddress>> lookup(
String host, {
InternetAddressType type = InternetAddressType.any,
}) {
return _IOService._dispatch(_IOService.socketLookup, [
host,
type._value,
]).then((response) {
if (isErrorResponse(response)) {
throw createError(response, "Failed host lookup: '$host'");
}
return [
for (List<Object?> result in (response as List).skip(1))
_InternetAddress(
InternetAddressType._from(result[0] as int),
result[1] as String,
host,
result[2] as Uint8List,
result[3] as int,
),
];
});
}
static Future<InternetAddress> reverseLookup(InternetAddress addr) {
return _IOService._dispatch(_IOService.socketReverseLookup, [
(addr as _InternetAddress)._in_addr,
]).then((response) {
if (isErrorResponse(response)) {
throw createError(response, "Failed reverse host lookup", addr);
} else {
return addr._cloneWithNewHost(response as String);
}
});
}
static Future<List<NetworkInterface>> listInterfaces({
bool includeLoopback = false,
bool includeLinkLocal = false,
InternetAddressType type = InternetAddressType.any,
}) {
return _IOService._dispatch(_IOService.socketListInterfaces, [
type._value,
]).then((response) {
if (isErrorResponse(response)) {
throw createError(response, "Failed listing interfaces");
} else {
var map = (response as List).skip(1).fold(
Map<String, NetworkInterface>(),
(map, result) {
List<Object?> resultList = result as List<Object?>;
var type = InternetAddressType._from(resultList[0] as int);
var name = resultList[3] as String;
var index = resultList[4] as int;
var address = _InternetAddress(
type,
resultList[1] as String,
"",
resultList[2] as Uint8List,
);
if (!includeLinkLocal && address.isLinkLocal) return map;
if (!includeLoopback && address.isLoopback) return map;
map.putIfAbsent(name, () => _NetworkInterface(name, index));
(map[name] as _NetworkInterface).addresses.add(address);
return map;
},
);
return map.values.toList();
}
});
}
static String escapeLinkLocalAddress(String host) {
// if the host contains escape, host is an IPv6 address with scope ID.
// Remove '25' before feeding into native calls.
int index = host.indexOf('%');
if (index >= 0) {
if (!checkLinkLocalAddress(host)) {
// The only well defined usage is link-local address. Checks Section 4 of https://tools.ietf.org/html/rfc6874.
// If it is not a valid link-local address and contains escape character, throw an exception.
throw FormatException(
'${host} is not a valid link-local address but contains %. Scope id should be used as part of link-local address.',
host,
index,
);
}
if (host.startsWith("25", index + 1)) {
// Remove '25' after '%' if present
host = host.replaceRange(index + 1, index + 3, '');
}
}
return host;
}
static bool checkLinkLocalAddress(String host) {
// The shortest possible link-local address is [fe80::1]
if (host.length < 7) return false;
var char = host[2];
return host.startsWith('fe') &&
(char == '8' || char == '9' || char == 'a' || char == 'b');
}
/// Explicitly makes two separate OS lookup requests: first for IPv4 then,
/// after a short delay, for IPv6.
///
/// This avoids making a single OS lookup request that internally does both
/// IPv4 and IPv6 lookups together, which will be as slow as the slowest
/// lookup. Some broken DNS servers do not support IPv6 AAAA records and
/// will cause the IPv6 lookup to timeout.
///
/// The IPv4 lookup is done first because, in practice, IPv4 traffic is
/// routed more reliably.
///
/// See https://dartbug.com/50868.
static Stream<List<InternetAddress>> staggeredLookup(
String host,
_InternetAddress? source,
) {
final controller = StreamController<List<InternetAddress>>(sync: true);
controller.onListen = () {
// Completed when there are no further addresses, or when the returned
// stream is canceled,
// The latter signals that no further addresses are needed.
// When both completers are completed, one way or another, the stream is
// closed.
final ipv4Completer = Completer<void>();
final ipv6Completer = Completer<void>();
// Only report an error if no address lookups were sucessful.
var anySuccess = false;
void lookupAddresses(
InternetAddressType type,
Completer<void> done,
) async {
try {
final addresses = await lookup(host, type: type);
anySuccess = true;
if (done.isCompleted) {
// By the time lookup is done, [connectNext] might have
// been able to connect to one of the resolved addresses.
return;
}
controller.add(addresses);
done.complete();
} catch (e, st) {
if (done.isCompleted) {
// By the time lookup is done, [connectNext] might have
// been able to connect to one of the resolved addresses.
return;
}
done.completeError(e, st);
}
}
const concurrentLookupDelay = Duration(milliseconds: 10);
Timer? ipv6LookupDelay;
lookupAddresses(InternetAddressType.IPv4, ipv4Completer);
if (source != null && source.type == InternetAddressType.IPv4) {
// Binding to an IPv4 address and connecting to an IPv6 address will
// never work.
ipv6Completer.complete();
} else {
// Introduce a delay before IPv6 lookup in order to favor IPv4.
ipv6LookupDelay = Timer(
concurrentLookupDelay,
() => lookupAddresses(InternetAddressType.IPv6, ipv6Completer),
);
}
Future.wait([ipv4Completer.future, ipv6Completer.future]).then(
(_) => controller.close(),
onError: (e, st) {
if (!anySuccess) {
controller.addError(e, st);
}
controller.close();
},
);
controller.onCancel = () {
// This is invoked when [connectNext] managed to connect to one of the
// looked-up addresses at which point we want to stop looking up
// the addresses.
if (!ipv4Completer.isCompleted) ipv4Completer.complete();
if (!ipv6Completer.isCompleted) ipv6Completer.complete();
ipv6LookupDelay?.cancel();
};
};
return controller.stream;
}
static Future<ConnectionTask<_NativeSocket>> startConnect(
dynamic host,
int port,
dynamic sourceAddress,
int sourcePort,
) {
// Looks up [sourceAddress] to one or more IP addresses,
// then tries connecting to each one until a connection succeeds.
// Attempts are staggered by a minimum delay, so a new
// attempt isn't made until either a previous attempt has *failed*,
// or the delay has passed.
// This ensures that at most *n* uncompleted connections can be
// active after *n* × *delay* time has passed.
if (host is String) {
host = escapeLinkLocalAddress(host);
}
_throwOnBadPort(port);
_throwOnBadPort(sourcePort);
_InternetAddress? source;
if (sourceAddress != null) {
if (sourceAddress is _InternetAddress) {
// A host of type [String] is interpreted to be a an internet host
// (or numeric IP e.g. '127.0.0.1'), which is never reachable using
// a Unix Domain Socket.
if (host is String && sourceAddress.type == InternetAddressType.unix) {
// ArgumentError would be better but changing it would not be
// backwards-compatible.
throw SocketException(
"Cannot connect to an internet host using a unix domain socket",
);
}
source = sourceAddress;
} else if (sourceAddress is String) {
source = _InternetAddress.fromString(sourceAddress);
} else {
throw ArgumentError.value(
sourceAddress,
"sourceAddress",
"Must be a string or native InternetAddress",
);
}
}
final stackTrace = StackTrace.current;
return Future.value(host).then<ConnectionTask<_NativeSocket>>((host) {
if (host is String) {
// Attempt to interpret the host as a numeric address
// (e.g. "127.0.0.1"). This will prevent [InternetAddress.lookup] from
// generating an unnecessary address in a different address family e.g.
// `InternetAddress.lookup('127.0.0.1', InternetAddressType.IPv6)`
// may return `InternetAddress('::ffff:127.0.0.1').
host = _InternetAddress.tryParse(host) ?? host;
}
if (host is _InternetAddress) {
return tryConnectToResolvedAddresses(
host,
port,
source,
sourcePort,
Stream.value(<_InternetAddress>[host]),
stackTrace,
);
}
final hostname = host as String;
final Stream<List<InternetAddress>> addresses = staggeredLookup(
hostname,
source,
);
return tryConnectToResolvedAddresses(
host,
port,
source,
sourcePort,
addresses,
stackTrace,
);
});
}
static ConnectionTask<_NativeSocket> tryConnectToResolvedAddresses(
dynamic host,
int port,
_InternetAddress? source,
int sourcePort,
Stream<List<InternetAddress>> addresses,
StackTrace callerStackTrace,
) {
// Completer for result.
final result = Completer<_NativeSocket>();
// Error, set if an error occurs.
// Keeps first error if multiple errors occur.
var error = null;
// Contains all sockets which haven't received an initial
// write or error event.
final connecting = <_NativeSocket>{};
// Timer counting down from the last connection attempt.
// Reset when a new connection is attempted,
// which happens either when a previous timer runs out,
// or when a previous connection attempt fails.
Timer? timer;
// Addresses arrived from lookup stream, but haven't been tried to connect
// to yet due to Timer-based throttling.
final pendingLookedUp = Queue<InternetAddress>();
// When deciding how to handle errors we need to know whether more
// addresses potentially are coming from the lookup stream.
bool isLookedUpStreamClosed = false;
late StreamSubscription<List<InternetAddress>> addressesSubscription;
Object? createConnection(
InternetAddress address,
_InternetAddress? source,
_NativeSocket socket,
) {
Object? connectionResult;
if (address.type == InternetAddressType.unix) {
if (source == null) {
connectionResult = socket._nativeCreateUnixDomainConnect(
address.address,
_Namespace._namespace,
);
} else {
if (source.type != InternetAddressType.unix) {
return SocketException(
// Use the same error message as used on Linux for better
// searchability...
"Address family not supported by protocol family, "
// ...and then add some details.
"sourceAddress.type must be ${InternetAddressType.unix} but was "
"${source.type}",
address: address,
);
}
connectionResult = socket._nativeCreateUnixDomainBindConnect(
address.address,
source.address,
_Namespace._namespace,
);
}
assert(
connectionResult == true ||
connectionResult is Error ||
connectionResult is OSError,
);
} else {
final address_ = address as _InternetAddress;
if (source == null && sourcePort == 0) {
connectionResult = socket._nativeCreateConnect(
address_._in_addr,
port,
address_._scope_id,
);
} else {
// allow specified port without address
if (source == null) {
source = address_.type == InternetAddressType.IPv4
? _InternetAddress.anyIPv4
: _InternetAddress.anyIPv6;
}
if (source.type != InternetAddressType.IPv4 &&
source.type != InternetAddressType.IPv6) {
return SocketException(
// Use the same error message as used on Linux for better
// searchability...
"Address family not supported by protocol family, "
// ...and then add some details.
"sourceAddress.type must be ${InternetAddressType.IPv4} or "
"${InternetAddressType.IPv6} but was ${source.type}",
address: address,
);
}
connectionResult = socket._nativeCreateBindConnect(
address_._in_addr,
port,
source._in_addr,
sourcePort,
address_._scope_id,
);
}
assert(connectionResult == true || connectionResult is OSError);
}
return connectionResult;
}
createConnectionError(
Object? connectionResult,
InternetAddress address,
int port,
_NativeSocket socket,
) {
if (connectionResult is OSError) {
final errorCode = connectionResult.errorCode;
if (source != null &&
errorCode != null &&
socket.isBindError(errorCode)) {
return createError(connectionResult, "Bind failed", source);
} else {
return createError(
connectionResult,
"Connection failed",
address,
port,
);
}
} else if (connectionResult is SocketException) {
return connectionResult;
} else if (connectionResult is Error) {
return connectionResult;
}
return createError(null, "Connection failed", address);
}
// Invoked either directly or via throttling Timer callback when we
// are ready to verify that we can connect to resolved address.
connectNext() {
timer?.cancel();
if (isLookedUpStreamClosed &&
connecting.isEmpty &&
pendingLookedUp.isEmpty) {
assert(error != null);
if (!result.isCompleted) {
// Might be already completed via onCancel
result.completeError(error, callerStackTrace);
}
return;
}
if (pendingLookedUp.isEmpty) {
assert(!isLookedUpStreamClosed || connecting.isNotEmpty);
return;
}
final address = pendingLookedUp.removeFirst();
final socket = _NativeSocket.normal(address);
// Will contain values of various types representing the result
// of trying to create a connection.
// A value of `true` means success, everything else means failure.
final Object? connectionResult = createConnection(
address,
source,
socket,
);
if (connectionResult != true) {
// connectionResult was not a success.
error = createConnectionError(connectionResult, address, port, socket);
connectNext(); // Try again after failure to connect.
return;
}
// Query the local port for error messages.
try {
socket.port;
} catch (e) {
if (e is OSError && e.errorCode == _inProgressErrorCode) {
// Ignore the error, proceed with waiting for a socket to become open.
// In non-blocking mode connect might not be established away, socket
// have to be waited for.
// EINPROGRESS error is ignored during |connect| call in native code,
// it has be ignored here during |port| query here.
} else {
error ??= createError(e, "Connection failed", address, port);
connectNext(); // Try again after failure to connect.
return;
}
}
// Try again if no response (failure or success) within a duration.
// If this occurs, the socket is still trying to connect, and might
// succeed or fail later.
final duration = address.isLoopback
? _retryDurationLoopback
: _retryDuration;
timer = Timer(duration, connectNext);
connecting.add(socket);
// Setup handlers for receiving the first write event which
// indicate that the socket is fully connected.
socket.setHandlers(
write: () {
// First remote response on connection.
// If error, drop the socket and go to the next address.
// If success, complete with the socket
// and stop all other open connection attempts.
connecting.remove(socket);
// From 'man 2 connect':
// After select(2) indicates writability, use getsockopt(2) to read
// the SO_ERROR option at level SOL_SOCKET to determine whether
// connect() completed successfully (SO_ERROR is zero) or
// unsuccessfully.
final osError = socket._nativeGetError();
if (osError != null) {
socket.close();
error ??= osError;
connectNext();
return;
}
// Connection success!
// Stop all other connecting sockets and the timer.
timer!.cancel();
socket.setListening(read: false, write: false);
for (var s in connecting) {
s.close();
s.setHandlers();
s.setListening(read: false, write: false);
}
connecting.clear();
addressesSubscription.cancel();
if (!result.isCompleted) {
// Might be already completed via onCancel
result.complete(socket);
}
},
error: (e, st) {
connecting.remove(socket);
socket.close();
socket.setHandlers();
socket.setListening(read: false, write: false);
// Keep first error, if present.
error ??= e;
connectNext(); // Try again after failure to connect.
},
);
socket.setListening(read: false, write: true);
}
void onCancel() {
timer?.cancel();
for (var s in connecting) {
s.close();
s.setHandlers();
s.setListening(read: false, write: false);
}
addressesSubscription.cancel();
connecting.clear();
if (!result.isCompleted) {
error ??= createError(
null,
"Connection attempt cancelled, host: ${host}, port: ${port}",
);
result.completeError(error, callerStackTrace);
}
}
addressesSubscription = addresses.listen(
(address) {
pendingLookedUp.addAll(address);
if (timer == null || !timer!.isActive) {
connectNext();
}
},
onDone: () {
isLookedUpStreamClosed = true;
connectNext();
},
onError: (e, st) {
error = e;
},
);
connectNext();
return ConnectionTask<_NativeSocket>._(result.future, onCancel);
}
static Future<_NativeSocket> connect(
dynamic host,
int port,
dynamic sourceAddress,
int sourcePort,
Duration? timeout,
) {
return startConnect(host, port, sourceAddress, sourcePort).then((
ConnectionTask<_NativeSocket> task,
) {
Future<_NativeSocket> socketFuture = task.socket;
if (timeout != null) {
socketFuture = socketFuture.timeout(
timeout,
onTimeout: () {
task.cancel();
throw createError(
OSError(
"Connection timed out",
Platform.isWindows
? 10060 // WSAETIMEDOUT
: 110, // ETIMEDOUT
),
"Connection timed out, host: ${host}, port: ${port}",
);
},
);
}
return socketFuture;
});
}
static Future<_InternetAddress> _resolveHost(dynamic host) async {
if (host is _InternetAddress) {
return host;
} else {
final list = await lookup(host);
if (list.isEmpty) {
throw createError(null, "Failed host lookup: '$host'");
}
return list.first as _InternetAddress;
}
}
static Future<_NativeSocket> bind(
host,
int port,
int backlog,
bool v6Only,
bool shared,
) async {
_throwOnBadPort(port);
if (host is String) {
host = escapeLinkLocalAddress(host);
}
final address = await _resolveHost(host);
var socket = _NativeSocket.listen(address);
var result;
if (address.type == InternetAddressType.unix) {
var path = address.address;
if (FileSystemEntity.isLinkSync(path)) {
path = Link(path).targetSync();
}
result = socket._nativeCreateUnixDomainBindListen(
path,
backlog,
shared,
_Namespace._namespace,
);
} else {
result = socket._nativeCreateBindListen(
address._in_addr,
port,
backlog,
v6Only,
shared,
address._scope_id,
);
}
if (result is OSError) {
throw SocketException(
"Failed to create server socket",
osError: result,
address: address,
port: port,
);
}
if (port != 0) socket.localPort = port;
socket.connectToEventHandler();
return socket;
}
static Future<_NativeSocket> bindDatagram(
host,
int port,
bool reuseAddress,
bool reusePort,
int ttl,
) async {
_throwOnBadPort(port);
_throwOnBadTtl(ttl);
final address = await _resolveHost(host);
var socket = _NativeSocket.datagram(address);
var result = socket._nativeCreateBindDatagram(
address._in_addr,
port,
reuseAddress,
reusePort,
ttl,
);
if (result is OSError) {
throw SocketException(
"Failed to create datagram socket",
osError: result,
address: address,
port: port,
);
}
if (port != 0) socket.localPort = port;
return socket;
}
_NativeSocket.datagram(this.localAddress)
: typeFlags = typeNormalSocket | typeUdpSocket;
_NativeSocket.normal(this.localAddress)
: typeFlags = typeNormalSocket | typeTcpSocket;
_NativeSocket.listen(this.localAddress)
: typeFlags = typeListeningSocket | typeTcpSocket {
isClosedWrite = true;
}
_NativeSocket.pipe() : typeFlags = typePipe;
_NativeSocket._watchCommon(int id, int type)
: typeFlags = typeNormalSocket | type {
isClosedWrite = true;
_nativeSetSocketId(id, typeFlags);
}
_NativeSocket._watchSignal(int id)
: this._watchCommon(id, typeInternalSignalSocket);
_NativeSocket._watch(int id) : this._watchCommon(id, typeInternalSocket);
bool get isListening => (typeFlags & typeListeningSocket) != 0;
bool get isPipe => (typeFlags & typePipe) != 0;
bool get isInternal => (typeFlags & typeInternalSocket) != 0;
bool get isInternalSignal => (typeFlags & typeInternalSignalSocket) != 0;
bool get isTcp => (typeFlags & typeTcpSocket) != 0;
bool get isUdp => (typeFlags & typeUdpSocket) != 0;
String get _serviceTypePath => throw UnimplementedError();
String get _serviceTypeName => throw UnimplementedError();
Uint8List? read(int? count) {
if (count != null && count <= 0) {
throw ArgumentError("Illegal length $count");
}
if (isClosing || isClosed) return null;
try {
Uint8List? list;
if (count != null) {
list = _nativeRead(count);
available = _nativeAvailable();
} else {
// If count is null, read as many bytes as possible.
// Loop here to ensure bytes that arrived while this read was
// issued are also read.
BytesBuilder builder = BytesBuilder(copy: false);
do {
assert(available > 0);
list = _nativeRead(available);
if (list == null) {
break;
}
builder.add(list);
available = _nativeAvailable();
const MAX_BUFFER_SIZE = 4 * 1024 * 1024;
if (builder.length > MAX_BUFFER_SIZE) {
// Don't consume too many bytes, otherwise we risk running
// out of memory when handling the whole aggregated lot.
break;
}
} while (available > 0);
if (builder.isEmpty) {
list = null;
} else {
list = builder.toBytes();
}
}
if (!const bool.fromEnvironment("dart.vm.product")) {
_SocketProfile.collectStatistic(
_nativeGetSocketId(),
_SocketProfileType.readBytes,
list?.length,
);
}
return list;
} catch (e) {
reportError(e, StackTrace.current, "Read failed");
return null;
}
}
Datagram? receive() {
if (isClosing || isClosed) return null;
try {
Datagram? result = _nativeRecvFrom();
if (!const bool.fromEnvironment("dart.vm.product")) {
_SocketProfile.collectStatistic(
_nativeGetSocketId(),
_SocketProfileType.readBytes,
result?.data.length,
);
}
_availableDatagram = _nativeAvailableDatagram();
return result;
} catch (e) {
reportError(e, StackTrace.current, "Receive failed");
return null;
}
}
SocketMessage? readMessage([int? count]) {
if (count != null && count <= 0) {
throw ArgumentError("Illegal length $count");
}
if (isClosing || isClosed) return null;
try {
final bytesCount = count ?? _nativeAvailable();
// Returned messagesData is a list of triples (level, type, uint8list)
// followed by uint8list with raw data.
// This is kept at this level to minimize dart api use in native method.
final List<dynamic> messagesData = _nativeReceiveMessage(bytesCount);
final messages = <SocketControlMessage>[];
if (messagesData.isNotEmpty) {
final triplesCount = (messagesData.length - 1) / 3;
assert((triplesCount * 3) == (messagesData.length - 1));
for (int i = 0; i < triplesCount; i++) {
final message = _SocketControlMessageImpl(
messagesData[i * 3] as int,
messagesData[i * 3 + 1] as int,
messagesData[i * 3 + 2] as Uint8List,
);
messages.add(message);
}
}
final socketMessage = SocketMessage(
messagesData[messagesData.length - 1] as Uint8List,
messages,
);
available = _nativeAvailable();
if (!const bool.fromEnvironment("dart.vm.product")) {
_SocketProfile.collectStatistic(
_nativeGetSocketId(),
_SocketProfileType.readBytes,
bytesCount,
);
}
return socketMessage;
} catch (e, st) {
reportError(e, st, "Read failed");
return null;
}
}
static int _fixOffset(int? offset) => offset ?? 0;
// This code issues a native write operation.
//
// On POSIX systems the data will be written using `write` syscall.
// When `write` returns a positive value this means that this number
// of bytes have been transferred from [buffer] into the OS buffer.
// At this point if the underlying descriptor is closed the OS will
// still attempt to deliver already written bytes to the destination.
//
// On Windows we use overlapped IO instead: `write` returning a positive
// value simply means that we have initiated an asynchronous IO operation
// for this many bytes. Closing the underlying handle will simply cancel the
// operation midway. Consequently you can only assume that bytes left userland
// when asynchronous write operation completes and this socket receives
// a [writeEvent].
int write(List<int> buffer, int offset, int? bytes) {
// TODO(40614): Remove once non-nullability is sound.
offset = _fixOffset(offset);
if (bytes == null) {
if (offset > buffer.length) {
throw RangeError.value(offset);
}
bytes = buffer.length - offset;
}
if (offset < 0) throw RangeError.value(offset);
if (bytes < 0) throw RangeError.value(bytes);
if ((offset + bytes) > buffer.length) {
throw RangeError.value(offset + bytes);
}
if (isClosing || isClosed) return 0;
if (bytes == 0) return 0;
try {
_BufferAndStart bufferAndStart = _ensureFastAndSerializableByteData(
buffer,
offset,
offset + bytes,
);
if (!const bool.fromEnvironment("dart.vm.product")) {
_SocketProfile.collectStatistic(
_nativeGetSocketId(),
_SocketProfileType.writeBytes,
bufferAndStart.buffer.length - bufferAndStart.start,
);
}
int result = _nativeWrite(
bufferAndStart.buffer,
bufferAndStart.start,
bytes,
);
if (result >= 0) {
// If write succeeded only partially or is pending then we should
// pause writing and wait for the write event to arrive from the
// event handler. If the write has fully completed then we should
// continue writing.
writeAvailable = (result == bytes) && !hasPendingWrite();
} else {
// Negative result indicates that we forced a short write for testing
// purpose. We are not guaranteed to get a writeEvent in this case
// unless there is a pending write - which will trigger an event
// when it completes. So the caller should continue writing into
// this socket.
result = -result;
writeAvailable = !hasPendingWrite();
}
return result;
} catch (e) {
StackTrace st = StackTrace.current;
scheduleMicrotask(() => reportError(e, st, "Write failed"));
return 0;
}
}
int send(
List<int> buffer,
int offset,
int bytes,
InternetAddress address,
int port,
) {
_throwOnBadPort(port);
if (isClosing || isClosed) return 0;
try {
_BufferAndStart bufferAndStart = _ensureFastAndSerializableByteData(
buffer,
offset,
bytes,
);
if (!const bool.fromEnvironment("dart.vm.product")) {
_SocketProfile.collectStatistic(
_nativeGetSocketId(),
_SocketProfileType.writeBytes,
bufferAndStart.buffer.length - bufferAndStart.start,
);
}
int result = _nativeSendTo(
bufferAndStart.buffer,
bufferAndStart.start,
bytes,
(address as _InternetAddress)._in_addr,
port,
);
return result;
} catch (e) {
StackTrace st = StackTrace.current;
scheduleMicrotask(() => reportError(e, st, "Send failed"));
return 0;
}
}
int sendMessage(
List<int> buffer,
int offset,
int? bytes,
List<SocketControlMessage> controlMessages,
) {
if (offset < 0) throw RangeError.value(offset);
if (bytes != null) {
if (bytes < 0) throw RangeError.value(bytes);
} else {
bytes = buffer.length - offset;
}
if ((offset + bytes) > buffer.length) {
throw RangeError.value(offset + bytes);
}
if (isClosing || isClosed) return 0;
try {
_BufferAndStart bufferAndStart = _ensureFastAndSerializableByteData(
buffer,
offset,
bytes,
);
if (!const bool.fromEnvironment("dart.vm.product")) {
_SocketProfile.collectStatistic(
_nativeGetSocketId(),
_SocketProfileType.writeBytes,
bufferAndStart.buffer.length - bufferAndStart.start,
);
}
// list of triples <level, type, data> arranged to minimize dart api
// use in native method.
List<dynamic> messages = <dynamic>[];
for (SocketControlMessage controlMessage in controlMessages) {
messages.add(controlMessage.level);
messages.add(controlMessage.type);
messages.add(controlMessage.data);
}
return _nativeSendMessage(
bufferAndStart.buffer,
bufferAndStart.start,
bytes,
messages,
);
} catch (e, st) {
scheduleMicrotask(() => reportError(e, st, "SendMessage failed"));
return 0;
}
}
_NativeSocket? accept() {
// Don't issue accept if we're closing.
if (isClosing || isClosed) return null;
assert(connections > 0);
connections--;
tokens++;
returnTokens(listeningTokenBatchSize);
var socket = _NativeSocket.normal(address);
if (_nativeAccept(socket) != true) return null;
socket.localPort = localPort;
return socket;
}
int get port {
if (localAddress.type == InternetAddressType.unix) return 0;
if (localPort != 0) return localPort;
if (isClosing || isClosed) throw const SocketException.closed();
var result = _nativeGetPort();
if (result is OSError) {
throw result;
}
return localPort = result;
}
int get remotePort {
if (localAddress.type == InternetAddressType.unix) return 0;
if (isClosing || isClosed) throw const SocketException.closed();
return _nativeGetRemotePeer()[1];
}
InternetAddress get address => localAddress;
InternetAddress get remoteAddress {
if (isClosing || isClosed) throw const SocketException.closed();
var result = _nativeGetRemotePeer();
var addr = result[0] as List<Object?>;
var type = InternetAddressType._from(addr[0] as int);
if (type == InternetAddressType.unix) {
return _InternetAddress.fromString(
addr[1] as String,
type: InternetAddressType.unix,
);
}
return _InternetAddress(
type,
addr[1] as String,
null,
addr[2] as Uint8List,
);
}
void issueReadEvent() {
if (closedReadEventSent) return;
if (readEventIssued) return;
readEventIssued = true;
void issue() {
readEventIssued = false;
if (isClosing) return;
// Note: it is by design that we don't deliver closedRead event
// unless read events are enabled. This also means we will not
// fully close (and dispose) of the socket unless it is drained
// of accumulated incomming data.
if (!sendReadEvents) return;
if (stopRead()) {
if (isClosedRead && !closedReadEventSent) {
if (isClosedWrite) close();
var handler = closedEventHandler;
if (handler == null) return;
closedReadEventSent = true;
handler();
}
return;
}
var handler = readEventHandler;
if (handler == null) return;
handler();
readEventIssued = true;
scheduleMicrotask(issue);
}
scheduleMicrotask(issue);
}
bool stopRead() {
if (isUdp) {
return !_availableDatagram;
} else {
return available == 0;
}
}
void issueWriteEvent({bool delayed = true}) {
if (writeEventIssued) return;
if (!writeAvailable) return;
void issue() {
writeEventIssued = false;
if (!writeAvailable) return;
if (isClosing) return;
if (!sendWriteEvents) return;
sendWriteEvents = false;
var handler = writeEventHandler;
if (handler == null) return;
handler();
}
if (delayed) {
writeEventIssued = true;
scheduleMicrotask(issue);
} else {
issue();
}
}
// Multiplexes socket events to the socket handlers.
void multiplex(Object eventsObj) {
// TODO(paulberry): when issue #31305 is fixed, we should be able to simply
// declare `events` as a `covariant int` parameter.
int events = eventsObj as int;
for (int i = firstEvent; i <= lastEvent; i++) {
if (((events & (1 << i)) != 0)) {
if (isClosing && i != destroyedEvent) continue;
switch (i) {
case readEvent:
if (isClosedRead) continue;
if (isListening) {
connections++;
if (!isClosed) {
// If the connection is closed right after it's accepted, there's a
// chance the close-handler is not set.
var handler = readEventHandler;
if (handler != null) handler();
}
} else {
if (isUdp) {
_availableDatagram = _nativeAvailableDatagram();
} else {
available = _nativeAvailable();
}
issueReadEvent();
continue;
}
break;
case writeEvent:
// On Windows there are two sources of write events: when pending
// write completes and when we subscribe to write events via
// setEventMaskCommand. Furthermore we don't always wait for a
// write event to issue a write. This means when event triggered by
// setEventMaskCommand arrives we might have already initiated a
// write. This means we should check [hasPendingWrite] here to
// be absolutely certain that the pending write operation has
// completed.
writeAvailable = !hasPendingWrite();
issueWriteEvent(delayed: false);
continue;
case errorEvent:
if (!isClosing) {
final osError = _nativeGetError();
if (osError != null) {
reportError(osError, null, osError.message);
} else {
reportError(
Error(),
StackTrace.current,
"Error event raised in event handler : "
"error condition has been reset",
);
}
}
break;
case closedEvent:
if (isClosedRead) continue;
if (!isListening && !isClosing && !isClosed) {
isClosedRead = true;
issueReadEvent();
continue;
} else if (!isClosed) {
// If the connection is closed right after it's accepted, there's a
// chance the close-handler is not set.
var handler = closedEventHandler;
if (handler != null) handler();
}
break;
case destroyedEvent:
assert(isClosing);
assert(!isClosed);
isClosed = true;
closeCompleter.complete();
disconnectFromEventHandler();
var handler = destroyedEventHandler;
if (handler != null) handler();
continue;
}
}
}
if (!isListening) {
tokens++;
returnTokens(normalTokenBatchSize);
}
}
void returnTokens(int tokenBatchSize) {
if (!isClosing && !isClosed) {
assert(eventPort != null);
// Return in batches.
if (tokens == tokenBatchSize) {
assert(tokens < (1 << firstCommand));
_sendToEventHandler((1 << returnTokenCommand) | tokens);
tokens = 0;
}
}
}
void setHandlers({
void Function()? read,
void Function()? write,
void Function(Object e, StackTrace? st)? error,
void Function()? closed,
void Function()? destroyed,
}) {
readEventHandler = read;
writeEventHandler = write;
errorEventHandler = error;
closedEventHandler = closed;
destroyedEventHandler = destroyed;
}
void setListening({bool read = true, bool write = true}) {
sendReadEvents = read;
sendWriteEvents = write;
if (read) issueReadEvent();
if (write) issueWriteEvent();
if (!flagsSent && !isClosing) {
flagsSent = true;
int flags = 1 << setEventMaskCommand;
if (!isClosedRead) flags |= 1 << readEvent;
if (!isClosedWrite) flags |= 1 << writeEvent;
_sendToEventHandler(flags);
}
}
Future close() {
if (!isClosing && !isClosed) {
_sendToEventHandler(1 << closeCommand);
isClosing = true;
}
return closeCompleter.future;
}
void shutdown(SocketDirection direction) {
if (!isClosing && !isClosed) {
switch (direction) {
case SocketDirection.receive:
shutdownRead();
break;
case SocketDirection.send:
shutdownWrite();
break;
case SocketDirection.both:
close();
break;
default:
throw ArgumentError(direction);
}
}
}
void shutdownWrite() {
if (!isClosing && !isClosed) {
if (closedReadEventSent) {
close();
} else {
_sendToEventHandler(1 << shutdownWriteCommand);
}
isClosedWrite = true;
}
}
void shutdownRead() {
if (!isClosing && !isClosed) {
if (isClosedWrite) {
close();
} else {
_sendToEventHandler(1 << shutdownReadCommand);
}
isClosedRead = true;
// Make sure to dispatch a closedRead event. Shutdown is only complete
// once the socket is drained of data and readClosed is dispatched.
issueReadEvent();
}
}
void _sendToEventHandler(int data) {
int fullData = (typeFlags & typeTypeMask) | data;
assert(!isClosing);
connectToEventHandler();
_EventHandler._sendData(this, eventPort!.sendPort, fullData);
}
void connectToEventHandler() {
assert(!isClosed);
if (eventPort == null) {
eventPort = RawReceivePort(multiplex, 'Socket Event Handler');
}
}
void disconnectFromEventHandler() {
eventPort!.close();
eventPort = null;
// Now that we don't track this Socket anymore, we can clear the owner
// field.
owner = null;
}
// Check whether this is an error response from a native port call.
static bool isErrorResponse(response) {
return response is List && response[0] != _successResponse;
}
// Create the appropriate error/exception from different returned
// error objects.
static createError(
error,
String message, [
InternetAddress? address,
int? port,
]) {
if (error is OSError) {
return SocketException(
message,
osError: error,
address: address,
port: port,
);
} else if (error is List) {
assert(isErrorResponse(error));
switch (error[0]) {
case _illegalArgumentResponse:
return ArgumentError();
case _osErrorResponse:
return SocketException(
message,
osError: OSError(error[2], error[1]),
address: address,
port: port,
);
default:
return AssertionError("Unknown error");
}
} else {
return SocketException(message, address: address, port: port);
}
}
void reportError(error, StackTrace? st, String message) {
var e = createError(
error,
message,
isUdp || isTcp ? address : null,
localPort,
);
// Invoke the error handler if any.
var handler = errorEventHandler;
if (handler != null) {
handler(e, st);
}
// For all errors we close the socket
close();
}
dynamic getOption(SocketOption option) {
// TODO(40614): Remove once non-nullability is sound.
ArgumentError.checkNotNull(option, "option");
var result = _nativeGetOption(option._value, address.type._value);
if (result is OSError) throw result;
return result;
}
bool setOption(SocketOption option, value) {
// TODO(40614): Remove once non-nullability is sound.
ArgumentError.checkNotNull(option, "option");
_nativeSetOption(option._value, address.type._value, value);
return true;
}
Uint8List getRawOption(RawSocketOption option) {
// TODO(40614): Remove once non-nullability is sound.
ArgumentError.checkNotNull(option, "option");
ArgumentError.checkNotNull(option.value, "option.value");
_nativeGetRawOption(option.level, option.option, option.value);
return option.value;
}
void setRawOption(RawSocketOption option) {
// TODO(40614): Remove once non-nullability is sound.
ArgumentError.checkNotNull(option, "option");
ArgumentError.checkNotNull(option.value, "option.value");
_nativeSetRawOption(option.level, option.option, option.value);
}
InternetAddress? multicastAddress(
InternetAddress addr,
NetworkInterface? interface,
) {
// On Mac OS using the interface index for joining IPv4 multicast groups
// is not supported. Here the IP address of the interface is needed.
if ((Platform.isMacOS || Platform.isIOS) &&
addr.type == InternetAddressType.IPv4) {
if (interface != null) {
for (int i = 0; i < interface.addresses.length; i++) {
if (interface.addresses[i].type == InternetAddressType.IPv4) {
return interface.addresses[i];
}
}
// No IPv4 address found on the interface.
throw SocketException(
"The network interface does not have an address "
"of the same family as the multicast address",
);
} else {
// Default to the ANY address if no interface is specified.
return InternetAddress.anyIPv4;
}
} else {
return null;
}
}
void joinMulticast(InternetAddress addr, NetworkInterface? interface) {
final interfaceAddr =
multicastAddress(addr, interface) as _InternetAddress?;
var interfaceIndex = interface == null ? 0 : interface.index;
_nativeJoinMulticast(
(addr as _InternetAddress)._in_addr,
interfaceAddr?._in_addr,
interfaceIndex,
);
}
void leaveMulticast(InternetAddress addr, NetworkInterface? interface) {
final interfaceAddr =
multicastAddress(addr, interface) as _InternetAddress?;
var interfaceIndex = interface == null ? 0 : interface.index;
_nativeLeaveMulticast(
(addr as _InternetAddress)._in_addr,
interfaceAddr?._in_addr,
interfaceIndex,
);
}
bool hasPendingWrite() {
return Platform.isWindows && _nativeHasPendingWrite();
}
@pragma("vm:external-name", "Socket_SetSocketId")
external void _nativeSetSocketId(int id, int typeFlags);
@pragma("vm:external-name", "Socket_Available")
external int _nativeAvailable();
@pragma("vm:external-name", "Socket_AvailableDatagram")
external bool _nativeAvailableDatagram();
@pragma("vm:external-name", "Socket_Read")
external Uint8List? _nativeRead(int len);
@pragma("vm:external-name", "Socket_RecvFrom")
external Datagram? _nativeRecvFrom();
@pragma("vm:external-name", "Socket_ReceiveMessage")
external List<dynamic> _nativeReceiveMessage(int len);
@pragma("vm:external-name", "Socket_WriteList")
external int _nativeWrite(List<int> buffer, int offset, int bytes);
@pragma("vm:external-name", "Socket_HasPendingWrite")
external bool _nativeHasPendingWrite();
@pragma("vm:external-name", "Socket_SendTo")
external int _nativeSendTo(
List<int> buffer,
int offset,
int bytes,
Uint8List address,
int port,
);
@pragma("vm:external-name", "Socket_SendMessage")
external _nativeSendMessage(
List<int> buffer,
int offset,
int bytes,
List<dynamic> controlMessages,
);
@pragma("vm:external-name", "Socket_CreateConnect")
external _nativeCreateConnect(Uint8List addr, int port, int scope_id);
@pragma("vm:external-name", "Socket_CreateUnixDomainConnect")
external _nativeCreateUnixDomainConnect(String addr, _Namespace namespace);
@pragma("vm:external-name", "Socket_CreateBindConnect")
external _nativeCreateBindConnect(
Uint8List addr,
int port,
Uint8List sourceAddr,
int sourcePort,
int scope_id,
);
@pragma("vm:external-name", "Socket_CreateUnixDomainBindConnect")
external _nativeCreateUnixDomainBindConnect(
String addr,
String sourceAddr,
_Namespace namespace,
);
@pragma("vm:external-name", "SocketBase_IsBindError")
external bool isBindError(int errorNumber);
@pragma("vm:external-name", "ServerSocket_CreateBindListen")
external _nativeCreateBindListen(
Uint8List addr,
int port,
int backlog,
bool v6Only,
bool shared,
int scope_id,
);
@pragma("vm:external-name", "ServerSocket_CreateUnixDomainBindListen")
external _nativeCreateUnixDomainBindListen(
String addr,
int backlog,
bool shared,
_Namespace namespace,
);
@pragma("vm:external-name", "Socket_CreateBindDatagram")
external _nativeCreateBindDatagram(
Uint8List addr,
int port,
bool reuseAddress,
bool reusePort,
int ttl,
);
@pragma("vm:external-name", "ServerSocket_Accept")
external bool _nativeAccept(_NativeSocket socket);
@pragma("vm:external-name", "Socket_GetPort")
external dynamic _nativeGetPort();
@pragma("vm:external-name", "Socket_GetRemotePeer")
external List _nativeGetRemotePeer();
@pragma("vm:external-name", "Socket_GetSocketId")
external int _nativeGetSocketId();
@pragma("vm:external-name", "Socket_GetFD")
external int get fd;
@pragma("vm:external-name", "Socket_GetError")
external OSError? _nativeGetError();
@pragma("vm:external-name", "Socket_GetOption")
external _nativeGetOption(int option, int protocol);
@pragma("vm:external-name", "Socket_GetRawOption")
external void _nativeGetRawOption(int level, int option, Uint8List data);
@pragma("vm:external-name", "Socket_SetOption")
external void _nativeSetOption(int option, int protocol, value);
@pragma("vm:external-name", "Socket_SetRawOption")
external void _nativeSetRawOption(int level, int option, Uint8List data);
@pragma("vm:external-name", "Socket_JoinMulticast")
external void _nativeJoinMulticast(
Uint8List addr,
Uint8List? interfaceAddr,
int interfaceIndex,
);
@pragma("vm:external-name", "Socket_LeaveMulticast")
external void _nativeLeaveMulticast(
Uint8List addr,
Uint8List? interfaceAddr,
int interfaceIndex,
);
@pragma("vm:external-name", "Socket_Fatal")
external static void _nativeFatal(msg);
}
class _RawServerSocket extends Stream<RawSocket>
implements RawServerSocket, _RawSocketBase {
final _NativeSocket _socket;
StreamController<RawSocket>? _controller;
bool _v6Only;
static Future<_RawServerSocket> bind(
address,
int port,
int backlog,
bool v6Only,
bool shared,
) {
_throwOnBadPort(port);
if (backlog < 0) throw ArgumentError("Invalid backlog $backlog");
return _NativeSocket.bind(
address,
port,
backlog,
v6Only,
shared,
).then((socket) => _RawServerSocket(socket, v6Only));
}
_RawServerSocket(this._socket, this._v6Only);
StreamSubscription<RawSocket> listen(
void onData(RawSocket event)?, {
Function? onError,
void onDone()?,
bool? cancelOnError,
}) {
if (_controller != null) {
throw StateError("Stream was already listened to");
}
var zone = Zone.current;
final controller = _controller = StreamController(
sync: true,
onListen: _onSubscriptionStateChange,
onCancel: _onSubscriptionStateChange,
onPause: _onPauseStateChange,
onResume: _onPauseStateChange,
);
_socket.setHandlers(
read: zone.bindCallbackGuarded(() {
while (_socket.connections > 0) {
var socket = _socket.accept();
if (socket == null) return;
if (!const bool.fromEnvironment("dart.vm.product")) {
_SocketProfile.collectNewSocket(
socket._nativeGetSocketId(),
_tcpSocket,
socket.address,
socket.port,
);
}
controller.add(_RawSocket(socket));
if (controller.isPaused) return;
}
}),
error: zone.bindBinaryCallbackGuarded((Object e, StackTrace? st) {
controller.addError(e, st);
controller.close();
}),
destroyed: () => controller.close(),
);
return controller.stream.listen(
onData,
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);
}
int get port => _socket.port;
InternetAddress get address => _socket.address;
Future<RawServerSocket> close() {
return _socket.close().then<RawServerSocket>((_) => this);
}
void _pause() {
_socket.setListening(read: false, write: false);
}
void _resume() {
_socket.setListening(read: true, write: false);
}
void _onSubscriptionStateChange() {
if (_controller!.hasListener) {
_resume();
} else {
_socket.close();
}
}
void _onPauseStateChange() {
if (_controller!.isPaused) {
_pause();
} else {
_resume();
}
}
bool get _closedReadEventSent => _socket.closedReadEventSent;
void set _owner(owner) {
_socket.owner = owner;
}
}
class _RawSocket extends Stream<RawSocketEvent>
implements RawSocket, _RawSocketBase {
final _NativeSocket _socket;
final _controller = StreamController<RawSocketEvent>(sync: true);
bool _readEventsEnabled = true;
bool _writeEventsEnabled = true;
// Flag to handle Ctrl-D closing of stdio on Mac OS.
bool _isMacOSTerminalInput = false;
static Future<RawSocket> connect(
dynamic host,
int port,
dynamic sourceAddress,
int sourcePort,
Duration? timeout,
) {
return _NativeSocket.connect(
host,
port,
sourceAddress,
sourcePort,
timeout,
).then((socket) {
if (!const bool.fromEnvironment("dart.vm.product")) {
_SocketProfile.collectNewSocket(
socket._nativeGetSocketId(),
_tcpSocket,
socket.address,
port,
);
}
return _RawSocket(socket);
});
}
static Future<ConnectionTask<_RawSocket>> startConnect(
dynamic host,
int port,
dynamic sourceAddress,
int sourcePort,
) {
return _NativeSocket.startConnect(
host,
port,
sourceAddress,
sourcePort,
).then((ConnectionTask<_NativeSocket> nativeTask) {
final Future<_RawSocket> raw = nativeTask.socket.then((
_NativeSocket nativeSocket,
) {
if (!const bool.fromEnvironment("dart.vm.product")) {
_SocketProfile.collectNewSocket(
nativeSocket._nativeGetSocketId(),
_tcpSocket,
nativeSocket.address,
port,
);
}
return _RawSocket(nativeSocket);
});
return ConnectionTask<_RawSocket>._(raw, nativeTask._onCancel);
});
}
_RawSocket(this._socket) {
var zone = Zone.current;
_controller
..onListen = _onSubscriptionStateChange
..onCancel = _onSubscriptionStateChange
..onPause = _onPauseStateChange
..onResume = _onPauseStateChange;
_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.readClosed),
destroyed: () {
_controller.add(RawSocketEvent.closed);
_controller.close();
},
error: zone.bindBinaryCallbackGuarded((Object e, StackTrace? st) {
_controller.addError(e, st);
_socket.close();
}),
);
}
factory _RawSocket._writePipe() {
var native = _NativeSocket.pipe();
native.isClosedRead = true;
native.closedReadEventSent = true;
return _RawSocket(native);
}
factory _RawSocket._readPipe(int? fd) {
var native = _NativeSocket.pipe();
native.isClosedWrite = true;
if (fd != null) _getStdioHandle(native, fd);
var result = _RawSocket(native);
if (fd != null) {
var socketType = _StdIOUtils._nativeSocketType(result._socket);
result._isMacOSTerminalInput =
Platform.isMacOS && socketType == _stdioHandleTypeTerminal;
}
return result;
}
StreamSubscription<RawSocketEvent> listen(
void onData(RawSocketEvent event)?, {
Function? onError,
void onDone()?,
bool? cancelOnError,
}) {
return _controller.stream.listen(
onData,
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);
}
int available() => _socket.available;
Uint8List? read([int? len]) {
if (_isMacOSTerminalInput) {
var available = this.available();
if (available == 0) return null;
var data = _socket.read(len);
if (data == null || data.length < available) {
// Reading less than available from a Mac OS terminal indicate Ctrl-D.
// This is interpreted as read closed.
scheduleMicrotask(() => _controller.add(RawSocketEvent.readClosed));
}
return data;
} else {
return _socket.read(len);
}
}
SocketMessage? readMessage([int? count]) {
return _socket.readMessage(count);
}
/// See [_NativeSocket.write] for some implementation notes.
int write(List<int> buffer, [int offset = 0, int? count]) =>
_socket.write(buffer, offset, count);
int sendMessage(
List<SocketControlMessage> controlMessages,
List<int> data, [
int offset = 0,
int? count,
]) => _socket.sendMessage(data, offset, count, controlMessages);
Future<RawSocket> close() => _socket.close().then<RawSocket>((_) {
if (!const bool.fromEnvironment("dart.vm.product")) {
_SocketProfile.collectStatistic(
_socket._nativeGetSocketId(),
_SocketProfileType.endTime,
);
}
return this;
});
void shutdown(SocketDirection direction) => _socket.shutdown(direction);
int get port => _socket.port;
int get remotePort => _socket.remotePort;
InternetAddress get address => _socket.address;
InternetAddress get remoteAddress => _socket.remoteAddress;
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);
Uint8List getRawOption(RawSocketOption option) =>
_socket.getRawOption(option);
void setRawOption(RawSocketOption option) => _socket.setRawOption(option);
_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 {
_socket.close();
}
}
bool get _closedReadEventSent => _socket.closedReadEventSent;
void set _owner(owner) {
_socket.owner = owner;
}
}
@patch
class ServerSocket {
@patch
static Future<ServerSocket> _bind(
address,
int port, {
int backlog = 0,
bool v6Only = false,
bool shared = false,
}) {
return _ServerSocket.bind(address, port, backlog, v6Only, shared);
}
}
class _ServerSocket extends Stream<Socket> implements ServerSocket {
final _socket;
static Future<_ServerSocket> bind(
address,
int port,
int backlog,
bool v6Only,
bool shared,
) {
return _RawServerSocket.bind(
address,
port,
backlog,
v6Only,
shared,
).then((socket) => _ServerSocket(socket));
}
_ServerSocket(this._socket);
StreamSubscription<Socket> listen(
void onData(Socket event)?, {
Function? onError,
void onDone()?,
bool? cancelOnError,
}) {
return _socket
.map<Socket>((rawSocket) => _Socket(rawSocket))
.listen(
onData,
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);
}
int get port => _socket.port;
InternetAddress get address => _socket.address;
Future<ServerSocket> close() =>
_socket.close().then<ServerSocket>((_) => this);
void set _owner(owner) {
_socket._owner = owner;
}
}
@patch
class Socket {
@patch
static Future<Socket> _connect(
dynamic host,
int port, {
dynamic sourceAddress,
int sourcePort = 0,
Duration? timeout,
}) {
return RawSocket.connect(
host,
port,
sourceAddress: sourceAddress,
sourcePort: sourcePort,
timeout: timeout,
).then((socket) => _Socket(socket));
}
@patch
static Future<ConnectionTask<Socket>> _startConnect(
dynamic host,
int port, {
dynamic sourceAddress,
int sourcePort = 0,
}) {
return RawSocket.startConnect(
host,
port,
sourceAddress: sourceAddress,
sourcePort: sourcePort,
).then((rawTask) {
Future<Socket> socket = rawTask.socket.then(
(rawSocket) => _Socket(rawSocket),
);
return ConnectionTask<Socket>._(socket, rawTask._onCancel);
});
}
}
class _SocketStreamConsumer implements StreamConsumer<List<int>> {
StreamSubscription? subscription;
final _Socket socket;
int? offset;
List<int>? buffer;
bool paused = false;
Completer<Socket>? streamCompleter;
_SocketStreamConsumer(this.socket);
Future<Socket> addStream(Stream<List<int>> stream) {
socket._ensureRawSocketSubscription();
final completer = streamCompleter = Completer<Socket>();
if (socket._raw != null) {
subscription = stream.listen(
(data) {
assert(!paused);
assert(buffer == null);
buffer = data;
offset = 0;
try {
write();
} catch (e) {
buffer = null;
offset = 0;
socket.destroy();
stop();
done(e);
}
},
onError: (error, [stackTrace]) {
socket.destroy();
done(error, stackTrace);
},
onDone: () {
// Note: stream only delivers done event if subscription is not paused.
// so it is crucial to keep subscription paused while writes are
// in flight.
assert(buffer == null);
done();
},
cancelOnError: true,
);
} else {
done();
}
return completer.future;
}
Future<Socket> close() {
socket._consumerDone();
return Future.value(socket);
}
bool get _previousWriteHasCompleted {
final rawSocket = socket._raw;
if (rawSocket is _RawSocket) {
return rawSocket._socket.writeAvailable;
}
assert(rawSocket is _RawSecureSocket);
// _RawSecureSocket has an internal buffering mechanism and it is going
// to flush its buffer before it shutsdown.
return true;
}
void write() {
final sub = subscription;
if (sub == null) return;
// We have something to write out.
if (offset! < buffer!.length) {
offset =
offset! + socket._write(buffer!, offset!, buffer!.length - offset!);
}
if (offset! < buffer!.length || !_previousWriteHasCompleted) {
// On Windows we might have written the whole buffer out but we are
// still waiting for the write to complete. We should not resume the
// subscription until the pending write finishes and we receive a
// writeEvent signaling that we can write the next chunk or that we
// can consider all data flushed from our side into kernel buffers.
if (!paused) {
paused = true;
sub.pause();
}
socket._enableWriteEvent();
} else {
// Write fully completed.
buffer = null;
if (paused) {
paused = false;
sub.resume();
}
}
}
void done([error, stackTrace]) {
final completer = streamCompleter;
if (completer != null) {
if (error != null) {
completer.completeError(error, stackTrace);
} else {
completer.complete(socket);
}
streamCompleter = null;
}
}
void stop() {
final sub = subscription;
if (sub == null) return;
sub.cancel();
subscription = null;
paused = false;
socket._disableWriteEvent();
}
}
class _Socket extends Stream<Uint8List> 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.
final _controller = StreamController<Uint8List>(sync: true);
bool _controllerClosed = false;
late _SocketStreamConsumer _consumer;
late IOSink _sink;
StreamSubscription? _subscription;
Completer<Object?>? _detachReady;
_Socket(RawSocket raw) : _raw = raw {
_controller
..onListen = _onSubscriptionStateChange
..onCancel = _onSubscriptionStateChange
..onPause = _onPauseStateChange
..onResume = _onPauseStateChange;
_consumer = _SocketStreamConsumer(this);
_sink = 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() {
return _Socket(_RawSocket._writePipe());
}
factory _Socket._readPipe([int? fd]) {
return _Socket(_RawSocket._readPipe(fd));
}
// Note: this code seems a bit suspicious because _raw can be _RawSocket and
// it can be _RawSecureSocket because _SecureSocket extends _Socket
// and these two types are incompatible because _RawSecureSocket._socket
// is Socket and not _NativeSocket.
_NativeSocket get _nativeSocket => (_raw as _RawSocket)._socket;
StreamSubscription<Uint8List> listen(
void onData(Uint8List 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, [String sep = ""]) =>
_sink.writeAll(objects, sep);
void add(List<int> bytes) => _sink.add(bytes);
/// Unsupported operation on sockets.
///
/// Throws an [UnsupportedError] because errors cannot be transmitted over a
/// [Socket].
void addError(Object error, [StackTrace? stackTrace]) {
throw UnsupportedError("Cannot send errors on sockets");
}
Future addStream(Stream<List<int>> stream) {
return _sink.addStream(stream);
}
Future flush() => _sink.flush();
Future close() => _sink.close();
Future 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) {
final raw = _raw;
if (raw == null) throw const SocketException.closed();
return raw.setOption(option, enabled);
}
Uint8List getRawOption(RawSocketOption option) {
final raw = _raw;
if (raw == null) throw const SocketException.closed();
return raw.getRawOption(option);
}
void setRawOption(RawSocketOption option) {
final raw = _raw;
if (raw == null) throw const SocketException.closed();
raw.setRawOption(option);
}
int get port {
final raw = _raw;
if (raw == null) throw const SocketException.closed();
return raw.port;
}
InternetAddress get address {
final raw = _raw;
if (raw == null) throw const SocketException.closed();
return raw.address;
}
int get remotePort {
final raw = _raw;
if (raw == null) throw const SocketException.closed();
return raw.remotePort;
}
InternetAddress get remoteAddress {
final raw = _raw;
if (raw == null) throw const SocketException.closed();
return raw.remoteAddress;
}
Future<List<Object?>> _detachRaw() {
var completer = Completer<Object?>();
_detachReady = completer;
_sink.close();
return completer.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() {
final raw = _raw;
if (_subscription == null && raw != null) {
_subscription = raw.listen(
_onData,
onError: _onError,
onDone: _onDone,
cancelOnError: true,
);
}
}
_closeRawSocket() {
var raw = _raw!;
_raw = null;
_closed = true;
raw.close();
}
void _onSubscriptionStateChange() {
final raw = _raw;
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() {
_raw?.readEventsEnabled = !_controller.isPaused;
}
void _onData(event) {
switch (event) {
case RawSocketEvent.read:
if (_raw == null) break;
var buffer = _raw!.read();
if (buffer != null) _controller.add(buffer);
break;
case RawSocketEvent.write:
_consumer.write();
break;
case RawSocketEvent.readClosed:
_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) {
final raw = _raw;
if (raw != null) {
return raw.write(data, offset, length);
}
return 0;
}
void _enableWriteEvent() {
_raw?.writeEventsEnabled = true;
}
void _disableWriteEvent() {
_raw?.writeEventsEnabled = false;
}
void _consumerDone() {
if (_detachReady != null) {
_detachReady!.complete(null);
} else {
final raw = _raw;
if (raw != null) {
raw.shutdown(SocketDirection.send);
_disableWriteEvent();
}
}
}
void set _owner(owner) {
// Note: _raw can be _RawSocket and _RawSecureSocket.
(_raw as _RawSocketBase)._owner = owner;
}
}
@patch
class RawDatagramSocket {
@patch
static Future<RawDatagramSocket> bind(
host,
int port, {
bool reuseAddress = true,
bool reusePort = false,
int ttl = 1,
}) {
return _RawDatagramSocket.bind(host, port, reuseAddress, reusePort, ttl);
}
}
class _RawDatagramSocket extends Stream<RawSocketEvent>
implements RawDatagramSocket {
_NativeSocket _socket;
late StreamController<RawSocketEvent> _controller;
bool _readEventsEnabled = true;
bool _writeEventsEnabled = true;
_RawDatagramSocket(this._socket) {
var zone = Zone.current;
_controller = StreamController<RawSocketEvent>(
sync: true,
onListen: _onSubscriptionStateChange,
onCancel: _onSubscriptionStateChange,
onPause: _onPauseStateChange,
onResume: _onPauseStateChange,
);
_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.readClosed),
destroyed: () {
_controller.add(RawSocketEvent.closed);
_controller.close();
},
error: zone.bindBinaryCallbackGuarded((Object e, StackTrace? st) {
_controller.addError(e, st);
_socket.close();
}),
);
}
static Future<RawDatagramSocket> bind(
host,
int port,
bool reuseAddress,
bool reusePort,
int ttl,
) {
_throwOnBadPort(port);
_throwOnBadTtl(ttl);
return _NativeSocket.bindDatagram(
host,
port,
reuseAddress,
reusePort,
ttl,
).then((socket) {
if (!const bool.fromEnvironment("dart.vm.product")) {
_SocketProfile.collectNewSocket(
socket._nativeGetSocketId(),
_udpSocket,
socket.address,
port,
);
}
return _RawDatagramSocket(socket);
});
}
StreamSubscription<RawSocketEvent> listen(
void onData(RawSocketEvent event)?, {
Function? onError,
void onDone()?,
bool? cancelOnError,
}) {
return _controller.stream.listen(
onData,
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);
}
Future close() => _socket.close().then<RawDatagramSocket>((_) {
if (!const bool.fromEnvironment("dart.vm.product")) {
_SocketProfile.collectStatistic(
_socket._nativeGetSocketId(),
_SocketProfileType.endTime,
);
}
return this;
});
int send(List<int> buffer, InternetAddress address, int port) =>
_socket.send(buffer, 0, buffer.length, address, port);
Datagram? receive() {
return _socket.receive();
}
void joinMulticast(InternetAddress group, [NetworkInterface? interface]) {
_socket.joinMulticast(group, interface);
}
void leaveMulticast(InternetAddress group, [NetworkInterface? interface]) {
_socket.leaveMulticast(group, interface);
}
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 get multicastLoopback =>
_socket.getOption(SocketOption._ipMulticastLoop);
void set multicastLoopback(bool value) =>
_socket.setOption(SocketOption._ipMulticastLoop, value);
int get multicastHops => _socket.getOption(SocketOption._ipMulticastHops);
void set multicastHops(int value) =>
_socket.setOption(SocketOption._ipMulticastHops, value);
NetworkInterface get multicastInterface => throw UnimplementedError();
void set multicastInterface(NetworkInterface? value) =>
throw UnimplementedError();
bool get broadcastEnabled => _socket.getOption(SocketOption._ipBroadcast);
void set broadcastEnabled(bool value) =>
_socket.setOption(SocketOption._ipBroadcast, value);
int get port => _socket.port;
InternetAddress get address => _socket.address;
_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 {
_socket.close();
}
}
Uint8List getRawOption(RawSocketOption option) =>
_socket.getRawOption(option);
void setRawOption(RawSocketOption option) => _socket.setRawOption(option);
}
@pragma("vm:entry-point", "call")
Datagram _makeDatagram(
Uint8List data,
String address,
Uint8List in_addr,
int port,
int type,
) {
return Datagram(
data,
_InternetAddress(InternetAddressType._from(type), address, null, in_addr),
port,
);
}
@patch
@pragma("vm:entry-point")
class ResourceHandle {
@patch
factory ResourceHandle.fromFile(RandomAccessFile file) {
int fd = (file as _RandomAccessFile).fd;
return _ResourceHandleImpl(fd);
}
@patch
factory ResourceHandle.fromSocket(Socket socket) {
final _socket = socket as _Socket;
if (_socket._raw == null) {
throw ArgumentError("Socket is closed");
}
final _RawSocket raw = _socket._raw! as _RawSocket;
final _NativeSocket nativeSocket = raw._socket;
int fd = nativeSocket.fd;
return _ResourceHandleImpl(fd);
}
@patch
factory ResourceHandle.fromRawSocket(RawSocket socket) {
final _RawSocket raw = socket as _RawSocket;
final _NativeSocket nativeSocket = raw._socket;
int fd = nativeSocket.fd;
return _ResourceHandleImpl(fd);
}
@patch
factory ResourceHandle.fromRawDatagramSocket(RawDatagramSocket socket) {
final _RawDatagramSocket raw = socket as _RawDatagramSocket;
final _NativeSocket nativeSocket = socket._socket;
int fd = nativeSocket.fd;
return _ResourceHandleImpl(fd);
}
@patch
factory ResourceHandle.fromStdin(Stdin stdin) {
return _ResourceHandleImpl(stdin._fd);
}
@patch
factory ResourceHandle.fromStdout(Stdout stdout) {
return _ResourceHandleImpl(stdout._fd);
}
@patch
factory ResourceHandle.fromReadPipe(ReadPipe pipe) {
_ReadPipe rp = pipe as _ReadPipe;
return ResourceHandle.fromFile(rp._openedFile!);
}
@patch
factory ResourceHandle.fromWritePipe(WritePipe pipe) {
_WritePipe wp = pipe as _WritePipe;
return ResourceHandle.fromFile(wp._file);
}
}
@pragma("vm:entry-point")
class _ResourceHandleImpl implements ResourceHandle {
bool _toMethodCalled = false;
@pragma("vm:entry-point")
int _handle; // file descriptor on linux
@pragma("vm:entry-point")
_ResourceHandleImpl(this._handle);
RandomAccessFile toFile() {
if (_toMethodCalled) {
throw StateError('Resource handle has already been used.');
}
_toMethodCalled = true;
return _toFile();
}
RawDatagramSocket toRawDatagramSocket() {
if (_toMethodCalled) {
throw StateError('Resource handle has already been used.');
}
_toMethodCalled = true;
return _toRawDatagramSocket();
}
RawSocket toRawSocket() {
if (_toMethodCalled) {
throw StateError('Resource handle has already been used.');
}
_toMethodCalled = true;
List<dynamic> list = _toRawSocket();
InternetAddressType type = InternetAddressType._from(list[0] as int);
String hostname = list[1] as String;
Uint8List rawAddr = list[2] as Uint8List;
int fd = list[3] as int;
InternetAddress internetAddress = type == InternetAddressType.unix
? _InternetAddress.fromString(hostname, type: InternetAddressType.unix)
: _InternetAddress(type, hostname, null, rawAddr);
final nativeSocket = _NativeSocket.normal(internetAddress);
nativeSocket._nativeSetSocketId(fd, _NativeSocket.typeInternalSocket);
return _RawSocket(nativeSocket);
}
Socket toSocket() {
if (_toMethodCalled) {
throw StateError('Resource handle has already been used.');
}
_toMethodCalled = true;
return _toSocket();
}
_ReadPipe toReadPipe() {
return _ReadPipe(toFile());
}
_WritePipe toWritePipe() {
return _WritePipe(toFile());
}
@pragma("vm:external-name", "ResourceHandleImpl_toFile")
external RandomAccessFile _toFile();
@pragma("vm:external-name", "ResourceHandleImpl_toSocket")
external Socket _toSocket();
@pragma("vm:external-name", "ResourceHandleImpl_toRawSocket")
external List<dynamic> _toRawSocket();
@pragma("vm:external-name", "ResourceHandleImpl_toRawDatagramSocket")
external RawDatagramSocket _toRawDatagramSocket();
@pragma("vm:entry-point")
static final _ResourceHandleImpl _sentinel = _ResourceHandleImpl(-1);
}
@patch
class SocketControlMessage {
@pragma("vm:external-name", "SocketControlMessage_fromHandles")
@patch
external factory SocketControlMessage.fromHandles(
List<ResourceHandle> handles,
);
}
@pragma("vm:entry-point")
class _SocketControlMessageImpl implements SocketControlMessage {
@pragma("vm:entry-point")
final int level;
@pragma("vm:entry-point")
final int type;
@pragma("vm:entry-point")
final Uint8List data;
@pragma("vm:entry-point")
_SocketControlMessageImpl(this.level, this.type, this.data);
@pragma("vm:external-name", "SocketControlMessageImpl_extractHandles")
external List<ResourceHandle> extractHandles();
static final _sentinel = _SocketControlMessageImpl(0, 0, Uint8List(0));
}