commit
2527ea57ee
|
|
@ -50,14 +50,15 @@ jobs:
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
runs-on: arm-ubuntu-latest-16core
|
#runs-on: arm-ubuntu-latest-16core
|
||||||
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- run: cat .github/workflows/versions.env >> $GITHUB_ENV
|
- run: cat .github/workflows/versions.env >> $GITHUB_ENV
|
||||||
- uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46
|
- uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46
|
||||||
with:
|
with:
|
||||||
sdk: ${{ env.dart_version }}
|
sdk: ${{ env.dart_version }}
|
||||||
architecture: "arm64"
|
#architecture: "arm64"
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update && sudo apt-get install --no-install-recommends --no-install-suggests -y lcov libsqlite3-0 libsqlite3-dev libolm3 libssl3
|
sudo apt-get update && sudo apt-get install --no-install-recommends --no-install-suggests -y lcov libsqlite3-0 libsqlite3-dev libolm3 libssl3
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
flutter_version=3.22.2
|
flutter_version=3.22.2
|
||||||
dart_version=3.4.3
|
dart_version=3.5.3
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import 'package:matrix/encryption/utils/json_signature_check_extension.dart';
|
||||||
import 'package:matrix/encryption/utils/olm_session.dart';
|
import 'package:matrix/encryption/utils/olm_session.dart';
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
import 'package:matrix/msc_extensions/msc_3814_dehydrated_devices/api.dart';
|
import 'package:matrix/msc_extensions/msc_3814_dehydrated_devices/api.dart';
|
||||||
|
import 'package:matrix/src/utils/run_benchmarked.dart';
|
||||||
import 'package:matrix/src/utils/run_in_root.dart';
|
import 'package:matrix/src/utils/run_in_root.dart';
|
||||||
|
|
||||||
class OlmManager {
|
class OlmManager {
|
||||||
|
|
@ -326,43 +327,50 @@ class OlmManager {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final _otkUpdateDedup = AsyncCache<void>.ephemeral();
|
||||||
|
|
||||||
Future<void> handleDeviceOneTimeKeysCount(
|
Future<void> handleDeviceOneTimeKeysCount(
|
||||||
Map<String, int>? countJson, List<String>? unusedFallbackKeyTypes) async {
|
Map<String, int>? countJson, List<String>? unusedFallbackKeyTypes) async {
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final haveFallbackKeys = encryption.isMinOlmVersion(3, 2, 0);
|
|
||||||
// Check if there are at least half of max_number_of_one_time_keys left on the server
|
|
||||||
// and generate and upload more if not.
|
|
||||||
|
|
||||||
// If the server did not send us a count, assume it is 0
|
await _otkUpdateDedup.fetch(() =>
|
||||||
final keyCount = countJson?.tryGet<int>('signed_curve25519') ?? 0;
|
runBenchmarked('handleOtkUpdate', () async {
|
||||||
|
final haveFallbackKeys = encryption.isMinOlmVersion(3, 2, 0);
|
||||||
|
// Check if there are at least half of max_number_of_one_time_keys left on the server
|
||||||
|
// and generate and upload more if not.
|
||||||
|
|
||||||
// If the server does not support fallback keys, it will not tell us about them.
|
// If the server did not send us a count, assume it is 0
|
||||||
// If the server supports them but has no key, upload a new one.
|
final keyCount = countJson?.tryGet<int>('signed_curve25519') ?? 0;
|
||||||
var unusedFallbackKey = true;
|
|
||||||
if (unusedFallbackKeyTypes?.contains('signed_curve25519') == false) {
|
|
||||||
unusedFallbackKey = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// fixup accidental too many uploads. We delete only one of them so that the server has time to update the counts and because we will get rate limited anyway.
|
// If the server does not support fallback keys, it will not tell us about them.
|
||||||
if (keyCount > _olmAccount!.max_number_of_one_time_keys()) {
|
// If the server supports them but has no key, upload a new one.
|
||||||
final requestingKeysFrom = {
|
var unusedFallbackKey = true;
|
||||||
client.userID!: {ourDeviceId!: 'signed_curve25519'}
|
if (unusedFallbackKeyTypes?.contains('signed_curve25519') == false) {
|
||||||
};
|
unusedFallbackKey = false;
|
||||||
await client.claimKeys(requestingKeysFrom, timeout: 10000);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Only upload keys if they are less than half of the max or we have no unused fallback key
|
// fixup accidental too many uploads. We delete only one of them so that the server has time to update the counts and because we will get rate limited anyway.
|
||||||
if (keyCount < (_olmAccount!.max_number_of_one_time_keys() / 2) ||
|
if (keyCount > _olmAccount!.max_number_of_one_time_keys()) {
|
||||||
!unusedFallbackKey) {
|
final requestingKeysFrom = {
|
||||||
await uploadKeys(
|
client.userID!: {ourDeviceId!: 'signed_curve25519'}
|
||||||
oldKeyCount: keyCount < (_olmAccount!.max_number_of_one_time_keys() / 2)
|
};
|
||||||
? keyCount
|
await client.claimKeys(requestingKeysFrom, timeout: 10000);
|
||||||
: null,
|
}
|
||||||
unusedFallbackKey: haveFallbackKeys ? unusedFallbackKey : null,
|
|
||||||
);
|
// Only upload keys if they are less than half of the max or we have no unused fallback key
|
||||||
}
|
if (keyCount < (_olmAccount!.max_number_of_one_time_keys() / 2) ||
|
||||||
|
!unusedFallbackKey) {
|
||||||
|
await uploadKeys(
|
||||||
|
oldKeyCount:
|
||||||
|
keyCount < (_olmAccount!.max_number_of_one_time_keys() / 2)
|
||||||
|
? keyCount
|
||||||
|
: null,
|
||||||
|
unusedFallbackKey: haveFallbackKeys ? unusedFallbackKey : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> storeOlmSession(OlmSession session) async {
|
Future<void> storeOlmSession(OlmSession session) async {
|
||||||
|
|
|
||||||
|
|
@ -127,10 +127,6 @@ class FakeMatrixApi extends BaseClient {
|
||||||
|
|
||||||
//print('\$method request to $action with Data: $data');
|
//print('\$method request to $action with Data: $data');
|
||||||
|
|
||||||
// Sync requests with timeout
|
|
||||||
if (data is Map<String, dynamic> && data['timeout'] is String) {
|
|
||||||
await Future.delayed(Duration(seconds: 5));
|
|
||||||
}
|
|
||||||
if (!servers.contains(request.url.origin)) {
|
if (!servers.contains(request.url.origin)) {
|
||||||
return Response(
|
return Response(
|
||||||
'<html><head></head><body>Not found ${request.url.origin}...</body></html>',
|
'<html><head></head><body>Not found ${request.url.origin}...</body></html>',
|
||||||
|
|
@ -202,8 +198,19 @@ class FakeMatrixApi extends BaseClient {
|
||||||
'/client/v3/rooms/!1234%3AfakeServer.notExisting/state/')) {
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/state/')) {
|
||||||
res = {'event_id': '\$event${_eventCounter++}'};
|
res = {'event_id': '\$event${_eventCounter++}'};
|
||||||
} else if (action.contains('/client/v3/sync')) {
|
} else if (action.contains('/client/v3/sync')) {
|
||||||
|
// Sync requests with timeout
|
||||||
|
final timeout = request.url.queryParameters['timeout'];
|
||||||
|
if (timeout != null && timeout != '0') {
|
||||||
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
|
}
|
||||||
res = {
|
res = {
|
||||||
'next_batch': DateTime.now().millisecondsSinceEpoch.toString(),
|
// So that it is clear which sync we are processing prefix it with 'empty_'
|
||||||
|
'next_batch': 'empty_${DateTime.now().millisecondsSinceEpoch}',
|
||||||
|
// ensure we don't generate new keys for no reason
|
||||||
|
'device_one_time_keys_count': {
|
||||||
|
'curve25519': 10,
|
||||||
|
'signed_curve25519': 100
|
||||||
|
},
|
||||||
};
|
};
|
||||||
} else if (method == 'PUT' &&
|
} else if (method == 'PUT' &&
|
||||||
_client != null &&
|
_client != null &&
|
||||||
|
|
@ -1037,7 +1044,7 @@ class FakeMatrixApi extends BaseClient {
|
||||||
'@bob:example.com',
|
'@bob:example.com',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
'device_one_time_keys_count': {'curve25519': 10, 'signed_curve25519': 20},
|
'device_one_time_keys_count': {'curve25519': 10, 'signed_curve25519': 100},
|
||||||
};
|
};
|
||||||
|
|
||||||
static Map<String, dynamic> archiveSyncResponse = {
|
static Map<String, dynamic> archiveSyncResponse = {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ void main() {
|
||||||
Logs().level = Level.info;
|
Logs().level = Level.info;
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
matrix = await getClient();
|
matrix = await getClient();
|
||||||
|
await matrix.abortSync();
|
||||||
|
|
||||||
voip = VoIP(matrix, MockWebRTCDelegate());
|
voip = VoIP(matrix, MockWebRTCDelegate());
|
||||||
VoIP.customTxid = '1234';
|
VoIP.customTxid = '1234';
|
||||||
|
|
|
||||||
|
|
@ -45,11 +45,13 @@ void main() {
|
||||||
final dbPath = join(Directory.current.path, 'test.sqlite');
|
final dbPath = join(Directory.current.path, 'test.sqlite');
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
expect(await File(dbPath).exists(), false);
|
expect(await File(dbPath).exists(), false,
|
||||||
|
reason: '$dbPath should not exist');
|
||||||
clientOnPath = await getClient(
|
clientOnPath = await getClient(
|
||||||
databasePath: dbPath,
|
databasePath: dbPath,
|
||||||
);
|
);
|
||||||
expect(await File(dbPath).exists(), true);
|
await clientOnPath.abortSync();
|
||||||
|
expect(await File(dbPath).exists(), true, reason: '$dbPath should exist');
|
||||||
});
|
});
|
||||||
test('logout', () async {
|
test('logout', () async {
|
||||||
expect(await File(dbPath).exists(), true);
|
expect(await File(dbPath).exists(), true);
|
||||||
|
|
@ -137,11 +139,12 @@ void main() {
|
||||||
newOlmAccount: pickledOlmAccount,
|
newOlmAccount: pickledOlmAccount,
|
||||||
);
|
);
|
||||||
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
|
||||||
|
|
||||||
final loginState = await loginStateFuture;
|
final loginState = await loginStateFuture;
|
||||||
final sync = await syncFuture;
|
final sync = await syncFuture;
|
||||||
|
|
||||||
|
// to ensure our state doesn't get overwritten once we manually inject SyncUpdates
|
||||||
|
await matrix.abortSync();
|
||||||
|
|
||||||
expect(loginState, LoginState.loggedIn);
|
expect(loginState, LoginState.loggedIn);
|
||||||
expect(matrix.onSync.value != null, true);
|
expect(matrix.onSync.value != null, true);
|
||||||
expect(matrix.encryptionEnabled, true);
|
expect(matrix.encryptionEnabled, true);
|
||||||
|
|
@ -185,7 +188,7 @@ void main() {
|
||||||
PresenceType.online);
|
PresenceType.online);
|
||||||
expect(presenceCounter, 1);
|
expect(presenceCounter, 1);
|
||||||
expect(accountDataCounter, 10);
|
expect(accountDataCounter, 10);
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
|
||||||
expect(matrix.userDeviceKeys.keys.toSet(), {
|
expect(matrix.userDeviceKeys.keys.toSet(), {
|
||||||
'@alice:example.com',
|
'@alice:example.com',
|
||||||
'@othertest:fakeServer.notExisting',
|
'@othertest:fakeServer.notExisting',
|
||||||
|
|
@ -1134,7 +1137,7 @@ void main() {
|
||||||
newOlmAccount: pickledOlmAccount,
|
newOlmAccount: pickledOlmAccount,
|
||||||
);
|
);
|
||||||
|
|
||||||
await Future.delayed(Duration(milliseconds: 500));
|
await client1.abortSync();
|
||||||
|
|
||||||
expect(client1.isLogged(), true);
|
expect(client1.isLogged(), true);
|
||||||
expect(client1.rooms.length, 3);
|
expect(client1.rooms.length, 3);
|
||||||
|
|
@ -1146,7 +1149,8 @@ void main() {
|
||||||
);
|
);
|
||||||
|
|
||||||
await client2.init();
|
await client2.init();
|
||||||
await Future.delayed(Duration(milliseconds: 500));
|
|
||||||
|
await client2.abortSync();
|
||||||
|
|
||||||
expect(client2.isLogged(), true);
|
expect(client2.isLogged(), true);
|
||||||
expect(client2.accessToken, client1.accessToken);
|
expect(client2.accessToken, client1.accessToken);
|
||||||
|
|
@ -1197,6 +1201,7 @@ void main() {
|
||||||
await client.getWellknown();
|
await client.getWellknown();
|
||||||
expect(
|
expect(
|
||||||
client.wellKnown?.mHomeserver.baseUrl.host, 'fakeserver.notexisting');
|
client.wellKnown?.mHomeserver.baseUrl.host, 'fakeserver.notexisting');
|
||||||
|
await client.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('refreshAccessToken', () async {
|
test('refreshAccessToken', () async {
|
||||||
|
|
@ -1204,6 +1209,7 @@ void main() {
|
||||||
expect(client.accessToken, 'abcd');
|
expect(client.accessToken, 'abcd');
|
||||||
await client.refreshAccessToken();
|
await client.refreshAccessToken();
|
||||||
expect(client.accessToken, 'a_new_token');
|
expect(client.accessToken, 'a_new_token');
|
||||||
|
await client.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handleSoftLogout', () async {
|
test('handleSoftLogout', () async {
|
||||||
|
|
@ -1226,6 +1232,7 @@ void main() {
|
||||||
storedClient?.tryGet<String>('refresh_token'),
|
storedClient?.tryGet<String>('refresh_token'),
|
||||||
'another_new_token',
|
'another_new_token',
|
||||||
);
|
);
|
||||||
|
await client.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('object equality', () async {
|
test('object equality', () async {
|
||||||
|
|
@ -1274,6 +1281,7 @@ void main() {
|
||||||
final client = await getClient();
|
final client = await getClient();
|
||||||
client.backgroundSync = true;
|
client.backgroundSync = true;
|
||||||
await client.clearCache();
|
await client.clearCache();
|
||||||
|
await client.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('dispose', () async {
|
test('dispose', () async {
|
||||||
|
|
@ -1311,6 +1319,7 @@ void main() {
|
||||||
await hiveClient.init();
|
await hiveClient.init();
|
||||||
await Future.delayed(Duration(milliseconds: 200));
|
await Future.delayed(Duration(milliseconds: 200));
|
||||||
expect(hiveClient.isLogged(), true);
|
expect(hiveClient.isLogged(), true);
|
||||||
|
await hiveClient.dispose(closeDatabase: false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getEventByPushNotification', () async {
|
test('getEventByPushNotification', () async {
|
||||||
|
|
@ -1402,6 +1411,8 @@ void main() {
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
reason: '!5345234235:example.com not found as archived room');
|
reason: '!5345234235:example.com not found as archived room');
|
||||||
|
|
||||||
|
await client.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test(
|
||||||
|
|
@ -1430,6 +1441,7 @@ void main() {
|
||||||
expect(error.userId, '@user:server');
|
expect(error.userId, '@user:server');
|
||||||
expect(error.toString(), 'Exception: BAD_ACCOUNT_KEY');
|
expect(error.toString(), 'Exception: BAD_ACCOUNT_KEY');
|
||||||
}
|
}
|
||||||
|
await customClient.dispose(closeDatabase: true);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ void main() {
|
||||||
|
|
||||||
test('setupClient', () async {
|
test('setupClient', () async {
|
||||||
client = await getClient();
|
client = await getClient();
|
||||||
|
await client.abortSync();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('fromJson', () async {
|
test('fromJson', () async {
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ void main() {
|
||||||
await olm.init();
|
await olm.init();
|
||||||
olm.get_library_version();
|
olm.get_library_version();
|
||||||
client = await getClient();
|
client = await getClient();
|
||||||
|
await client.abortSync();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('basic things', () async {
|
test('basic things', () async {
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,8 @@ void main() {
|
||||||
});
|
});
|
||||||
test('Reply To Request', () async {
|
test('Reply To Request', () async {
|
||||||
final matrix = await getClient();
|
final matrix = await getClient();
|
||||||
|
await matrix.abortSync();
|
||||||
|
|
||||||
matrix.setUserId('@alice:example.com'); // we need to pretend to be alice
|
matrix.setUserId('@alice:example.com'); // we need to pretend to be alice
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
await matrix
|
await matrix
|
||||||
|
|
|
||||||
|
|
@ -95,8 +95,13 @@ void main() async {
|
||||||
KeyVerificationMethod.reciprocate
|
KeyVerificationMethod.reciprocate
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// cancel sync to reduce background load and prevent sync overwriting which keys are tracked.
|
||||||
|
await client1.abortSync();
|
||||||
|
await client2.abortSync();
|
||||||
|
|
||||||
// get client2 device keys to start verification
|
// get client2 device keys to start verification
|
||||||
await client1.updateUserDeviceKeys(additionalUsers: {client2.userID!});
|
await client1.updateUserDeviceKeys(additionalUsers: {client2.userID!});
|
||||||
|
await client2.updateUserDeviceKeys(additionalUsers: {client1.userID!});
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ Future<Client> getClient({
|
||||||
newOlmAccount: pickledOlmAccount,
|
newOlmAccount: pickledOlmAccount,
|
||||||
);
|
);
|
||||||
await Future.delayed(Duration(milliseconds: 10));
|
await Future.delayed(Duration(milliseconds: 10));
|
||||||
|
await client.abortSync();
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ void main() {
|
||||||
Logs().level = Level.error;
|
Logs().level = Level.error;
|
||||||
test('Login', () async {
|
test('Login', () async {
|
||||||
matrix = await getClient();
|
matrix = await getClient();
|
||||||
|
await matrix.abortSync();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Create from json', () async {
|
test('Create from json', () async {
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ void main() {
|
||||||
client = await getClient(
|
client = await getClient(
|
||||||
sendTimelineEventTimeout: const Duration(seconds: 5),
|
sendTimelineEventTimeout: const Duration(seconds: 5),
|
||||||
);
|
);
|
||||||
|
await client.abortSync();
|
||||||
|
|
||||||
final poison = Random().nextInt(2 ^ 32);
|
final poison = Random().nextInt(2 ^ 32);
|
||||||
currentPoison = poison;
|
currentPoison = poison;
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ void main() {
|
||||||
await client.login(LoginType.mLoginPassword,
|
await client.login(LoginType.mLoginPassword,
|
||||||
identifier: AuthenticationUserIdentifier(user: 'test'),
|
identifier: AuthenticationUserIdentifier(user: 'test'),
|
||||||
password: '1234');
|
password: '1234');
|
||||||
|
await client.abortSync();
|
||||||
});
|
});
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
await client.logout();
|
await client.logout();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue