Compare commits

...

3 Commits

3 changed files with 138 additions and 5 deletions

View File

@ -12,6 +12,14 @@ class RatchetModifiedEvent extends OmemoEvent {
final OmemoDoubleRatchet ratchet; final OmemoDoubleRatchet ratchet;
} }
/// Triggered when a ratchet has been removed and should be removed from storage.
class RatchetRemovedEvent extends OmemoEvent {
RatchetRemovedEvent(this.jid, this.deviceId);
final String jid;
final int deviceId;
}
/// Triggered when the device map has been modified /// Triggered when the device map has been modified
class DeviceMapModifiedEvent extends OmemoEvent { class DeviceMapModifiedEvent extends OmemoEvent {

View File

@ -50,6 +50,17 @@ class OmemoSessionManager {
); );
} }
/// Deserialise the OmemoSessionManager from JSON data [data] that does not contain
/// the ratchet sessions.
factory OmemoSessionManager.fromJsonWithoutSessions(Map<String, dynamic> data, Map<RatchetMapKey, OmemoDoubleRatchet> ratchetMap, TrustManager trustManager) {
return OmemoSessionManager(
Device.fromJson(data['device']! as Map<String, dynamic>),
data['devices']! as Map<String, List<int>>,
ratchetMap,
trustManager,
);
}
/// Generate a new cryptographic identity. /// Generate a new cryptographic identity.
static Future<OmemoSessionManager> generateNewIdentity(String jid, TrustManager trustManager, { int opkAmount = 100 }) async { static Future<OmemoSessionManager> generateNewIdentity(String jid, TrustManager trustManager, { int opkAmount = 100 }) async {
assert(opkAmount > 0, 'opkAmount must be bigger than 0.'); assert(opkAmount > 0, 'opkAmount must be bigger than 0.');
@ -83,6 +94,7 @@ class OmemoSessionManager {
/// A stream that receives events regarding the session /// A stream that receives events regarding the session
Stream<OmemoEvent> get eventStream => _eventStreamController.stream; Stream<OmemoEvent> get eventStream => _eventStreamController.stream;
/// Returns our own device.
Future<Device> getDevice() async { Future<Device> getDevice() async {
Device? dev; Device? dev;
await _deviceLock.synchronized(() async { await _deviceLock.synchronized(() async {
@ -393,12 +405,32 @@ class OmemoSessionManager {
Map<String, List<int>>? map; Map<String, List<int>>? map;
await _lock.synchronized(() async { await _lock.synchronized(() async {
map = _deviceMap; map = _deviceMap;
}); });
return map!; return map!;
} }
/// Removes the ratchet identified by [jid] and [deviceId] from the session manager.
/// Also triggers events for commiting the new device map to storage and removing
/// the old ratchet.
Future<void> removeRatchet(String jid, int deviceId) async {
await _lock.synchronized(() async {
// Remove the ratchet
_ratchetMap.remove(RatchetMapKey(jid, deviceId));
// Commit it
_eventStreamController.add(RatchetRemovedEvent(jid, deviceId));
// Remove the device from jid
_deviceMap[jid]!.remove(deviceId);
if (_deviceMap[jid]!.isEmpty) {
_deviceMap.remove(jid);
}
// Commit it
_eventStreamController.add(DeviceMapModifiedEvent(_deviceMap));
});
}
@visibleForTesting @visibleForTesting
OmemoDoubleRatchet getRatchet(String jid, int deviceId) => _ratchetMap[RatchetMapKey(jid, deviceId)]!; OmemoDoubleRatchet getRatchet(String jid, int deviceId) => _ratchetMap[RatchetMapKey(jid, deviceId)]!;
@ -423,7 +455,6 @@ class OmemoSessionManager {
}, },
... ...
], ],
'trust': { ... }
} }
*/ */
@ -439,8 +470,25 @@ class OmemoSessionManager {
'devices': _deviceMap, 'devices': _deviceMap,
'device': await (await getDevice()).toJson(), 'device': await (await getDevice()).toJson(),
'sessions': sessions, 'sessions': sessions,
// TODO(PapaTutuWawa): Implement };
'trust': <String, dynamic>{}, }
/// Serialise the entire session manager into a JSON object.
Future<Map<String, dynamic>> toJsonWithoutSessions() async {
/*
{
'devices': {
'alice@...': [1, 2, ...],
'bob@...': [1],
...
},
'device': { ... },
}
*/
return {
'devices': _deviceMap,
'device': await (await getDevice()).toJson(),
}; };
} }
} }

View File

@ -422,4 +422,81 @@ void main() {
expect(messageText, aliceReceivedMessage); expect(messageText, aliceReceivedMessage);
} }
}); });
group('Test removing a ratchet', () {
test('Test removing a ratchet when the user has multiple', () async {
const aliceJid = 'alice@server.local';
const bobJid = 'bob@some.server.local';
final aliceSession = await OmemoSessionManager.generateNewIdentity(
aliceJid,
AlwaysTrustingTrustManager(),
opkAmount: 1,
);
final bobSession1 = await OmemoSessionManager.generateNewIdentity(
bobJid,
AlwaysTrustingTrustManager(),
opkAmount: 1,
);
final bobSession2 = await OmemoSessionManager.generateNewIdentity(
bobJid,
AlwaysTrustingTrustManager(),
opkAmount: 1,
);
// Alice sends a message to those two Bobs
final aliceMessage = await aliceSession.encryptToJid(
bobJid,
'Hallo Welt',
newSessions: [
await (await bobSession1.getDevice()).toBundle(),
await (await bobSession2.getDevice()).toBundle(),
],
);
// One of those two sessions is broken, so Alice removes the session2 ratchet
final id1 = (await bobSession1.getDevice()).id;
final id2 = (await bobSession2.getDevice()).id;
await aliceSession.removeRatchet(bobJid, id1);
final map = await aliceSession.getRatchetMap();
expect(map.containsKey(RatchetMapKey(bobJid, id1)), false);
expect(map.containsKey(RatchetMapKey(bobJid, id2)), true);
final deviceMap = await aliceSession.getDeviceMap();
expect(deviceMap.containsKey(bobJid), true);
expect(deviceMap[bobJid], [id2]);
});
test('Test removing a ratchet when the user has only one', () async {
const aliceJid = 'alice@server.local';
const bobJid = 'bob@some.server.local';
final aliceSession = await OmemoSessionManager.generateNewIdentity(
aliceJid,
AlwaysTrustingTrustManager(),
opkAmount: 1,
);
final bobSession = await OmemoSessionManager.generateNewIdentity(
bobJid,
AlwaysTrustingTrustManager(),
opkAmount: 1,
);
// Alice sends a message to those two Bobs
final aliceMessage = await aliceSession.encryptToJid(
bobJid,
'Hallo Welt',
newSessions: [
await (await bobSession.getDevice()).toBundle(),
],
);
// One of those two sessions is broken, so Alice removes the session2 ratchet
final id = (await bobSession.getDevice()).id;
await aliceSession.removeRatchet(bobJid, id);
final map = await aliceSession.getRatchetMap();
expect(map.containsKey(RatchetMapKey(bobJid, id)), false);
final deviceMap = await aliceSession.getDeviceMap();
expect(deviceMap.containsKey(bobJid), false);
});
});
} }