@@ -292,6 +292,43 @@ class KeyManager {
292292 return roomInboundGroupSessions[sessionId] = dbSess;
293293 }
294294
295+ void _sendEncryptionInfoEvent ({
296+ required String roomId,
297+ required List <String > userIds,
298+ List <String >? deviceIds,
299+ }) async {
300+ await client.database? .transaction (() async {
301+ await client.handleSync (
302+ SyncUpdate (
303+ nextBatch: '' ,
304+ rooms: RoomsUpdate (
305+ join: {
306+ roomId: JoinedRoomUpdate (
307+ timeline: TimelineUpdate (
308+ events: [
309+ MatrixEvent (
310+ eventId:
311+ 'fake_event_${client .generateUniqueTransactionId ()}' ,
312+ content: {
313+ 'body' :
314+ '${userIds .join (', ' )} can now read along${deviceIds != null ? ' on ${deviceIds .length } new device(s)' : '' }' ,
315+ if (deviceIds != null ) 'devices' : deviceIds,
316+ 'users' : userIds,
317+ },
318+ type: EventTypes .encryptionInfo,
319+ senderId: client.userID! ,
320+ originServerTs: DateTime .now (),
321+ ),
322+ ],
323+ ),
324+ ),
325+ },
326+ ),
327+ ),
328+ );
329+ });
330+ }
331+
295332 Map <String , Map <String , bool >> _getDeviceKeyIdMap (
296333 List <DeviceKeys > deviceKeys,
297334 ) {
@@ -327,21 +364,6 @@ class KeyManager {
327364 return true ;
328365 }
329366
330- if (! wipe) {
331- // first check if it needs to be rotated
332- final encryptionContent =
333- room.getState (EventTypes .Encryption )? .parsedRoomEncryptionContent;
334- final maxMessages = encryptionContent? .rotationPeriodMsgs ?? 100 ;
335- final maxAge = encryptionContent? .rotationPeriodMs ??
336- 604800000 ; // default of one week
337- if ((sess.sentMessages ?? maxMessages) >= maxMessages ||
338- sess.creationTime
339- .add (Duration (milliseconds: maxAge))
340- .isBefore (DateTime .now ())) {
341- wipe = true ;
342- }
343- }
344-
345367 final inboundSess = await loadInboundGroupSession (
346368 room.id,
347369 sess.outboundGroupSession! .session_id (),
@@ -352,81 +374,100 @@ class KeyManager {
352374 wipe = true ;
353375 }
354376
355- if (! wipe) {
356- // next check if the devices in the room changed
357- final devicesToReceive = < DeviceKeys > [];
358- final newDeviceKeys = await room.getUserDeviceKeys ();
359- final newDeviceKeyIds = _getDeviceKeyIdMap (newDeviceKeys);
360- // first check for user differences
361- final oldUserIds = sess.devices.keys.toSet ();
362- final newUserIds = newDeviceKeyIds.keys.toSet ();
363- if (oldUserIds.difference (newUserIds).isNotEmpty) {
364- // a user left the room, we must wipe the session
365- wipe = true ;
366- } else {
367- final newUsers = newUserIds.difference (oldUserIds);
368- if (newUsers.isNotEmpty) {
369- // new user! Gotta send the megolm session to them
370- devicesToReceive
371- .addAll (newDeviceKeys.where ((d) => newUsers.contains (d.userId)));
377+ // next check if the devices in the room changed
378+ final devicesToReceive = < DeviceKeys > [];
379+ final newDeviceKeys = await room.getUserDeviceKeys ();
380+ final newDeviceKeyIds = _getDeviceKeyIdMap (newDeviceKeys);
381+ // first check for user differences
382+ final oldUserIds = sess.devices.keys.toSet ();
383+ final newUserIds = newDeviceKeyIds.keys.toSet ();
384+ if (oldUserIds.difference (newUserIds).isNotEmpty) {
385+ // a user left the room, we must wipe the session
386+ wipe = true ;
387+ } else {
388+ final newUsers = newUserIds.difference (oldUserIds);
389+ if (newUsers.isNotEmpty) {
390+ // new user! Gotta send the megolm session to them
391+ devicesToReceive
392+ .addAll (newDeviceKeys.where ((d) => newUsers.contains (d.userId)));
393+ _sendEncryptionInfoEvent (roomId: roomId, userIds: newUsers.toList ());
394+ }
395+ // okay, now we must test all the individual user devices, if anything new got blocked
396+ // or if we need to send to any new devices.
397+ // for this it is enough if we iterate over the old user Ids, as the new ones already have the needed keys in the list.
398+ // we also know that all the old user IDs appear in the old one, else we have already wiped the session
399+ for (final userId in oldUserIds) {
400+ final oldBlockedDevices = sess.devices.containsKey (userId)
401+ ? sess.devices[userId]! .entries
402+ .where ((e) => e.value)
403+ .map ((e) => e.key)
404+ .toSet ()
405+ : < String > {};
406+ final newBlockedDevices = newDeviceKeyIds.containsKey (userId)
407+ ? newDeviceKeyIds[userId]!
408+ .entries
409+ .where ((e) => e.value)
410+ .map ((e) => e.key)
411+ .toSet ()
412+ : < String > {};
413+ // we don't really care about old devices that got dropped (deleted), we only care if new ones got added and if new ones got blocked
414+ // check if new devices got blocked
415+ if (newBlockedDevices.difference (oldBlockedDevices).isNotEmpty) {
416+ wipe = true ;
417+ }
418+ // and now add all the new devices!
419+ final oldDeviceIds = sess.devices.containsKey (userId)
420+ ? sess.devices[userId]! .entries
421+ .where ((e) => ! e.value)
422+ .map ((e) => e.key)
423+ .toSet ()
424+ : < String > {};
425+ final newDeviceIds = newDeviceKeyIds.containsKey (userId)
426+ ? newDeviceKeyIds[userId]!
427+ .entries
428+ .where ((e) => ! e.value)
429+ .map ((e) => e.key)
430+ .toSet ()
431+ : < String > {};
432+
433+ // check if a device got removed
434+ if (oldDeviceIds.difference (newDeviceIds).isNotEmpty) {
435+ wipe = true ;
372436 }
373- // okay, now we must test all the individual user devices, if anything new got blocked
374- // or if we need to send to any new devices.
375- // for this it is enough if we iterate over the old user Ids, as the new ones already have the needed keys in the list.
376- // we also know that all the old user IDs appear in the old one, else we have already wiped the session
377- for (final userId in oldUserIds) {
378- final oldBlockedDevices = sess.devices.containsKey (userId)
379- ? sess.devices[userId]! .entries
380- .where ((e) => e.value)
381- .map ((e) => e.key)
382- .toSet ()
383- : < String > {};
384- final newBlockedDevices = newDeviceKeyIds.containsKey (userId)
385- ? newDeviceKeyIds[userId]!
386- .entries
387- .where ((e) => e.value)
388- .map ((e) => e.key)
389- .toSet ()
390- : < String > {};
391- // we don't really care about old devices that got dropped (deleted), we only care if new ones got added and if new ones got blocked
392- // check if new devices got blocked
393- if (newBlockedDevices.difference (oldBlockedDevices).isNotEmpty) {
394- wipe = true ;
395- break ;
396- }
397- // and now add all the new devices!
398- final oldDeviceIds = sess.devices.containsKey (userId)
399- ? sess.devices[userId]! .entries
400- .where ((e) => ! e.value)
401- .map ((e) => e.key)
402- .toSet ()
403- : < String > {};
404- final newDeviceIds = newDeviceKeyIds.containsKey (userId)
405- ? newDeviceKeyIds[userId]!
406- .entries
407- .where ((e) => ! e.value)
408- .map ((e) => e.key)
409- .toSet ()
410- : < String > {};
411-
412- // check if a device got removed
413- if (oldDeviceIds.difference (newDeviceIds).isNotEmpty) {
414- wipe = true ;
415- break ;
416- }
417437
418- // check if any new devices need keys
419- final newDevices = newDeviceIds.difference (oldDeviceIds);
420- if (newDeviceIds.isNotEmpty) {
421- devicesToReceive.addAll (
422- newDeviceKeys.where (
423- (d) => d.userId == userId && newDevices.contains (d.deviceId),
424- ),
438+ // check if any new devices need keys
439+ final newDevices = newDeviceIds.difference (oldDeviceIds);
440+ if (newDeviceIds.isNotEmpty) {
441+ devicesToReceive.addAll (
442+ newDeviceKeys.where (
443+ (d) => d.userId == userId && newDevices.contains (d.deviceId),
444+ ),
445+ );
446+ if (userId != client.userID && newDevices.isNotEmpty) {
447+ _sendEncryptionInfoEvent (
448+ roomId: roomId,
449+ userIds: [userId],
450+ deviceIds: newDevices.toList (),
425451 );
426452 }
427453 }
428454 }
429455
456+ if (! wipe) {
457+ // first check if it needs to be rotated
458+ final encryptionContent =
459+ room.getState (EventTypes .Encryption )? .parsedRoomEncryptionContent;
460+ final maxMessages = encryptionContent? .rotationPeriodMsgs ?? 100 ;
461+ final maxAge = encryptionContent? .rotationPeriodMs ??
462+ 604800000 ; // default of one week
463+ if ((sess.sentMessages ?? maxMessages) >= maxMessages ||
464+ sess.creationTime
465+ .add (Duration (milliseconds: maxAge))
466+ .isBefore (DateTime .now ())) {
467+ wipe = true ;
468+ }
469+ }
470+
430471 if (! wipe) {
431472 if (! use) {
432473 return false ;
0 commit comments