Files
IKAR/tool/verify_video_delivery.dart
T

83 lines
5.0 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:crypto/crypto.dart';
import '../lib/core/transport/ikar_network.dart';
import 'live_ikar_smoke.dart' as live;
Future<void> main() async {
final devices = <live.Device>[];
final client = IkarNetwork.client();
final checks = <String>[];
final bytes = await File('assets/debug/fake_video_note.mp4').readAsBytes();
try {
final stamp = DateTime.now().microsecondsSinceEpoch.toString().substring(6);
for (var index = 0; index < 3; index++) {
devices.add(await live.Device.create(Uri.parse('https://ikar.kusoft.xyz'),
Platform.environment['IKAR_TEST_NODE'] ?? 'node', '+999$stamp$index', 'Video test $index'));
}
final sender = devices[0], recipient = devices[1], outsider = devices[2];
final chatId = sender.id ^ recipient.id;
final info = (await sender.ok(82, {'count': 1}))['info'][0];
final upload = await client.postUrl(Uri.parse(info['url']));
upload.headers.contentType = ContentType('application', 'x-binary');
upload.headers.set('Content-Range', 'bytes 0-${bytes.length - 1}/${bytes.length}');
upload.contentLength = bytes.length;
upload.add(bytes);
final uploaded = await upload.close();
live.check(uploaded.statusCode == 200, 'Chunked upload failed');
await uploaded.drain<void>();
checks.add('chunked MP4 upload');
final delivered = recipient.transport.events.firstWhere((event) => event['opcode'] == 128).timeout(const Duration(seconds: 30));
final sent = await sender.ok(64, {'chatId': chatId, 'message': {
'cid': DateTime.now().microsecondsSinceEpoch,
'attaches': [{'_type': 'VIDEO', 'token': info['token']}],
}});
await delivered;
final message = sent['message'];
final received = (await recipient.ok(49, {'chatId': chatId}))['messages'].firstWhere((item) => item['id'] == message['id']);
checks.add('video delivery and persistent history on recipient');
for (var index = 0; index < 2; index++) {
final peer = devices[index];
final item = (index == 0 ? message : received)['attaches'][0];
final sources = await peer.ok(83, {'chatId': chatId, 'messageId': message['id'], 'videoId': item['videoId'], 'token': item['token']});
live.check(sources['EXTERNAL'] is String && sources['HLS'] == null, 'Invalid video source keys');
final source = Uri.parse(sources['EXTERNAL']);
final response = await (await client.getUrl(source)).close();
live.check(response.statusCode == 200 && response.headers.contentType?.mimeType == 'video/mp4', 'Video source unavailable');
final downloaded = await response.fold<List<int>>([], (all, part) => all..addAll(part));
live.check(sha256.convert(downloaded) == sha256.convert(bytes), 'Downloaded video differs from upload');
final rangeRequest = await client.getUrl(source);
rangeRequest.headers.set('Range', 'bytes=0-31');
final range = await rangeRequest.close();
final rangeBytes = await range.fold<List<int>>([], (all, part) => all..addAll(part));
live.check(range.statusCode == 206 && rangeBytes.length == 32 &&
range.headers.value('Content-Range') == 'bytes 0-31/${bytes.length}', 'Seeking byte range unsupported');
final thumbnail = Uri.parse(item['thumbnail']);
live.check(thumbnail.path.endsWith('/thumbnail') && thumbnail.path != source.path &&
(item['width'] as int) > 0 && (item['height'] as int) > 0 && (item['duration'] as int) > 0, 'Preview metadata absent');
final image = await (await client.getUrl(thumbnail)).close();
final jpeg = await image.fold<List<int>>([], (all, part) => all..addAll(part));
live.check(image.statusCode == 200 && image.headers.contentType?.mimeType == 'image/jpeg' &&
jpeg.length > 100 && jpeg[0] == 255 && jpeg[1] == 216 && jpeg[2] == 255, 'JPEG preview unavailable');
if (index == 0) await File('artifacts/video-smoke-thumbnail.jpg').writeAsBytes(jpeg);
final wrongScope = thumbnail.replace(queryParameters: {'access_token': source.queryParameters['access_token']!});
final denied = await (await client.getUrl(wrongScope)).close();
live.check(denied.statusCode == 401, 'Video token escaped its path scope');
await denied.drain<void>();
checks.add('${index == 0 ? 'sender' : 'recipient'}: source/hash, seeking, JPEG/metadata and scoped token');
}
final denied = await outsider.transport.request(83, {'chatId': chatId, 'videoId': message['attaches'][0]['videoId']});
live.check(denied['cmd'] == 3, 'Nonmember obtained media source');
checks.add('nonmember isolation');
final report = {'passed': true, 'checks': checks, 'timestamp': DateTime.now().toUtc().toIso8601String()};
await File('artifacts/video-live-verification.json').writeAsString(const JsonEncoder.withIndent(' ').convert(report));
stdout.writeln(jsonEncode(report));
} finally {
for (final peer in devices.reversed) {
try { await peer.ok(199, {}); } finally { await peer.close(); }
}
client.close(force: true);
}
}