From fb1e286cf994a4cf29e8985e551eecb28f8e0605 Mon Sep 17 00:00:00 2001 From: otsmr Date: Sun, 1 Mar 2026 23:34:07 +0100 Subject: [PATCH] fix signal out of sync issue --- CHANGELOG.md | 3 - .../client/generated/messages.pbenum.dart | 10 +- .../client/generated/messages.pbjson.dart | 126 +++++++++--------- lib/src/model/protobuf/client/messages.proto | 1 + .../api/client2client/errors.c2c.dart | 8 +- lib/src/services/api/server_messages.dart | 49 +++---- lib/src/services/api/utils.dart | 2 +- lib/src/services/group.services.dart | 2 +- .../services/signal/encryption.signal.dart | 33 ++++- lib/src/services/signal/session.signal.dart | 41 ++---- lib/src/services/signal/utils.signal.dart | 5 + .../developer/automated_testing.view.dart | 106 +++++++++++---- pubspec.yaml | 2 +- 13 files changed, 232 insertions(+), 156 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 078742f..d21b202 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,6 @@ Feature: Show link in chat if the saved media file contains one Improve: Verification badge for groups Improve: Huge reduction in app size Fix: Crash on older devices when compressing a video - -## 0.0.94 - Fix: Problem with decrypting messages fixed ## 0.0.93 diff --git a/lib/src/model/protobuf/client/generated/messages.pbenum.dart b/lib/src/model/protobuf/client/generated/messages.pbenum.dart index 8277780..a8d9e86 100644 --- a/lib/src/model/protobuf/client/generated/messages.pbenum.dart +++ b/lib/src/model/protobuf/client/generated/messages.pbenum.dart @@ -76,17 +76,21 @@ class EncryptedContent_ErrorMessages_Type extends $pb.ProtobufEnum { static const EncryptedContent_ErrorMessages_Type UNKNOWN_MESSAGE_TYPE = EncryptedContent_ErrorMessages_Type._( 2, _omitEnumNames ? '' : 'UNKNOWN_MESSAGE_TYPE'); + static const EncryptedContent_ErrorMessages_Type SESSION_OUT_OF_SYNC = + EncryptedContent_ErrorMessages_Type._( + 3, _omitEnumNames ? '' : 'SESSION_OUT_OF_SYNC'); static const $core.List values = [ ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD, UNKNOWN_MESSAGE_TYPE, + SESSION_OUT_OF_SYNC, ]; - static final $core.Map<$core.int, EncryptedContent_ErrorMessages_Type> - _byValue = $pb.ProtobufEnum.initByValue(values); + static final $core.List _byValue = + $pb.ProtobufEnum.$_initByValueList(values, 3); static EncryptedContent_ErrorMessages_Type? valueOf($core.int value) => - _byValue[value]; + value < 0 || value >= _byValue.length ? null : _byValue[value]; const EncryptedContent_ErrorMessages_Type._(super.value, super.name); } diff --git a/lib/src/model/protobuf/client/generated/messages.pbjson.dart b/lib/src/model/protobuf/client/generated/messages.pbjson.dart index ac60f8f..a8a1fa3 100644 --- a/lib/src/model/protobuf/client/generated/messages.pbjson.dart +++ b/lib/src/model/protobuf/client/generated/messages.pbjson.dart @@ -395,6 +395,7 @@ const EncryptedContent_ErrorMessages_Type$json = { '2': [ {'1': 'ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD', '2': 0}, {'1': 'UNKNOWN_MESSAGE_TYPE', '2': 2}, + {'1': 'SESSION_OUT_OF_SYNC', '2': 3}, ], }; @@ -863,67 +864,68 @@ final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode( 'gBARJLCg5lcnJvcl9tZXNzYWdlcxgSIAEoCzIfLkVuY3J5cHRlZENvbnRlbnQuRXJyb3JNZXNz' 'YWdlc0gQUg1lcnJvck1lc3NhZ2VziAEBEmQKF2FkZGl0aW9uYWxfZGF0YV9tZXNzYWdlGBMgAS' 'gLMicuRW5jcnlwdGVkQ29udGVudC5BZGRpdGlvbmFsRGF0YU1lc3NhZ2VIEVIVYWRkaXRpb25h' - 'bERhdGFNZXNzYWdliAEBGtcBCg1FcnJvck1lc3NhZ2VzEjgKBHR5cGUYASABKA4yJC5FbmNyeX' + 'bERhdGFNZXNzYWdliAEBGvABCg1FcnJvck1lc3NhZ2VzEjgKBHR5cGUYASABKA4yJC5FbmNyeX' 'B0ZWRDb250ZW50LkVycm9yTWVzc2FnZXMuVHlwZVIEdHlwZRIsChJyZWxhdGVkX3JlY2VpcHRf' - 'aWQYAiABKAlSEHJlbGF0ZWRSZWNlaXB0SWQiXgoEVHlwZRI8CjhFUlJPUl9QUk9DRVNTSU5HX0' + 'aWQYAiABKAlSEHJlbGF0ZWRSZWNlaXB0SWQidwoEVHlwZRI8CjhFUlJPUl9QUk9DRVNTSU5HX0' '1FU1NBR0VfQ1JFQVRFRF9BQ0NPVU5UX1JFUVVFU1RfSU5TVEVBRBAAEhgKFFVOS05PV05fTUVT' - 'U0FHRV9UWVBFEAIaUQoLR3JvdXBDcmVhdGUSGgoIc3RhdGVLZXkYAyABKAxSCHN0YXRlS2V5Ei' - 'YKDmdyb3VwUHVibGljS2V5GAQgASgMUg5ncm91cFB1YmxpY0tleRozCglHcm91cEpvaW4SJgoO' - 'Z3JvdXBQdWJsaWNLZXkYASABKAxSDmdyb3VwUHVibGljS2V5GhYKFFJlc2VuZEdyb3VwUHVibG' - 'ljS2V5GrYCCgtHcm91cFVwZGF0ZRIoCg9ncm91cEFjdGlvblR5cGUYASABKAlSD2dyb3VwQWN0' - 'aW9uVHlwZRIxChFhZmZlY3RlZENvbnRhY3RJZBgCIAEoA0gAUhFhZmZlY3RlZENvbnRhY3RJZI' - 'gBARInCgxuZXdHcm91cE5hbWUYAyABKAlIAVIMbmV3R3JvdXBOYW1liAEBElMKIm5ld0RlbGV0' - 'ZU1lc3NhZ2VzQWZ0ZXJNaWxsaXNlY29uZHMYBCABKANIAlIibmV3RGVsZXRlTWVzc2FnZXNBZn' - 'Rlck1pbGxpc2Vjb25kc4gBAUIUChJfYWZmZWN0ZWRDb250YWN0SWRCDwoNX25ld0dyb3VwTmFt' - 'ZUIlCiNfbmV3RGVsZXRlTWVzc2FnZXNBZnRlck1pbGxpc2Vjb25kcxqpAQoLVGV4dE1lc3NhZ2' - 'USKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW5kZXJNZXNzYWdlSWQSEgoEdGV4dBgCIAEo' - 'CVIEdGV4dBIcCgl0aW1lc3RhbXAYAyABKANSCXRpbWVzdGFtcBIrCg5xdW90ZU1lc3NhZ2VJZB' - 'gEIAEoCUgAUg5xdW90ZU1lc3NhZ2VJZIgBAUIRCg9fcXVvdGVNZXNzYWdlSWQazgEKFUFkZGl0' - 'aW9uYWxEYXRhTWVzc2FnZRIqChFzZW5kZXJfbWVzc2FnZV9pZBgBIAEoCVIPc2VuZGVyTWVzc2' - 'FnZUlkEhwKCXRpbWVzdGFtcBgCIAEoA1IJdGltZXN0YW1wEhIKBHR5cGUYAyABKAlSBHR5cGUS' - 'OwoXYWRkaXRpb25hbF9tZXNzYWdlX2RhdGEYBCABKAxIAFIVYWRkaXRpb25hbE1lc3NhZ2VEYX' - 'RhiAEBQhoKGF9hZGRpdGlvbmFsX21lc3NhZ2VfZGF0YRpiCghSZWFjdGlvbhIoCg90YXJnZXRN' - 'ZXNzYWdlSWQYASABKAlSD3RhcmdldE1lc3NhZ2VJZBIUCgVlbW9qaRgCIAEoCVIFZW1vamkSFg' - 'oGcmVtb3ZlGAMgASgIUgZyZW1vdmUatwIKDU1lc3NhZ2VVcGRhdGUSOAoEdHlwZRgBIAEoDjIk' - 'LkVuY3J5cHRlZENvbnRlbnQuTWVzc2FnZVVwZGF0ZS5UeXBlUgR0eXBlEi0KD3NlbmRlck1lc3' - 'NhZ2VJZBgCIAEoCUgAUg9zZW5kZXJNZXNzYWdlSWSIAQESOgoYbXVsdGlwbGVUYXJnZXRNZXNz' - 'YWdlSWRzGAMgAygJUhhtdWx0aXBsZVRhcmdldE1lc3NhZ2VJZHMSFwoEdGV4dBgEIAEoCUgBUg' - 'R0ZXh0iAEBEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wIi0KBFR5cGUSCgoGREVMRVRF' - 'EAASDQoJRURJVF9URVhUEAESCgoGT1BFTkVEEAJCEgoQX3NlbmRlck1lc3NhZ2VJZEIHCgVfdG' - 'V4dBrwBQoFTWVkaWESKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW5kZXJNZXNzYWdlSWQS' - 'MAoEdHlwZRgCIAEoDjIcLkVuY3J5cHRlZENvbnRlbnQuTWVkaWEuVHlwZVIEdHlwZRJDChpkaX' - 'NwbGF5TGltaXRJbk1pbGxpc2Vjb25kcxgDIAEoA0gAUhpkaXNwbGF5TGltaXRJbk1pbGxpc2Vj' - 'b25kc4gBARI2ChZyZXF1aXJlc0F1dGhlbnRpY2F0aW9uGAQgASgIUhZyZXF1aXJlc0F1dGhlbn' - 'RpY2F0aW9uEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wEisKDnF1b3RlTWVzc2FnZUlk' - 'GAYgASgJSAFSDnF1b3RlTWVzc2FnZUlkiAEBEikKDWRvd25sb2FkVG9rZW4YByABKAxIAlINZG' - '93bmxvYWRUb2tlbogBARIpCg1lbmNyeXB0aW9uS2V5GAggASgMSANSDWVuY3J5cHRpb25LZXmI' - 'AQESKQoNZW5jcnlwdGlvbk1hYxgJIAEoDEgEUg1lbmNyeXB0aW9uTWFjiAEBEi0KD2VuY3J5cH' - 'Rpb25Ob25jZRgKIAEoDEgFUg9lbmNyeXB0aW9uTm9uY2WIAQESOwoXYWRkaXRpb25hbF9tZXNz' - 'YWdlX2RhdGEYCyABKAxIBlIVYWRkaXRpb25hbE1lc3NhZ2VEYXRhiAEBIj4KBFR5cGUSDAoIUk' - 'VVUExPQUQQABIJCgVJTUFHRRABEgkKBVZJREVPEAISBwoDR0lGEAMSCQoFQVVESU8QBEIdChtf' - 'ZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHNCEQoPX3F1b3RlTWVzc2FnZUlkQhAKDl9kb3dubG' - '9hZFRva2VuQhAKDl9lbmNyeXB0aW9uS2V5QhAKDl9lbmNyeXB0aW9uTWFjQhIKEF9lbmNyeXB0' - 'aW9uTm9uY2VCGgoYX2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGqcBCgtNZWRpYVVwZGF0ZRI2Cg' - 'R0eXBlGAEgASgOMiIuRW5jcnlwdGVkQ29udGVudC5NZWRpYVVwZGF0ZS5UeXBlUgR0eXBlEigK' - 'D3RhcmdldE1lc3NhZ2VJZBgCIAEoCVIPdGFyZ2V0TWVzc2FnZUlkIjYKBFR5cGUSDAoIUkVPUE' - 'VORUQQABIKCgZTVE9SRUQQARIUChBERUNSWVBUSU9OX0VSUk9SEAIaeAoOQ29udGFjdFJlcXVl' - 'c3QSOQoEdHlwZRgBIAEoDjIlLkVuY3J5cHRlZENvbnRlbnQuQ29udGFjdFJlcXVlc3QuVHlwZV' - 'IEdHlwZSIrCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZSRUpFQ1QQARIKCgZBQ0NFUFQQAhqeAgoN' - 'Q29udGFjdFVwZGF0ZRI4CgR0eXBlGAEgASgOMiQuRW5jcnlwdGVkQ29udGVudC5Db250YWN0VX' - 'BkYXRlLlR5cGVSBHR5cGUSNQoTYXZhdGFyU3ZnQ29tcHJlc3NlZBgCIAEoDEgAUhNhdmF0YXJT' - 'dmdDb21wcmVzc2VkiAEBEh8KCHVzZXJuYW1lGAMgASgJSAFSCHVzZXJuYW1liAEBEiUKC2Rpc3' - 'BsYXlOYW1lGAQgASgJSAJSC2Rpc3BsYXlOYW1liAEBIh8KBFR5cGUSCwoHUkVRVUVTVBAAEgoK' - 'BlVQREFURRABQhYKFF9hdmF0YXJTdmdDb21wcmVzc2VkQgsKCV91c2VybmFtZUIOCgxfZGlzcG' - 'xheU5hbWUa1QEKCFB1c2hLZXlzEjMKBHR5cGUYASABKA4yHy5FbmNyeXB0ZWRDb250ZW50LlB1' - 'c2hLZXlzLlR5cGVSBHR5cGUSGQoFa2V5SWQYAiABKANIAFIFa2V5SWSIAQESFQoDa2V5GAMgAS' - 'gMSAFSA2tleYgBARIhCgljcmVhdGVkQXQYBCABKANIAlIJY3JlYXRlZEF0iAEBIh8KBFR5cGUS' - 'CwoHUkVRVUVTVBAAEgoKBlVQREFURRABQggKBl9rZXlJZEIGCgRfa2V5QgwKCl9jcmVhdGVkQX' - 'QaqQEKCUZsYW1lU3luYxIiCgxmbGFtZUNvdW50ZXIYASABKANSDGZsYW1lQ291bnRlchI2ChZs' - 'YXN0RmxhbWVDb3VudGVyQ2hhbmdlGAIgASgDUhZsYXN0RmxhbWVDb3VudGVyQ2hhbmdlEh4KCm' - 'Jlc3RGcmllbmQYAyABKAhSCmJlc3RGcmllbmQSIAoLZm9yY2VVcGRhdGUYBCABKAhSC2ZvcmNl' - 'VXBkYXRlQgoKCF9ncm91cElkQg8KDV9pc0RpcmVjdENoYXRCFwoVX3NlbmRlclByb2ZpbGVDb3' - 'VudGVyQhAKDl9tZXNzYWdlVXBkYXRlQggKBl9tZWRpYUIOCgxfbWVkaWFVcGRhdGVCEAoOX2Nv' - 'bnRhY3RVcGRhdGVCEQoPX2NvbnRhY3RSZXF1ZXN0QgwKCl9mbGFtZVN5bmNCCwoJX3B1c2hLZX' - 'lzQgsKCV9yZWFjdGlvbkIOCgxfdGV4dE1lc3NhZ2VCDgoMX2dyb3VwQ3JlYXRlQgwKCl9ncm91' - 'cEpvaW5CDgoMX2dyb3VwVXBkYXRlQhcKFV9yZXNlbmRHcm91cFB1YmxpY0tleUIRCg9fZXJyb3' - 'JfbWVzc2FnZXNCGgoYX2FkZGl0aW9uYWxfZGF0YV9tZXNzYWdl'); + 'U0FHRV9UWVBFEAISFwoTU0VTU0lPTl9PVVRfT0ZfU1lOQxADGlEKC0dyb3VwQ3JlYXRlEhoKCH' + 'N0YXRlS2V5GAMgASgMUghzdGF0ZUtleRImCg5ncm91cFB1YmxpY0tleRgEIAEoDFIOZ3JvdXBQ' + 'dWJsaWNLZXkaMwoJR3JvdXBKb2luEiYKDmdyb3VwUHVibGljS2V5GAEgASgMUg5ncm91cFB1Ym' + 'xpY0tleRoWChRSZXNlbmRHcm91cFB1YmxpY0tleRq2AgoLR3JvdXBVcGRhdGUSKAoPZ3JvdXBB' + 'Y3Rpb25UeXBlGAEgASgJUg9ncm91cEFjdGlvblR5cGUSMQoRYWZmZWN0ZWRDb250YWN0SWQYAi' + 'ABKANIAFIRYWZmZWN0ZWRDb250YWN0SWSIAQESJwoMbmV3R3JvdXBOYW1lGAMgASgJSAFSDG5l' + 'd0dyb3VwTmFtZYgBARJTCiJuZXdEZWxldGVNZXNzYWdlc0FmdGVyTWlsbGlzZWNvbmRzGAQgAS' + 'gDSAJSIm5ld0RlbGV0ZU1lc3NhZ2VzQWZ0ZXJNaWxsaXNlY29uZHOIAQFCFAoSX2FmZmVjdGVk' + 'Q29udGFjdElkQg8KDV9uZXdHcm91cE5hbWVCJQojX25ld0RlbGV0ZU1lc3NhZ2VzQWZ0ZXJNaW' + 'xsaXNlY29uZHMaqQEKC1RleHRNZXNzYWdlEigKD3NlbmRlck1lc3NhZ2VJZBgBIAEoCVIPc2Vu' + 'ZGVyTWVzc2FnZUlkEhIKBHRleHQYAiABKAlSBHRleHQSHAoJdGltZXN0YW1wGAMgASgDUgl0aW' + '1lc3RhbXASKwoOcXVvdGVNZXNzYWdlSWQYBCABKAlIAFIOcXVvdGVNZXNzYWdlSWSIAQFCEQoP' + 'X3F1b3RlTWVzc2FnZUlkGs4BChVBZGRpdGlvbmFsRGF0YU1lc3NhZ2USKgoRc2VuZGVyX21lc3' + 'NhZ2VfaWQYASABKAlSD3NlbmRlck1lc3NhZ2VJZBIcCgl0aW1lc3RhbXAYAiABKANSCXRpbWVz' + 'dGFtcBISCgR0eXBlGAMgASgJUgR0eXBlEjsKF2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGAQgAS' + 'gMSABSFWFkZGl0aW9uYWxNZXNzYWdlRGF0YYgBAUIaChhfYWRkaXRpb25hbF9tZXNzYWdlX2Rh' + 'dGEaYgoIUmVhY3Rpb24SKAoPdGFyZ2V0TWVzc2FnZUlkGAEgASgJUg90YXJnZXRNZXNzYWdlSW' + 'QSFAoFZW1vamkYAiABKAlSBWVtb2ppEhYKBnJlbW92ZRgDIAEoCFIGcmVtb3ZlGrcCCg1NZXNz' + 'YWdlVXBkYXRlEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50Lk1lc3NhZ2VVcGRhdG' + 'UuVHlwZVIEdHlwZRItCg9zZW5kZXJNZXNzYWdlSWQYAiABKAlIAFIPc2VuZGVyTWVzc2FnZUlk' + 'iAEBEjoKGG11bHRpcGxlVGFyZ2V0TWVzc2FnZUlkcxgDIAMoCVIYbXVsdGlwbGVUYXJnZXRNZX' + 'NzYWdlSWRzEhcKBHRleHQYBCABKAlIAVIEdGV4dIgBARIcCgl0aW1lc3RhbXAYBSABKANSCXRp' + 'bWVzdGFtcCItCgRUeXBlEgoKBkRFTEVURRAAEg0KCUVESVRfVEVYVBABEgoKBk9QRU5FRBACQh' + 'IKEF9zZW5kZXJNZXNzYWdlSWRCBwoFX3RleHQa8AUKBU1lZGlhEigKD3NlbmRlck1lc3NhZ2VJ' + 'ZBgBIAEoCVIPc2VuZGVyTWVzc2FnZUlkEjAKBHR5cGUYAiABKA4yHC5FbmNyeXB0ZWRDb250ZW' + '50Lk1lZGlhLlR5cGVSBHR5cGUSQwoaZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHMYAyABKANI' + 'AFIaZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHOIAQESNgoWcmVxdWlyZXNBdXRoZW50aWNhdG' + 'lvbhgEIAEoCFIWcmVxdWlyZXNBdXRoZW50aWNhdGlvbhIcCgl0aW1lc3RhbXAYBSABKANSCXRp' + 'bWVzdGFtcBIrCg5xdW90ZU1lc3NhZ2VJZBgGIAEoCUgBUg5xdW90ZU1lc3NhZ2VJZIgBARIpCg' + '1kb3dubG9hZFRva2VuGAcgASgMSAJSDWRvd25sb2FkVG9rZW6IAQESKQoNZW5jcnlwdGlvbktl' + 'eRgIIAEoDEgDUg1lbmNyeXB0aW9uS2V5iAEBEikKDWVuY3J5cHRpb25NYWMYCSABKAxIBFINZW' + '5jcnlwdGlvbk1hY4gBARItCg9lbmNyeXB0aW9uTm9uY2UYCiABKAxIBVIPZW5jcnlwdGlvbk5v' + 'bmNliAEBEjsKF2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGAsgASgMSAZSFWFkZGl0aW9uYWxNZX' + 'NzYWdlRGF0YYgBASI+CgRUeXBlEgwKCFJFVVBMT0FEEAASCQoFSU1BR0UQARIJCgVWSURFTxAC' + 'EgcKA0dJRhADEgkKBUFVRElPEARCHQobX2Rpc3BsYXlMaW1pdEluTWlsbGlzZWNvbmRzQhEKD1' + '9xdW90ZU1lc3NhZ2VJZEIQCg5fZG93bmxvYWRUb2tlbkIQCg5fZW5jcnlwdGlvbktleUIQCg5f' + 'ZW5jcnlwdGlvbk1hY0ISChBfZW5jcnlwdGlvbk5vbmNlQhoKGF9hZGRpdGlvbmFsX21lc3NhZ2' + 'VfZGF0YRqnAQoLTWVkaWFVcGRhdGUSNgoEdHlwZRgBIAEoDjIiLkVuY3J5cHRlZENvbnRlbnQu' + 'TWVkaWFVcGRhdGUuVHlwZVIEdHlwZRIoCg90YXJnZXRNZXNzYWdlSWQYAiABKAlSD3RhcmdldE' + '1lc3NhZ2VJZCI2CgRUeXBlEgwKCFJFT1BFTkVEEAASCgoGU1RPUkVEEAESFAoQREVDUllQVElP' + 'Tl9FUlJPUhACGngKDkNvbnRhY3RSZXF1ZXN0EjkKBHR5cGUYASABKA4yJS5FbmNyeXB0ZWRDb2' + '50ZW50LkNvbnRhY3RSZXF1ZXN0LlR5cGVSBHR5cGUiKwoEVHlwZRILCgdSRVFVRVNUEAASCgoG' + 'UkVKRUNUEAESCgoGQUNDRVBUEAIangIKDUNvbnRhY3RVcGRhdGUSOAoEdHlwZRgBIAEoDjIkLk' + 'VuY3J5cHRlZENvbnRlbnQuQ29udGFjdFVwZGF0ZS5UeXBlUgR0eXBlEjUKE2F2YXRhclN2Z0Nv' + 'bXByZXNzZWQYAiABKAxIAFITYXZhdGFyU3ZnQ29tcHJlc3NlZIgBARIfCgh1c2VybmFtZRgDIA' + 'EoCUgBUgh1c2VybmFtZYgBARIlCgtkaXNwbGF5TmFtZRgEIAEoCUgCUgtkaXNwbGF5TmFtZYgB' + 'ASIfCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAUIWChRfYXZhdGFyU3ZnQ29tcHJlc3' + 'NlZEILCglfdXNlcm5hbWVCDgoMX2Rpc3BsYXlOYW1lGtUBCghQdXNoS2V5cxIzCgR0eXBlGAEg' + 'ASgOMh8uRW5jcnlwdGVkQ29udGVudC5QdXNoS2V5cy5UeXBlUgR0eXBlEhkKBWtleUlkGAIgAS' + 'gDSABSBWtleUlkiAEBEhUKA2tleRgDIAEoDEgBUgNrZXmIAQESIQoJY3JlYXRlZEF0GAQgASgD' + 'SAJSCWNyZWF0ZWRBdIgBASIfCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAUIICgZfa2' + 'V5SWRCBgoEX2tleUIMCgpfY3JlYXRlZEF0GqkBCglGbGFtZVN5bmMSIgoMZmxhbWVDb3VudGVy' + 'GAEgASgDUgxmbGFtZUNvdW50ZXISNgoWbGFzdEZsYW1lQ291bnRlckNoYW5nZRgCIAEoA1IWbG' + 'FzdEZsYW1lQ291bnRlckNoYW5nZRIeCgpiZXN0RnJpZW5kGAMgASgIUgpiZXN0RnJpZW5kEiAK' + 'C2ZvcmNlVXBkYXRlGAQgASgIUgtmb3JjZVVwZGF0ZUIKCghfZ3JvdXBJZEIPCg1faXNEaXJlY3' + 'RDaGF0QhcKFV9zZW5kZXJQcm9maWxlQ291bnRlckIQCg5fbWVzc2FnZVVwZGF0ZUIICgZfbWVk' + 'aWFCDgoMX21lZGlhVXBkYXRlQhAKDl9jb250YWN0VXBkYXRlQhEKD19jb250YWN0UmVxdWVzdE' + 'IMCgpfZmxhbWVTeW5jQgsKCV9wdXNoS2V5c0ILCglfcmVhY3Rpb25CDgoMX3RleHRNZXNzYWdl' + 'Qg4KDF9ncm91cENyZWF0ZUIMCgpfZ3JvdXBKb2luQg4KDF9ncm91cFVwZGF0ZUIXChVfcmVzZW' + '5kR3JvdXBQdWJsaWNLZXlCEQoPX2Vycm9yX21lc3NhZ2VzQhoKGF9hZGRpdGlvbmFsX2RhdGFf' + 'bWVzc2FnZQ=='); diff --git a/lib/src/model/protobuf/client/messages.proto b/lib/src/model/protobuf/client/messages.proto index 062fc6f..7154bac 100644 --- a/lib/src/model/protobuf/client/messages.proto +++ b/lib/src/model/protobuf/client/messages.proto @@ -58,6 +58,7 @@ message EncryptedContent { enum Type { ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD = 0; UNKNOWN_MESSAGE_TYPE = 2; + SESSION_OUT_OF_SYNC = 3; } Type type = 1; string related_receipt_id = 2; diff --git a/lib/src/services/api/client2client/errors.c2c.dart b/lib/src/services/api/client2client/errors.c2c.dart index d5259f0..598424e 100644 --- a/lib/src/services/api/client2client/errors.c2c.dart +++ b/lib/src/services/api/client2client/errors.c2c.dart @@ -3,14 +3,15 @@ import 'package:drift/drift.dart' show Value; import 'package:twonly/globals.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart'; +import 'package:twonly/src/utils/log.dart'; Future handleErrorMessage( int fromUserId, EncryptedContent_ErrorMessages error, ) async { + Log.error('Got error from $fromUserId: $error'); + switch (error.type) { - case EncryptedContent_ErrorMessages_Type.UNKNOWN_MESSAGE_TYPE: - break; case EncryptedContent_ErrorMessages_Type .ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD: await twonlyDB.receiptsDao.updateReceiptWidthUserId( @@ -25,5 +26,8 @@ Future handleErrorMessage( requested: Value(true), ), ); + // ignore: no_default_cases + default: + break; } } diff --git a/lib/src/services/api/server_messages.dart b/lib/src/services/api/server_messages.dart index 5e84cfe..3f88459 100644 --- a/lib/src/services/api/server_messages.dart +++ b/lib/src/services/api/server_messages.dart @@ -32,38 +32,41 @@ import 'package:twonly/src/utils/misc.dart'; final lockHandleServerMessage = Mutex(); Future handleServerMessage(server.ServerToClient msg) async { - /// Returns means, that the server can delete the message from the server. - final ok = client.Response_Ok()..none = true; - var response = client.Response()..ok = ok; + return lockHandleServerMessage.protect(() async { + /// Returns means, that the server can delete the message from the server. + final ok = client.Response_Ok()..none = true; + var response = client.Response()..ok = ok; - try { - if (msg.v0.hasRequestNewPreKeys()) { - response = await handleRequestNewPreKey(); - } else if (msg.v0.hasNewMessage()) { - await handleClient2ClientMessage(msg.v0.newMessage); - } else if (msg.v0.hasNewMessages()) { - for (final newMessage in msg.v0.newMessages.newMessages) { - try { - await handleClient2ClientMessage(newMessage); - } catch (e) { - Log.error(e); + try { + if (msg.v0.hasRequestNewPreKeys()) { + response = await handleRequestNewPreKey(); + } else if (msg.v0.hasNewMessage()) { + await handleClient2ClientMessage(msg.v0.newMessage); + } else if (msg.v0.hasNewMessages()) { + for (final newMessage in msg.v0.newMessages.newMessages) { + try { + await handleClient2ClientMessage(newMessage); + } catch (e) { + Log.error(e); + } } + } else { + Log.error('Unknown server message: $msg'); } - } else { - Log.error('Unknown server message: $msg'); + } catch (e) { + Log.error(e); } - } catch (e) { - Log.error(e); - } - final v0 = client.V0() - ..seq = msg.v0.seq - ..response = response; + final v0 = client.V0() + ..seq = msg.v0.seq + ..response = response; - await apiService.sendResponse(ClientToServer()..v0 = v0); + await apiService.sendResponse(ClientToServer()..v0 = v0); + }); } DateTime lastPushKeyRequest = clock.now().subtract(const Duration(hours: 1)); +bool alreadyPerformedAnResync = false; Mutex protectReceiptCheck = Mutex(); diff --git a/lib/src/services/api/utils.dart b/lib/src/services/api/utils.dart index facc198..38d5501 100644 --- a/lib/src/services/api/utils.dart +++ b/lib/src/services/api/utils.dart @@ -84,7 +84,7 @@ Future handleMediaError(MediaFile media) async { Future importSignalContactAndCreateRequest( server.Response_UserData userdata, ) async { - if (await createNewSignalSession(userdata)) { + if (await processSignalUserData(userdata)) { // 1. Setup notifications keys with the other user await setupNotificationWithUsers( forceContact: userdata.userId.toInt(), diff --git a/lib/src/services/group.services.dart b/lib/src/services/group.services.dart index e9c8697..e975778 100644 --- a/lib/src/services/group.services.dart +++ b/lib/src/services/group.services.dart @@ -472,7 +472,7 @@ Future addNewHiddenContact(int contactId) async { const Value(true), // this will hide the contact in the contact list ), ); - await createNewSignalSession(userData); + await processSignalUserData(userData); unawaited(setupNotificationWithUsers(forceContact: contactId)); return true; } diff --git a/lib/src/services/signal/encryption.signal.dart b/lib/src/services/signal/encryption.signal.dart index d499f95..a565f32 100644 --- a/lib/src/services/signal/encryption.signal.dart +++ b/lib/src/services/signal/encryption.signal.dart @@ -1,9 +1,12 @@ import 'dart:typed_data'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; +// ignore: implementation_imports +import 'package:libsignal_protocol_dart/src/invalid_message_exception.dart'; import 'package:mutex/mutex.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; -import 'package:twonly/src/services/signal/consts.signal.dart'; +import 'package:twonly/src/services/api/messages.dart'; +import 'package:twonly/src/services/signal/session.signal.dart'; import 'package:twonly/src/services/signal/utils.signal.dart'; import 'package:twonly/src/utils/log.dart'; @@ -17,7 +20,7 @@ Future signalEncryptMessage( return lockingSignalEncryption.protect(() async { try { final signalStore = (await getSignalStore())!; - final address = SignalProtocolAddress(target.toString(), defaultDeviceId); + final address = getSignalAddress(target); final session = SessionCipher.fromStore(signalStore, address); return await session.encrypt(plaintextContent); } catch (e) { @@ -27,16 +30,18 @@ Future signalEncryptMessage( }); } +bool alreadyPerformedAnResync = false; + Future<(EncryptedContent?, PlaintextContent_DecryptionErrorMessage_Type?)> signalDecryptMessage( - int source, + int fromUserId, Uint8List encryptedContentRaw, int type, ) async { try { final session = SessionCipher.fromStore( (await getSignalStore())!, - SignalProtocolAddress(source.toString(), defaultDeviceId), + getSignalAddress(fromUserId), ); Uint8List plaintext; @@ -59,6 +64,26 @@ Future<(EncryptedContent?, PlaintextContent_DecryptionErrorMessage_Type?)> } on InvalidKeyIdException catch (e) { Log.warn(e); return (null, PlaintextContent_DecryptionErrorMessage_Type.PREKEY_UNKNOWN); + } on InvalidMessageException catch (e) { + if (!alreadyPerformedAnResync) { + if (await handleSessionResync(fromUserId)) { + // This flag prevents from resyncing the session the client received multiple new + // messages from the server he could not decrypt + alreadyPerformedAnResync = true; + + // This message contains a new PreKeyBundle establishing a new signal session + await sendCipherText( + fromUserId, + EncryptedContent( + errorMessages: EncryptedContent_ErrorMessages( + type: EncryptedContent_ErrorMessages_Type.SESSION_OUT_OF_SYNC, + ), + ), + ); + } + } + Log.warn(e); + return (null, PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN); } catch (e) { Log.error(e); return (null, PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN); diff --git a/lib/src/services/signal/session.signal.dart b/lib/src/services/signal/session.signal.dart index 6e48445..e00520e 100644 --- a/lib/src/services/signal/session.signal.dart +++ b/lib/src/services/signal/session.signal.dart @@ -6,17 +6,14 @@ import 'package:twonly/src/services/signal/consts.signal.dart'; import 'package:twonly/src/services/signal/utils.signal.dart'; import 'package:twonly/src/utils/log.dart'; -Future createNewSignalSession(Response_UserData userData) async { +Future processSignalUserData(Response_UserData userData) async { final SignalProtocolStore? signalStore = await getSignalStore(); if (signalStore == null) { return false; } - final targetAddress = SignalProtocolAddress( - userData.userId.toString(), - defaultDeviceId, - ); + final targetAddress = getSignalAddress(userData.userId.toInt()); final sessionBuilder = SessionBuilder.fromSignalStore( signalStore, @@ -82,30 +79,6 @@ Future deleteSessionWithTarget(int target) async { await signalStore.sessionStore.deleteSession(address); } -Future generateSessionFingerPrint(int target) async { - final signalStore = await getSignalStore(); - if (signalStore == null) return null; - try { - final targetIdentity = await signalStore - .getIdentity(SignalProtocolAddress(target.toString(), defaultDeviceId)); - if (targetIdentity != null) { - final generator = NumericFingerprintGenerator(5200); - final localFingerprint = generator.createFor( - 1, - Uint8List.fromList([gUser.userId]), - (await signalStore.getIdentityKeyPair()).getPublicKey(), - Uint8List.fromList([target]), - targetIdentity, - ); - - return localFingerprint; - } - return null; - } catch (e) { - return null; - } -} - Future getPublicKeyFromContact(int contactId) async { final signalStore = await getSignalStore(); if (signalStore == null) return null; @@ -124,3 +97,13 @@ Future getPublicKeyFromContact(int contactId) async { return null; } } + +Future handleSessionResync(int fromUserId) async { + final userData = await apiService.getUserById(fromUserId); + if (userData != null) { + Log.info('Got new session data from the server to re-sync the session'); + return processSignalUserData(userData); + } + Log.info('Could not download userdata from the server.'); + return false; +} diff --git a/lib/src/services/signal/utils.signal.dart b/lib/src/services/signal/utils.signal.dart index ef4a1cd..dc8ef36 100644 --- a/lib/src/services/signal/utils.signal.dart +++ b/lib/src/services/signal/utils.signal.dart @@ -1,6 +1,7 @@ import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import 'package:twonly/src/database/signal/connect_signal_protocol_store.dart'; import 'package:twonly/src/model/json/signal_identity.dart'; +import 'package:twonly/src/services/signal/consts.signal.dart'; import 'package:twonly/src/services/signal/identity.signal.dart'; Future getSignalStore() async { @@ -18,3 +19,7 @@ Future getSignalStoreFromIdentity( signalIdentity.registrationId, ); } + +SignalProtocolAddress getSignalAddress(int userId) { + return SignalProtocolAddress(userId.toString(), defaultDeviceId); +} diff --git a/lib/src/views/settings/developer/automated_testing.view.dart b/lib/src/views/settings/developer/automated_testing.view.dart index d4beded..aafa579 100644 --- a/lib/src/views/settings/developer/automated_testing.view.dart +++ b/lib/src/views/settings/developer/automated_testing.view.dart @@ -2,8 +2,10 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/services/api/messages.dart'; +import 'package:twonly/src/services/signal/utils.signal.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; @@ -26,42 +28,92 @@ class _AutomatedTestingViewState extends State { @override Widget build(BuildContext context) { + if (kReleaseMode) return Container(); return Scaffold( appBar: AppBar( title: const Text('Automated Testing'), ), body: ListView( children: [ - if (!kReleaseMode) - ListTile( - title: const Text('Sending a lot of messages.'), - subtitle: Text(lotsOfMessagesStatus), - onTap: () async { - final username = await showUserNameDialog(context); - if (username == null) return; - Log.info('Requested to send to $username'); + ListTile( + title: const Text('Trigger Signal Out-Of-Sync'), + onTap: () async { + final username = await showUserNameDialog(context); + if (username == null) return; + final contacts = await twonlyDB.contactsDao + .getContactsByUsername(username.toLowerCase()); + if (contacts.length != 1) { + Log.error('No single user fund'); + return; + } + final userId = contacts.first.userId; - final contacts = await twonlyDB.contactsDao - .getContactsByUsername(username.toLowerCase()); + final group = await twonlyDB.groupsDao.getDirectChat(userId); + if (group == null) { + Log.error('Target user must have a group!'); + return; + } - for (final contact in contacts) { - Log.info('Sending to ${contact.username}'); - final group = - await twonlyDB.groupsDao.getDirectChat(contact.userId); - for (var i = 0; i < 200; i++) { - setState(() { - lotsOfMessagesStatus = - 'At message $i to ${contact.username}.'; - }); - await insertAndSendTextMessage( - group!.groupId, - 'Message $i.', - null, - ); - } + final sessionStore = await getSignalStore(); + + // 1. Store a valid session + final originalSession = + await sessionStore!.loadSession(getSignalAddress(userId)); + final serializedSession = originalSession.serialize(); + + for (var i = 0; i < 10; i++) { + await insertAndSendTextMessage( + group.groupId, + 'DesyncTest_1', + null, + ); + } + + final corruptedSession = + SessionRecord.fromSerialized(serializedSession); + await sessionStore.storeSession( + getSignalAddress(userId), + corruptedSession, + ); + + await insertAndSendTextMessage( + group.groupId, + 'DesyncTest_2', + null, + ); + + // The other client should res + }, + ), + ListTile( + title: const Text('Sending a lot of messages.'), + subtitle: Text(lotsOfMessagesStatus), + onTap: () async { + final username = await showUserNameDialog(context); + if (username == null) return; + Log.info('Requested to send to $username'); + + final contacts = await twonlyDB.contactsDao + .getContactsByUsername(username.toLowerCase()); + + for (final contact in contacts) { + Log.info('Sending to ${contact.username}'); + final group = + await twonlyDB.groupsDao.getDirectChat(contact.userId); + for (var i = 0; i < 200; i++) { + setState(() { + lotsOfMessagesStatus = + 'At message $i to ${contact.username}.'; + }); + await insertAndSendTextMessage( + group!.groupId, + 'Message $i.', + null, + ); } - }, - ), + } + }, + ), ], ), ); diff --git a/pubspec.yaml b/pubspec.yaml index 4ef5a04..9eb29ca 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: "twonly, a privacy-friendly way to connect with friends through sec publish_to: 'none' -version: 0.0.96+96 +version: 0.0.97+97 environment: sdk: ^3.6.0