83 lines
5.0 KiB
Dart
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);
|
|
}
|
|
}
|