Files
remotedesk/client/flutter_android/test/windows_agent_client_test.dart

261 lines
8.7 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:remotedesk_android/models.dart';
import 'package:remotedesk_android/windows_agent_client.dart';
import 'package:remotedesk_android/windows_webrtc_peer.dart';
void main() {
test('plain Windows Agent handshake delivers a bounded zlib frame', () async {
final server = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
final serverTask = server.first.then((socket) async {
socket.write(
'${jsonEncode({'kind': 'windows_agent_hello', 'protocol_major': 1, 'protocol_minor': 1, 'totp_required': true})}\n',
);
final lines = _lines(socket);
expect(await lines.moveNext(), isTrue);
expect(jsonDecode(lines.current)['totp_code'], '123456');
socket.write('{"kind":"authentication_ok"}\n');
expect(await lines.moveNext(), isTrue);
expect(jsonDecode(lines.current)['media_transport'], 'plain_stream');
socket.write(
'{"kind":"desktop_opened","encoding":"zlib_bgra",'
'"transport":"plain_stream"}\n',
);
socket.add(_frame(width: 2, height: 1, pixels: [1, 2, 3, 4, 5, 6, 7, 8]));
await socket.flush();
});
final client = WindowsAgentClient(
host: _host('windows-test', server.port),
settings: const AppSettings(framesPerSecond: 10),
);
final frameFuture = client.frames.first.timeout(const Duration(seconds: 5));
await client.connect(totpCode: '123456');
final frame = await frameFuture;
expect(frame.width, 2);
expect(frame.height, 1);
expect(frame.bgra, [1, 2, 3, 4, 5, 6, 7, 8]);
await client.dispose();
await serverTask;
await server.close();
});
test('Windows Agent prefers WebRTC video and DataChannels', () async {
final server = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
final peer = _FakeWebRtcPeer();
final serverTask = server.first.then((socket) async {
socket.write(
'${jsonEncode({
'kind': 'windows_agent_hello',
'protocol_major': 1,
'protocol_minor': 3,
'totp_required': true,
'supported_media_transports': ['webrtc', 'plain_stream'],
})}\n',
);
final lines = _lines(socket);
expect(await lines.moveNext(), isTrue);
socket.write('{"kind":"authentication_ok"}\n');
expect(await lines.moveNext(), isTrue);
expect(jsonDecode(lines.current)['media_transport'], 'webrtc');
socket.write(
'${jsonEncode({'kind': 'webrtc_media_ready', 'width': 2, 'height': 2, 'ice_servers': <Object>[], 'encrypted': true})}\n',
);
for (final expected in ['offer', 'ice_candidate', 'ice_end']) {
expect(await lines.moveNext(), isTrue);
final signal = jsonDecode(lines.current) as Map<String, dynamic>;
expect(signal['kind'], 'webrtc_signal');
expect(signal['signal_kind'], expected);
}
socket.write(
'${jsonEncode({'kind': 'webrtc_signal', 'signal_kind': 'answer', 'payload': '{"type":"answer","sdp":"answer-sdp"}'})}\n',
);
socket.write(
'${jsonEncode({'kind': 'webrtc_signal', 'signal_kind': 'ice_candidate', 'payload': '{"candidate":"candidate:2","sdpMid":"0","sdpMLineIndex":0}'})}\n',
);
socket.write(
'${jsonEncode({'kind': 'webrtc_signal', 'signal_kind': 'ice_end', 'payload': ''})}\n',
);
socket.write(
'{"kind":"desktop_opened","encoding":"h264",'
'"transport":"webrtc","width":2,"height":2}\n',
);
await socket.flush();
});
final client = WindowsAgentClient(
host: _host('windows-webrtc-test', server.port),
settings: const AppSettings(framesPerSecond: 30),
webRtcPeerFactory: () => peer,
);
final frameFuture = client.frames.first.timeout(const Duration(seconds: 5));
await client.connect(totpCode: '123456');
final frame = await frameFuture;
expect(frame.textureId, 77);
expect(frame.width, 2);
expect(frame.height, 2);
expect(peer.remoteSignals.map((signal) => signal.$1), [
'answer',
'ice_candidate',
'ice_end',
]);
client.sendPointer(1, 1);
client.sendButton('left', true);
expect(peer.commands[0].pointer, isTrue);
expect(peer.commands[0].value['input_type'], 'mouse');
expect(peer.commands[1].pointer, isFalse);
expect(peer.commands[1].value['mouse_action'], 'left_down');
await client.dispose();
expect(peer.closed, isTrue);
await serverTask;
await server.close();
});
test('Windows Agent falls back to TCP when WebRTC is unavailable', () async {
final server = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
final peer = _FakeWebRtcPeer();
final serverTask = server.first.then((socket) async {
socket.write(
'${jsonEncode({
'kind': 'windows_agent_hello',
'protocol_major': 1,
'protocol_minor': 3,
'totp_required': true,
'supported_media_transports': ['webrtc', 'plain_stream'],
})}\n',
);
final lines = _lines(socket);
expect(await lines.moveNext(), isTrue);
socket.write('{"kind":"authentication_ok"}\n');
expect(await lines.moveNext(), isTrue);
expect(jsonDecode(lines.current)['media_transport'], 'webrtc');
socket.write(
'${jsonEncode({'kind': 'webrtc_media_ready', 'width': 2, 'height': 1, 'ice_servers': <Object>[], 'encrypted': true})}\n',
);
for (var index = 0; index < 3; index++) {
expect(await lines.moveNext(), isTrue);
}
socket.write('{"kind":"webrtc_media_unavailable"}\n');
await socket.flush();
expect(await lines.moveNext(), isTrue);
expect(jsonDecode(lines.current)['media_transport'], 'plain_stream');
socket.write(
'{"kind":"desktop_opened","encoding":"zlib_bgra",'
'"transport":"plain_stream"}\n',
);
socket.add(_frame(width: 2, height: 1, pixels: [9, 8, 7, 6, 5, 4, 3, 2]));
await socket.flush();
});
final client = WindowsAgentClient(
host: _host('windows-fallback-test', server.port),
settings: const AppSettings(framesPerSecond: 15),
webRtcPeerFactory: () => peer,
);
final frameFuture = client.frames.first.timeout(const Duration(seconds: 5));
await client.connect(totpCode: '123456');
final frame = await frameFuture;
expect(frame.textureId, isNull);
expect(frame.bgra, [9, 8, 7, 6, 5, 4, 3, 2]);
expect(peer.closed, isTrue);
await client.dispose();
await serverTask;
await server.close();
});
}
RemoteHost _host(String id, int port) => RemoteHost(
id: id,
name: id,
address: '127.0.0.1:$port',
user: '',
certificateSha256: '',
platform: HostPlatform.windows,
);
StreamIterator<String> _lines(Socket socket) => StreamIterator(
socket
.cast<List<int>>()
.transform(utf8.decoder)
.transform(const LineSplitter()),
);
Uint8List _frame({
required int width,
required int height,
required List<int> pixels,
}) {
final compressed = ZLibCodec().encode(pixels);
final header = ByteData(40);
header.setUint8(0, 0x52);
header.setUint8(1, 0x44);
header.setUint8(2, 0x57);
header.setUint8(3, 0x46);
header.setUint8(4, 2);
header.setUint8(5, 1);
header.setUint32(8, width, Endian.little);
header.setUint32(12, height, Endian.little);
header.setUint32(16, pixels.length, Endian.little);
header.setUint32(20, compressed.length, Endian.little);
return Uint8List.fromList([...header.buffer.asUint8List(), ...compressed]);
}
class _FakeWebRtcPeer implements WindowsWebRtcPeer {
final remoteSignals = <(String, String)>[];
final commands = <({Map<String, Object?> value, bool pointer})>[];
bool closed = false;
@override
int get textureId => 77;
@override
int get videoWidth => 2;
@override
int get videoHeight => 2;
@override
Future<void> start({
required List<Map<String, dynamic>> iceServers,
required WindowsWebRtcSignalSender onSignal,
required void Function() onFailure,
}) async {
expect(iceServers, isEmpty);
onSignal('offer', '{"type":"offer","sdp":"offer-sdp"}');
onSignal(
'ice_candidate',
'{"candidate":"candidate:1","sdpMid":"0","sdpMLineIndex":0}',
);
onSignal('ice_end', '');
}
@override
Future<void> applySignal(String kind, String payload) async {
remoteSignals.add((kind, payload));
}
@override
Future<void> waitUntilReady(Duration timeout) async {}
@override
Future<void> waitForFirstFrame(Duration timeout) async {}
@override
void sendControl(Map<String, Object?> command, {required bool pointer}) {
commands.add((value: Map<String, Object?>.from(command), pointer: pointer));
}
@override
Future<void> close() async {
closed = true;
}
}