- Flutter app (Android/iOS): home, explore, albums, search, player with equalizer, mini player, sign up/sign in with email codes, profile, encrypted offline downloads, About page - server/: one-command deploy of self-hosted Supabase behind HestiaCP/nginx (or Caddy), migrations, backups, email templates - supabase/migrations: schema, row-level security, profiles - tools/: song scanner and importer Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
39 lines
1.4 KiB
Dart
39 lines
1.4 KiB
Dart
import 'dart:math';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:nanga_kathai/offline/song_cipher.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
void main() {
|
|
test('decrypting any range after a seek returns the original bytes', () {
|
|
final r = Random(1);
|
|
final plain = Uint8List.fromList(List.generate(100000, (_) => r.nextInt(256)));
|
|
final cipher = SongCipher(SongCipher.newKey());
|
|
final nonce = SongCipher.newNonce();
|
|
|
|
// Encrypt in uneven chunks, the way a download arrives.
|
|
final enc = Uint8List.fromList(plain);
|
|
for (var off = 0; off < enc.length; off += 7777) {
|
|
final end = min(off + 7777, enc.length);
|
|
final part = Uint8List.sublistView(enc, off, end);
|
|
cipher.apply(part, off, nonce);
|
|
}
|
|
expect(enc, isNot(equals(plain)));
|
|
|
|
for (final (start, end) in [(0, 100000), (5, 21), (16, 32), (99990, 100000), (12345, 67891)]) {
|
|
final chunk = Uint8List.fromList(enc.sublist(start, end));
|
|
cipher.apply(chunk, start, nonce);
|
|
expect(chunk, equals(plain.sublist(start, end)), reason: 'range $start-$end');
|
|
}
|
|
});
|
|
|
|
test('a different key cannot decrypt', () {
|
|
final plain = Uint8List.fromList(List.generate(64, (i) => i));
|
|
final nonce = SongCipher.newNonce();
|
|
final enc = Uint8List.fromList(plain);
|
|
SongCipher(SongCipher.newKey()).apply(enc, 0, nonce);
|
|
SongCipher(SongCipher.newKey()).apply(enc, 0, nonce);
|
|
expect(enc, isNot(equals(plain)));
|
|
});
|
|
}
|