| // 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. |
| |
| // @dart = 2.9 |
| |
| import "dart:async"; |
| import "dart:io"; |
| import "dart:typed_data"; |
| |
| import "package:async_helper/async_helper.dart"; |
| import "package:expect/expect.dart"; |
| |
| class FutureExpect { |
| static Future check(Future result, check) => |
| result.then((value) => check(value)); |
| static Future throws(Future result) => result.then((value) { |
| throw new ExpectException( |
| "FutureExpect.throws received $value instead of an exception"); |
| }, onError: (_) => null); |
| } |
| |
| testDatagramBroadcastOptions() { |
| test(address) { |
| asyncStart(); |
| RawDatagramSocket.bind(address, 0).then((socket) { |
| Expect.isFalse(socket.broadcastEnabled); |
| socket.broadcastEnabled = true; |
| if (!Platform.isMacOS) { |
| Expect.isTrue(socket.broadcastEnabled); |
| } |
| socket.broadcastEnabled = false; |
| Expect.isFalse(socket.broadcastEnabled); |
| asyncEnd(); |
| }); |
| } |
| |
| test(InternetAddress.loopbackIPv4); |
| test(InternetAddress.anyIPv4); |
| } |
| |
| testDatagramMulticastOptions() { |
| test(address) { |
| asyncStart(); |
| RawDatagramSocket.bind(address, 0).then((socket) { |
| Expect.isTrue(socket.multicastLoopback); |
| Expect.equals(1, socket.multicastHops); |
| Expect.throws(() => socket.multicastInterface); |
| |
| socket.multicastLoopback = false; |
| socket.multicastHops = 4; |
| Expect.isFalse(socket.multicastLoopback); |
| Expect.equals(4, socket.multicastHops); |
| Expect.throws(() => socket.multicastInterface = null); |
| |
| socket.multicastLoopback = true; |
| socket.multicastHops = 1; |
| Expect.isTrue(socket.multicastLoopback); |
| Expect.equals(1, socket.multicastHops); |
| Expect.throws(() => socket.multicastInterface); |
| |
| asyncEnd(); |
| }); |
| } |
| |
| test(InternetAddress.loopbackIPv4); |
| test(InternetAddress.anyIPv4); |
| test(InternetAddress.loopbackIPv6); |
| test(InternetAddress.anyIPv6); |
| } |
| |
| testDatagramSocketReuseAddress() { |
| test(address, reuseAddress) { |
| asyncStart(); |
| RawDatagramSocket.bind(address, 0, |
| reuseAddress: reuseAddress, |
| reusePort: Platform.isMacOS && reuseAddress) |
| .then((socket) async { |
| if (reuseAddress) { |
| RawDatagramSocket.bind(address, socket.port, |
| reusePort: Platform.isMacOS) |
| .then((s) => Expect.isTrue(s is RawDatagramSocket)) |
| .then(asyncSuccess); |
| } else { |
| await FutureExpect.throws(RawDatagramSocket.bind(address, socket.port)) |
| .then(asyncSuccess); |
| } |
| }); |
| } |
| |
| test(InternetAddress.loopbackIPv4, true); |
| test(InternetAddress.loopbackIPv4, false); |
| test(InternetAddress.loopbackIPv6, true); |
| test(InternetAddress.loopbackIPv6, false); |
| } |
| |
| testDatagramSocketTtl() { |
| test(address, ttl, shouldSucceed) { |
| asyncStart(); |
| if (shouldSucceed) { |
| RawDatagramSocket.bind(address, 0, ttl: ttl).then(asyncSuccess); |
| } else { |
| Expect.throws(() => RawDatagramSocket.bind(address, 0, ttl: ttl)); |
| asyncEnd(); |
| } |
| } |
| |
| test(InternetAddress.loopbackIPv4, 1, true); |
| test(InternetAddress.loopbackIPv4, 255, true); |
| test(InternetAddress.loopbackIPv4, 256, false); |
| test(InternetAddress.loopbackIPv4, 0, false); |
| test(InternetAddress.loopbackIPv4, null, false); |
| |
| test(InternetAddress.loopbackIPv6, 1, true); |
| test(InternetAddress.loopbackIPv6, 255, true); |
| test(InternetAddress.loopbackIPv6, 256, false); |
| test(InternetAddress.loopbackIPv6, 0, false); |
| test(InternetAddress.loopbackIPv6, null, false); |
| } |
| |
| testDatagramSocketMulticastIf() { |
| test(address) async { |
| asyncStart(); |
| final socket = await RawDatagramSocket.bind(address, 0); |
| RawSocketOption option; |
| int idx; |
| if (address.type == InternetAddressType.IPv4) { |
| option = RawSocketOption(RawSocketOption.levelIPv4, |
| RawSocketOption.IPv4MulticastInterface, address.rawAddress); |
| } else { |
| if (!NetworkInterface.listSupported) { |
| asyncEnd(); |
| return; |
| } |
| var interface = await NetworkInterface.list(); |
| if (interface.length == 0) { |
| asyncEnd(); |
| return; |
| } |
| idx = interface[0].index; |
| option = RawSocketOption.fromInt(RawSocketOption.levelIPv6, |
| RawSocketOption.IPv6MulticastInterface, idx); |
| } |
| |
| socket.setRawOption(option); |
| final getResult = socket.getRawOption(option); |
| |
| if (address.type == InternetAddressType.IPv4) { |
| Expect.listEquals(getResult, address.rawAddress); |
| } else { |
| // RawSocketOption.fromInt() will create a Uint8List(4). |
| Expect.equals( |
| getResult.buffer.asByteData().getUint32(0, Endian.host), idx); |
| } |
| |
| asyncSuccess(socket); |
| } |
| |
| test(InternetAddress.loopbackIPv4); |
| test(InternetAddress.anyIPv4); |
| test(InternetAddress.loopbackIPv6); |
| test(InternetAddress.anyIPv6); |
| } |
| |
| testBroadcast() { |
| test(bindAddress, broadcastAddress, enabled) { |
| asyncStart(); |
| Future.wait([ |
| RawDatagramSocket.bind(bindAddress, 0, reuseAddress: false), |
| RawDatagramSocket.bind(bindAddress, 0, reuseAddress: false) |
| ]).then((values) { |
| var broadcastTimer; |
| var sender = values[0]; |
| var receiver = values[1]; |
| // On Windows at least the receiver needs to have broadcast |
| // enabled whereas on Linux at least the sender needs to. |
| receiver.broadcastEnabled = enabled; |
| sender.broadcastEnabled = enabled; |
| receiver.listen((event) { |
| if (event == RawSocketEvent.read) { |
| Expect.isTrue(enabled); |
| sender.close(); |
| receiver.close(); |
| broadcastTimer.cancel(); |
| asyncEnd(); |
| } |
| }); |
| |
| int sendCount = 0; |
| send(_) { |
| int bytes = |
| sender.send(new Uint8List(1), broadcastAddress, receiver.port); |
| Expect.isTrue(bytes == 0 || bytes == 1); |
| sendCount++; |
| if (!enabled && sendCount == 50) { |
| sender.close(); |
| receiver.close(); |
| broadcastTimer.cancel(); |
| asyncEnd(); |
| } |
| } |
| |
| broadcastTimer = new Timer.periodic(new Duration(milliseconds: 10), send); |
| }); |
| } |
| |
| var broadcast = new InternetAddress("255.255.255.255"); |
| test(InternetAddress.anyIPv4, broadcast, false); |
| test(InternetAddress.anyIPv4, broadcast, true); |
| } |
| |
| testLoopbackMulticast() { |
| test(bindAddress, multicastAddress, enabled) { |
| asyncStart(); |
| Future.wait([ |
| RawDatagramSocket.bind(bindAddress, 0, reuseAddress: false), |
| RawDatagramSocket.bind(bindAddress, 0, reuseAddress: false) |
| ]).then((values) { |
| var senderTimer; |
| var sender = values[0]; |
| var receiver = values[1]; |
| |
| sender.joinMulticast(multicastAddress); |
| receiver.joinMulticast(multicastAddress); |
| // On Windows at least the receiver needs to have multicast |
| // loop enabled whereas on Linux at least the sender needs to. |
| receiver.multicastLoopback = enabled; |
| sender.multicastLoopback = enabled; |
| |
| receiver.listen((event) { |
| if (event == RawSocketEvent.read) { |
| if (!enabled) { |
| var data = receiver.receive(); |
| print(data.port); |
| print(data.address); |
| } |
| Expect.isTrue(enabled); |
| sender.close(); |
| receiver.close(); |
| senderTimer.cancel(); |
| asyncEnd(); |
| } |
| }); |
| |
| int sendCount = 0; |
| send(_) { |
| int bytes = |
| sender.send(new Uint8List(1), multicastAddress, receiver.port); |
| Expect.isTrue(bytes == 0 || bytes == 1); |
| sendCount++; |
| if (!enabled && sendCount == 50) { |
| sender.close(); |
| receiver.close(); |
| senderTimer.cancel(); |
| asyncEnd(); |
| } |
| } |
| |
| senderTimer = new Timer.periodic(new Duration(milliseconds: 10), send); |
| }); |
| } |
| |
| test(InternetAddress.anyIPv4, new InternetAddress("228.0.0.4"), true); |
| test(InternetAddress.anyIPv4, new InternetAddress("224.0.0.0"), false); |
| // TODO(30306): Reenable for Linux |
| if (!Platform.isMacOS && !Platform.isLinux) { |
| test(InternetAddress.anyIPv6, new InternetAddress("ff11::0"), true); |
| test(InternetAddress.anyIPv6, new InternetAddress("ff11::0"), false); |
| } |
| } |
| |
| testLoopbackMulticastError() { |
| var bindAddress = InternetAddress.anyIPv4; |
| var multicastAddress = new InternetAddress("228.0.0.4"); |
| asyncStart(); |
| Future.wait([ |
| RawDatagramSocket.bind(bindAddress, 0, reuseAddress: false), |
| RawDatagramSocket.bind(bindAddress, 0, reuseAddress: false) |
| ]).then((values) { |
| var sender = values[0]; |
| var receiver = values[1]; |
| Expect.throws(() { |
| sender.joinMulticast(new InternetAddress("127.0.0.1")); |
| }, (e) => e is! TypeError); |
| sender.close(); |
| receiver.close(); |
| asyncEnd(); |
| }); |
| } |
| |
| testSendReceive(InternetAddress bindAddress, int dataSize) { |
| asyncStart(); |
| |
| var total = 1000; |
| |
| int receivedSeq = 0; |
| |
| var ackSeq = 0; |
| Timer ackTimer; |
| |
| Future.wait([ |
| RawDatagramSocket.bind(bindAddress, 0, reuseAddress: false), |
| RawDatagramSocket.bind(bindAddress, 0, reuseAddress: false) |
| ]).then((values) { |
| var sender = values[0]; |
| var receiver = values[1]; |
| if (bindAddress.isMulticast) { |
| sender.multicastLoopback = true; |
| receiver.multicastLoopback = true; |
| sender.joinMulticast(bindAddress); |
| receiver.joinMulticast(bindAddress); |
| } |
| |
| Uint8List createDataPackage(int seq) { |
| var data = new Uint8List(dataSize); |
| (new ByteData.view(data.buffer, 0, 4)).setUint32(0, seq); |
| return data; |
| } |
| |
| Uint8List createAckPackage(int seq) { |
| var data = new Uint8List(4); |
| new ByteData.view(data.buffer, 0, 4).setUint32(0, seq); |
| return data; |
| } |
| |
| int packageSeq(Datagram datagram) => |
| new ByteData.view((datagram.data as Uint8List).buffer).getUint32(0); |
| |
| void sendData(int seq) { |
| // Send a datagram acknowledging the received sequence. |
| int bytes = |
| sender.send(createDataPackage(seq), bindAddress, receiver.port); |
| Expect.isTrue(bytes == 0 || bytes == dataSize); |
| } |
| |
| void sendAck(address, port) { |
| // Send a datagram acknowledging the received sequence. |
| int bytes = receiver.send(createAckPackage(receivedSeq), address, port); |
| Expect.isTrue(bytes == 0 || bytes == 4); |
| // Start a "long" timer for more data. |
| if (ackTimer != null) ackTimer.cancel(); |
| ackTimer = new Timer.periodic( |
| new Duration(milliseconds: 100), (_) => sendAck(address, port)); |
| } |
| |
| sender.listen((event) { |
| switch (event) { |
| case RawSocketEvent.read: |
| var datagram = sender.receive(); |
| if (datagram != null) { |
| Expect.equals(datagram.port, receiver.port); |
| if (!bindAddress.isMulticast) { |
| Expect.equals(receiver.address, datagram.address); |
| } |
| ackSeq = packageSeq(datagram); |
| if (ackSeq < total) { |
| sender.writeEventsEnabled = true; |
| } else { |
| sender.close(); |
| receiver.close(); |
| ackTimer.cancel(); |
| asyncEnd(); |
| } |
| } |
| break; |
| case RawSocketEvent.write: |
| // Send the next package. |
| sendData(ackSeq + 1); |
| break; |
| case RawSocketEvent.closed: |
| break; |
| default: |
| throw "Unexpected event $event"; |
| } |
| }); |
| |
| receiver.writeEventsEnabled = false; |
| receiver.listen((event) { |
| switch (event) { |
| case RawSocketEvent.read: |
| var datagram = receiver.receive(); |
| if (datagram != null) { |
| Expect.equals(datagram.port, sender.port); |
| Expect.equals(dataSize, datagram.data.length); |
| if (!bindAddress.isMulticast) { |
| Expect.equals(receiver.address, datagram.address); |
| } |
| var seq = packageSeq(datagram); |
| if (seq == receivedSeq + 1) { |
| receivedSeq = seq; |
| sendAck(bindAddress, sender.port); |
| } |
| } |
| break; |
| case RawSocketEvent.write: |
| throw "Unexpected.write"; |
| break; |
| case RawSocketEvent.closed: |
| break; |
| default: |
| throw "Unexpected event $event"; |
| } |
| }); |
| }); |
| } |
| |
| main() { |
| testDatagramBroadcastOptions(); |
| testDatagramMulticastOptions(); |
| testDatagramSocketReuseAddress(); |
| testDatagramSocketTtl(); |
| testDatagramSocketMulticastIf(); |
| testBroadcast(); |
| testLoopbackMulticast(); |
| testLoopbackMulticastError(); |
| testSendReceive(InternetAddress.loopbackIPv4, 1000); |
| testSendReceive(InternetAddress.loopbackIPv6, 1000); |
| if (!Platform.isMacOS) { |
| testSendReceive(InternetAddress.loopbackIPv4, 32 * 1024); |
| testSendReceive(InternetAddress.loopbackIPv6, 32 * 1024); |
| testSendReceive(InternetAddress.loopbackIPv4, 64 * 1024 - 32); |
| testSendReceive(InternetAddress.loopbackIPv6, 64 * 1024 - 32); |
| } |
| } |