import 'dart:async'; import 'dart:io'; import 'package:proto_socket/proto_socket.dart'; const _host = '127.0.0.1'; const _wsPath = '/'; const _heartbeatIntervalSeconds = 30; const _heartbeatWaitSeconds = 10; const _connectWindow = Duration(seconds: 3); const _requestWindow = Duration(seconds: 10); class _TcpClient extends ProtobufClient { _TcpClient(Socket socket) : super(socket, _heartbeatIntervalSeconds, _heartbeatWaitSeconds, { TestData.getDefault().info_.qualifiedMessageName: TestData.fromBuffer, }); } class _WsClient extends WsProtobufClient { _WsClient(WebSocket ws) : super(ws, _heartbeatIntervalSeconds, _heartbeatWaitSeconds, { TestData.getDefault().info_.qualifiedMessageName: TestData.fromBuffer, }); } class _ClientHandle { final Communicator client; final Future Function() close; _ClientHandle(this.client, this.close); } Future main(List args) async { final mode = _argValue(args, 'mode') ?? 'tcp'; final phase = _argValue(args, 'phase') ?? 'send-push'; final port = int.tryParse(_argValue(args, 'port') ?? ''); final certPath = _argValue(args, 'cert'); print( 'INFO typeName dart=${TestData.getDefault().info_.qualifiedMessageName}'); if (port == null) { _fail('setup', 'port is required'); exitCode = 1; return; } late _ClientHandle handle; try { handle = await _connectWithRetry(mode, port, certPath: certPath); } catch (error) { _fail('setup', error.toString()); exitCode = 1; return; } var ok = false; try { switch (phase) { case 'send-push': ok = await _runSendPush(handle.client); break; case 'requests': ok = await _runRequests(handle.client); break; case 'legacy-receive': ok = await _runLegacyReceive(handle.client); break; default: _fail('setup', 'unknown phase "$phase"'); } } finally { await handle.close(); } if (!ok) { exitCode = 1; } exit(exitCode); } Future<_ClientHandle> _connectWithRetry(String mode, int port, {String? certPath}) async { final deadline = DateTime.now().add(_connectWindow); Object? lastError; while (DateTime.now().isBefore(deadline)) { try { switch (mode) { case 'tcp': final socket = await ProtobufClient.connect(_host, port) .timeout(const Duration(milliseconds: 300)); final client = _TcpClient(socket); return _ClientHandle(client, client.close); case 'ws': final ws = await WsProtobufClient.connect(_host, port, path: _wsPath) .timeout(const Duration(milliseconds: 300)); final client = _WsClient(ws); return _ClientHandle(client, client.close); case 'tls': final ctx = _buildClientSecurityContext(certPath, mode); final socket = await ProtobufClient.connectSecure( _host, port, context: ctx, onBadCertificate: (certificate) => _acceptTestCertificate(certPath, certificate), ).timeout(const Duration(milliseconds: 300)); final client = _TcpClient(socket); return _ClientHandle(client, client.close); case 'wss': final ctx = _buildClientSecurityContext(certPath, mode); final ws = await WsProtobufClient.connectSecure(_host, port, path: _wsPath, context: ctx, onBadCertificate: (certificate) => _acceptTestCertificate(certPath, certificate)) .timeout(const Duration(milliseconds: 300)); final client = _WsClient(ws); return _ClientHandle(client, client.close); default: throw ArgumentError('unknown mode "$mode"'); } } catch (error) { lastError = error; await Future.delayed(const Duration(milliseconds: 100)); } } throw TimeoutException( 'connect $mode:$port timed out: $lastError', _connectWindow); } SecurityContext _buildClientSecurityContext(String? certPath, String mode) { if (certPath == null || certPath.isEmpty) { throw ArgumentError('--cert is required for $mode mode'); } return SecurityContext(withTrustedRoots: false) ..setTrustedCertificates(certPath); } bool _acceptTestCertificate(String? certPath, X509Certificate certificate) { if (certPath == null || certPath.isEmpty) { return false; } return certificate.pem == File(certPath).readAsStringSync(); } Future _runSendPush(Communicator client) async { final pushCompleter = Completer(); client.addListener((data) { if (!pushCompleter.isCompleted) { pushCompleter.complete(data); } }); await client.send(TestData() ..index = 101 ..message = 'fire from dart client'); _pass('1', 'fire-and-forget sent'); try { final push = await pushCompleter.future.timeout(_requestWindow); if (push.index != 200 || push.message != 'push from python server') { _fail( '2', 'unexpected push index=${push.index} message="${push.message}"'); return false; } _pass('2', 'received push from python server'); return true; } catch (error) { _fail('2', error.toString()); return false; } } Future _runRequests(Communicator client) async { try { final single = await client .sendRequest(TestData() ..index = 21 ..message = 'single request from dart') .timeout(_requestWindow); if (single.index != 42 || single.message != 'echo: single request from dart') { _fail('3', 'unexpected response index=${single.index} message="${single.message}"'); return false; } _pass('3', 'single request response matched'); final futures = >[]; for (var i = 0; i < _concurrencyCount(); i++) { futures.add(() async { final index = 30 + i; final message = 'multi request $i from dart'; final res = await client .sendRequest(TestData() ..index = index ..message = message) .timeout(_requestWindow); if (res.index != index * 2 || res.message != 'echo: $message') { throw StateError( 'request $i got index=${res.index} message="${res.message}"'); } }()); } await Future.wait(futures); _pass('4', 'concurrent request responses matched'); return true; } catch (error) { _fail('4', error.toString()); return false; } } Future _runLegacyReceive(Communicator client) async { final data = (TestData() ..index = 101 ..message = 'legacy fire from dart') .writeToBuffer(); await client.queuePacket(PacketBase() ..typeName = 'TestData' ..nonce = 1 ..data = data); await Future.delayed(const Duration(milliseconds: 150)); return true; } int _concurrencyCount() { final raw = Platform.environment["PROTO_SOCKET_CROSSTEST_CONCURRENCY"] ?? "5"; final value = int.tryParse(raw); if (value == null || value <= 0) { return 5; } return value; } String? _argValue(List args, String name) { final prefix = '--$name='; for (final arg in args) { if (arg.startsWith(prefix)) { return arg.substring(prefix.length); } } return null; } void _pass(String scenario, String detail) { print('PASS scenario=$scenario detail=$detail'); } void _fail(String scenario, String error) { print('FAIL scenario=$scenario error=$error'); }