blob: f3047e5deeedf4c077464ebd50daba19cec60115 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'package:multicast_dns/multicast_dns.dart';
import 'package:test/fake.dart';
import 'package:test/test.dart';
void main() {
test('Can inject datagram socket factory and configure mdns port', () async {
late int lastPort;
final FakeRawDatagramSocket datagramSocket = FakeRawDatagramSocket();
final MDnsClient client = MDnsClient(rawDatagramSocketFactory:
(dynamic host, int port,
{bool reuseAddress = true,
bool reusePort = true,
int ttl = 1}) async {
lastPort = port;
return datagramSocket;
});
await client.start(
mDnsPort: 1234,
interfacesFactory: (InternetAddressType type) async =>
<NetworkInterface>[]);
expect(lastPort, 1234);
});
test('Closes IPv4 sockets', () async {
final FakeRawDatagramSocket datagramSocket = FakeRawDatagramSocket();
final MDnsClient client = MDnsClient(rawDatagramSocketFactory:
(dynamic host, int port,
{bool reuseAddress = true,
bool reusePort = true,
int ttl = 1}) async {
return datagramSocket;
});
await client.start(
mDnsPort: 1234,
interfacesFactory: (InternetAddressType type) async =>
<NetworkInterface>[]);
expect(datagramSocket.closed, false);
client.stop();
expect(datagramSocket.closed, true);
});
test('Closes IPv6 sockets', () async {
final FakeRawDatagramSocket datagramSocket = FakeRawDatagramSocket();
datagramSocket.address = InternetAddress.anyIPv6;
final MDnsClient client = MDnsClient(rawDatagramSocketFactory:
(dynamic host, int port,
{bool reuseAddress = true,
bool reusePort = true,
int ttl = 1}) async {
return datagramSocket;
});
await client.start(
mDnsPort: 1234,
interfacesFactory: (InternetAddressType type) async =>
<NetworkInterface>[]);
expect(datagramSocket.closed, false);
client.stop();
expect(datagramSocket.closed, true);
});
test('start() is idempotent', () async {
final FakeRawDatagramSocket datagramSocket = FakeRawDatagramSocket();
datagramSocket.address = InternetAddress.anyIPv4;
final MDnsClient client = MDnsClient(rawDatagramSocketFactory:
(dynamic host, int port,
{bool reuseAddress = true,
bool reusePort = true,
int ttl = 1}) async {
return datagramSocket;
});
await client.start(
interfacesFactory: (InternetAddressType type) async =>
<NetworkInterface>[]);
await client.start();
await client.lookup(ResourceRecordQuery.serverPointer('_')).toList();
});
group('Bind a single socket to ANY IPv4 and more than one when IPv6', () {
final List<Map<String, Object>> testCases = <Map<String, Object>>[
<String, Object>{
'name': 'IPv4',
'datagramSocketType': InternetAddress.anyIPv4,
'interfacePrefix': '192.168.2.'
},
<String, Object>{
'name': 'IPv6',
'datagramSocketType': InternetAddress.anyIPv6,
'interfacePrefix': '2001:0db8:85a3:0000:0000:8a2e:7335:030'
}
];
for (final Map<String, Object> testCase in testCases) {
test('Bind a single socket to ANY ${testCase["name"]}', () async {
final FakeRawDatagramSocket datagramSocket = FakeRawDatagramSocket();
datagramSocket.address =
testCase['datagramSocketType']! as InternetAddress;
final List<dynamic> selectedInterfacesForSendingPackets = <dynamic>[];
final MDnsClient client = MDnsClient(rawDatagramSocketFactory:
(dynamic host, int port,
{bool reuseAddress = true,
bool reusePort = true,
int ttl = 1}) async {
selectedInterfacesForSendingPackets.add(host);
return datagramSocket;
});
const int numberOfFakeInterfaces = 10;
Future<Iterable<NetworkInterface>> fakeNetworkInterfacesFactory(
InternetAddressType type) async {
final List<NetworkInterface> fakeInterfaces = <NetworkInterface>[];
// Generate "fake" interfaces
for (int i = 0; i < numberOfFakeInterfaces; i++) {
fakeInterfaces.add(FakeNetworkInterface(
'inetfake$i',
<InternetAddress>[
InternetAddress("${testCase['interfacePrefix']! as String}$i")
],
0,
));
}
// ignore: always_specify_types
return Future.value(fakeInterfaces);
}
final InternetAddress listenAddress =
testCase['datagramSocketType']! as InternetAddress;
await client.start(
listenAddress: listenAddress,
mDnsPort: 1234,
interfacesFactory: fakeNetworkInterfacesFactory);
client.stop();
if (testCase['datagramSocketType'] == InternetAddress.anyIPv4) {
expect(selectedInterfacesForSendingPackets.length, 1);
} else {
// + 1 because of unspecified address (::)
expect(selectedInterfacesForSendingPackets.length,
numberOfFakeInterfaces + 1);
}
expect(selectedInterfacesForSendingPackets[0], listenAddress.address);
});
}
});
}
class FakeRawDatagramSocket extends Fake implements RawDatagramSocket {
@override
InternetAddress address = InternetAddress.anyIPv4;
@override
StreamSubscription<RawSocketEvent> listen(
void Function(RawSocketEvent event)? onData,
{Function? onError,
void Function()? onDone,
bool? cancelOnError}) {
return const Stream<RawSocketEvent>.empty().listen(onData,
onError: onError, cancelOnError: cancelOnError, onDone: onDone);
}
bool closed = false;
@override
void close() {
closed = true;
}
@override
int send(List<int> buffer, InternetAddress address, int port) {
return buffer.length;
}
@override
void joinMulticast(InternetAddress group, [NetworkInterface? interface]) {
// nothing to do here
}
@override
void setRawOption(RawSocketOption option) {
// nothing to do here
}
}
class FakeNetworkInterface implements NetworkInterface {
FakeNetworkInterface(this._name, this._addresses, this._index);
final String _name;
final List<InternetAddress> _addresses;
final int _index;
@override
List<InternetAddress> get addresses => _addresses;
@override
String get name => _name;
@override
int get index => _index;
}