decrypt events in sync loop, making it async

This commit is contained in:
Sorunome 2020-05-19 09:49:37 +02:00
parent 3ee5c2effa
commit c5e4e2c751
No known key found for this signature in database
GPG key ID: B19471D07FC9BE9C
3 changed files with 49 additions and 69 deletions

View file

@ -986,16 +986,17 @@ class Client {
final syncResp = await _syncRequest; final syncResp = await _syncRequest;
if (hash != _syncRequest.hashCode) return; if (hash != _syncRequest.hashCode) return;
_timeoutFactor = 1; _timeoutFactor = 1;
final futures = handleSync(syncResp); if (database != null) {
if (_disposed) return; await database.transaction(() async {
await database?.transaction(() async { await handleSync(syncResp);
for (final f in futures) {
await f();
}
if (prevBatch != syncResp['next_batch']) { if (prevBatch != syncResp['next_batch']) {
await database.storePrevBatch(syncResp['next_batch'], id); await database.storePrevBatch(syncResp['next_batch'], id);
} }
}); });
} else {
await handleSync(syncResp);
}
if (_disposed) return;
if (prevBatch == null) { if (prevBatch == null) {
onFirstSync.add(true); onFirstSync.add(true);
prevBatch = syncResp['next_batch']; prevBatch = syncResp['next_batch'];
@ -1015,34 +1016,33 @@ class Client {
} }
/// Use this method only for testing utilities! /// Use this method only for testing utilities!
List<Future<dynamic> Function()> handleSync(dynamic sync) { Future<void> handleSync(dynamic sync) async {
final dbActions = <Future<dynamic> Function()>[];
if (sync['to_device'] is Map<String, dynamic> && if (sync['to_device'] is Map<String, dynamic> &&
sync['to_device']['events'] is List<dynamic>) { sync['to_device']['events'] is List<dynamic>) {
_handleToDeviceEvents(sync['to_device']['events']); _handleToDeviceEvents(sync['to_device']['events']);
} }
if (sync['rooms'] is Map<String, dynamic>) { if (sync['rooms'] is Map<String, dynamic>) {
if (sync['rooms']['join'] is Map<String, dynamic>) { if (sync['rooms']['join'] is Map<String, dynamic>) {
_handleRooms(sync['rooms']['join'], Membership.join, dbActions); await _handleRooms(sync['rooms']['join'], Membership.join);
} }
if (sync['rooms']['invite'] is Map<String, dynamic>) { if (sync['rooms']['invite'] is Map<String, dynamic>) {
_handleRooms(sync['rooms']['invite'], Membership.invite, dbActions); await _handleRooms(sync['rooms']['invite'], Membership.invite);
} }
if (sync['rooms']['leave'] is Map<String, dynamic>) { if (sync['rooms']['leave'] is Map<String, dynamic>) {
_handleRooms(sync['rooms']['leave'], Membership.leave, dbActions); await _handleRooms(sync['rooms']['leave'], Membership.leave);
} }
} }
if (sync['presence'] is Map<String, dynamic> && if (sync['presence'] is Map<String, dynamic> &&
sync['presence']['events'] is List<dynamic>) { sync['presence']['events'] is List<dynamic>) {
_handleGlobalEvents(sync['presence']['events'], 'presence', dbActions); await _handleGlobalEvents(sync['presence']['events'], 'presence');
} }
if (sync['account_data'] is Map<String, dynamic> && if (sync['account_data'] is Map<String, dynamic> &&
sync['account_data']['events'] is List<dynamic>) { sync['account_data']['events'] is List<dynamic>) {
_handleGlobalEvents( await _handleGlobalEvents(
sync['account_data']['events'], 'account_data', dbActions); sync['account_data']['events'], 'account_data');
} }
if (sync['device_lists'] is Map<String, dynamic>) { if (sync['device_lists'] is Map<String, dynamic>) {
_handleDeviceListsEvents(sync['device_lists'], dbActions); await _handleDeviceListsEvents(sync['device_lists']);
} }
if (sync['device_one_time_keys_count'] is Map<String, dynamic>) { if (sync['device_one_time_keys_count'] is Map<String, dynamic>) {
_handleDeviceOneTimeKeysCount(sync['device_one_time_keys_count']); _handleDeviceOneTimeKeysCount(sync['device_one_time_keys_count']);
@ -1054,7 +1054,6 @@ class Client {
); );
} }
onSync.add(sync); onSync.add(sync);
return dbActions;
} }
void _handleDeviceOneTimeKeysCount( void _handleDeviceOneTimeKeysCount(
@ -1071,15 +1070,13 @@ class Client {
} }
} }
void _handleDeviceListsEvents(Map<String, dynamic> deviceLists, Future<void> _handleDeviceListsEvents(Map<String, dynamic> deviceLists) async {
List<Future<dynamic> Function()> dbActions) {
if (deviceLists['changed'] is List) { if (deviceLists['changed'] is List) {
for (final userId in deviceLists['changed']) { for (final userId in deviceLists['changed']) {
if (_userDeviceKeys.containsKey(userId)) { if (_userDeviceKeys.containsKey(userId)) {
_userDeviceKeys[userId].outdated = true; _userDeviceKeys[userId].outdated = true;
if (database != null) { if (database != null) {
dbActions await database.storeUserDeviceKeysInfo(id, userId, true);
.add(() => database.storeUserDeviceKeysInfo(id, userId, true));
} }
} }
} }
@ -1175,9 +1172,10 @@ class Client {
} }
} }
void _handleRooms(Map<String, dynamic> rooms, Membership membership, Future<void> _handleRooms(Map<String, dynamic> rooms, Membership membership) async {
List<Future<dynamic> Function()> dbActions) { for (final entry in rooms.entries) {
rooms.forEach((String id, dynamic room) { final id = entry.key;
final room = entry.value;
// calculate the notification counts, the limitedTimeline and prevbatch // calculate the notification counts, the limitedTimeline and prevbatch
num highlight_count = 0; num highlight_count = 0;
num notification_count = 0; num notification_count = 0;
@ -1224,8 +1222,7 @@ class Client {
roomObj.resetSortOrder(); roomObj.resetSortOrder();
} }
if (database != null) { if (database != null) {
dbActions.add( await database.storeRoomUpdate(this.id, update, getRoomById(id));
() => database.storeRoomUpdate(this.id, update, getRoomById(id)));
} }
onRoomUpdate.add(update); onRoomUpdate.add(update);
@ -1235,45 +1232,44 @@ class Client {
if (room['state'] is Map<String, dynamic> && if (room['state'] is Map<String, dynamic> &&
room['state']['events'] is List<dynamic> && room['state']['events'] is List<dynamic> &&
room['state']['events'].isNotEmpty) { room['state']['events'].isNotEmpty) {
_handleRoomEvents(id, room['state']['events'], 'state', dbActions); await _handleRoomEvents(id, room['state']['events'], 'state');
handledEvents = true; handledEvents = true;
} }
if (room['invite_state'] is Map<String, dynamic> && if (room['invite_state'] is Map<String, dynamic> &&
room['invite_state']['events'] is List<dynamic>) { room['invite_state']['events'] is List<dynamic>) {
_handleRoomEvents( await _handleRoomEvents(
id, room['invite_state']['events'], 'invite_state', dbActions); id, room['invite_state']['events'], 'invite_state');
} }
if (room['timeline'] is Map<String, dynamic> && if (room['timeline'] is Map<String, dynamic> &&
room['timeline']['events'] is List<dynamic> && room['timeline']['events'] is List<dynamic> &&
room['timeline']['events'].isNotEmpty) { room['timeline']['events'].isNotEmpty) {
_handleRoomEvents( await _handleRoomEvents(
id, room['timeline']['events'], 'timeline', dbActions); id, room['timeline']['events'], 'timeline');
handledEvents = true; handledEvents = true;
} }
if (room['ephemeral'] is Map<String, dynamic> && if (room['ephemeral'] is Map<String, dynamic> &&
room['ephemeral']['events'] is List<dynamic>) { room['ephemeral']['events'] is List<dynamic>) {
_handleEphemerals(id, room['ephemeral']['events'], dbActions); await _handleEphemerals(id, room['ephemeral']['events']);
} }
if (room['account_data'] is Map<String, dynamic> && if (room['account_data'] is Map<String, dynamic> &&
room['account_data']['events'] is List<dynamic>) { room['account_data']['events'] is List<dynamic>) {
_handleRoomEvents( await _handleRoomEvents(
id, room['account_data']['events'], 'account_data', dbActions); id, room['account_data']['events'], 'account_data');
} }
if (handledEvents && database != null && roomObj != null) { if (handledEvents && database != null && roomObj != null) {
dbActions.add(() => roomObj.updateSortOrder()); await roomObj.updateSortOrder();
}
} }
});
} }
void _handleEphemerals(String id, List<dynamic> events, Future<void> _handleEphemerals(String id, List<dynamic> events) async {
List<Future<dynamic> Function()> dbActions) {
for (num i = 0; i < events.length; i++) { for (num i = 0; i < events.length; i++) {
_handleEvent(events[i], id, 'ephemeral', dbActions); await _handleEvent(events[i], id, 'ephemeral');
// Receipt events are deltas between two states. We will create a // Receipt events are deltas between two states. We will create a
// fake room account data event for this and store the difference // fake room account data event for this and store the difference
@ -1310,20 +1306,18 @@ class Client {
} }
} }
events[i]['content'] = receiptStateContent; events[i]['content'] = receiptStateContent;
_handleEvent(events[i], id, 'account_data', dbActions); await _handleEvent(events[i], id, 'account_data');
} }
} }
} }
void _handleRoomEvents(String chat_id, List<dynamic> events, String type, Future<void> _handleRoomEvents(String chat_id, List<dynamic> events, String type) async {
List<Future<dynamic> Function()> dbActions) {
for (num i = 0; i < events.length; i++) { for (num i = 0; i < events.length; i++) {
_handleEvent(events[i], chat_id, type, dbActions); await _handleEvent(events[i], chat_id, type);
} }
} }
void _handleGlobalEvents(List<dynamic> events, String type, Future<void> _handleGlobalEvents(List<dynamic> events, String type) async {
List<Future<dynamic> Function()> dbActions) {
for (var i = 0; i < events.length; i++) { for (var i = 0; i < events.length; i++) {
if (events[i]['type'] is String && if (events[i]['type'] is String &&
events[i]['content'] is Map<String, dynamic>) { events[i]['content'] is Map<String, dynamic>) {
@ -1333,15 +1327,14 @@ class Client {
content: events[i], content: events[i],
); );
if (database != null) { if (database != null) {
dbActions.add(() => database.storeUserEventUpdate(id, update)); await database.storeUserEventUpdate(id, update);
} }
onUserEvent.add(update); onUserEvent.add(update);
} }
} }
} }
void _handleEvent(Map<String, dynamic> event, String roomID, String type, Future<void> _handleEvent(Map<String, dynamic> event, String roomID, String type) async {
List<Future<dynamic> Function()> dbActions) {
if (event['type'] is String && event['content'] is Map<String, dynamic>) { if (event['type'] is String && event['content'] is Map<String, dynamic>) {
// The client must ignore any new m.room.encryption event to prevent // The client must ignore any new m.room.encryption event to prevent
// man-in-the-middle attacks! // man-in-the-middle attacks!
@ -1365,7 +1358,7 @@ class Client {
update = update.decrypt(room); update = update.decrypt(room);
} }
if (type != 'ephemeral' && database != null) { if (type != 'ephemeral' && database != null) {
dbActions.add(() => database.storeEventUpdate(id, update)); await database.storeEventUpdate(id, update);
} }
_updateRoomsByEventUpdate(update); _updateRoomsByEventUpdate(update);
onEvent.add(update); onEvent.add(update);

View file

@ -116,19 +116,6 @@ class Timeline {
try { try {
if (eventUpdate.roomID != room.id) return; if (eventUpdate.roomID != room.id) return;
// try to decrypt the event first, if needed
if (eventUpdate.eventType == 'm.room.encrypted' && room.client.database != null) {
try {
await room.loadInboundGroupSessionKey(eventUpdate.content['content']['session_id']);
eventUpdate = eventUpdate.decrypt(room);
if (eventUpdate.eventType != 'm.room.encrypted') {
await room.client.database.storeEventUpdate(room.client.id, eventUpdate);
}
} catch (err) {
print('[WARNING] (_handleEventUpdate) Failed to decrypt event: ${err.toString()}');
}
}
if (eventUpdate.type == 'timeline' || eventUpdate.type == 'history') { if (eventUpdate.type == 'timeline' || eventUpdate.type == 'history') {
// Redaction events are handled as modification for existing events. // Redaction events are handled as modification for existing events.
if (eventUpdate.eventType == 'm.room.redaction') { if (eventUpdate.eventType == 'm.room.redaction') {

View file

@ -207,7 +207,7 @@ void main() {
.verified, .verified,
false); false);
matrix.handleSync({ await matrix.handleSync({
'device_lists': { 'device_lists': {
'changed': [ 'changed': [
'@alice:example.com', '@alice:example.com',
@ -221,7 +221,7 @@ void main() {
expect(matrix.userDeviceKeys.length, 2); expect(matrix.userDeviceKeys.length, 2);
expect(matrix.userDeviceKeys['@alice:example.com'].outdated, true); expect(matrix.userDeviceKeys['@alice:example.com'].outdated, true);
matrix.handleSync({ await matrix.handleSync({
'rooms': { 'rooms': {
'join': { 'join': {
'!726s6s6q:example.com': { '!726s6s6q:example.com': {
@ -527,7 +527,7 @@ void main() {
test('Track oneTimeKeys', () async { test('Track oneTimeKeys', () async {
if (matrix.encryptionEnabled) { if (matrix.encryptionEnabled) {
var last = matrix.lastTimeKeysUploaded ?? DateTime.now(); var last = matrix.lastTimeKeysUploaded ?? DateTime.now();
matrix.handleSync({ await matrix.handleSync({
'device_one_time_keys_count': {'signed_curve25519': 49} 'device_one_time_keys_count': {'signed_curve25519': 49}
}); });
await Future.delayed(Duration(milliseconds: 50)); await Future.delayed(Duration(milliseconds: 50));
@ -542,7 +542,7 @@ void main() {
expect(matrix.rooms[1].outboundGroupSession == null, true); expect(matrix.rooms[1].outboundGroupSession == null, true);
await matrix.rooms[1].createOutboundGroupSession(); await matrix.rooms[1].createOutboundGroupSession();
expect(matrix.rooms[1].outboundGroupSession != null, true); expect(matrix.rooms[1].outboundGroupSession != null, true);
matrix.handleSync({ await matrix.handleSync({
'device_lists': { 'device_lists': {
'changed': [ 'changed': [
'@alice:example.com', '@alice:example.com',
@ -562,7 +562,7 @@ void main() {
expect(matrix.rooms[1].outboundGroupSession == null, true); expect(matrix.rooms[1].outboundGroupSession == null, true);
await matrix.rooms[1].createOutboundGroupSession(); await matrix.rooms[1].createOutboundGroupSession();
expect(matrix.rooms[1].outboundGroupSession != null, true); expect(matrix.rooms[1].outboundGroupSession != null, true);
matrix.handleSync({ await matrix.handleSync({
'rooms': { 'rooms': {
'join': { 'join': {
'!726s6s6q:example.com': { '!726s6s6q:example.com': {