Export/import verified state
This commit is contained in:
parent
b517bb817f
commit
104995e980
3 changed files with 83 additions and 23 deletions
|
@ -9,8 +9,7 @@ option java_package = "org.thoughtcrime.securesms.backup.v2.proto";
|
|||
message BackupInfo {
|
||||
uint64 version = 1;
|
||||
uint64 backupTimeMs = 2;
|
||||
bytes mediaRootBackupKey = 3; // 32-byte random value generated when the
|
||||
// backup is uploaded for the first time.
|
||||
bytes mediaRootBackupKey = 3; // 32-byte random value generated when the backup is uploaded for the first time.
|
||||
}
|
||||
|
||||
// Frames must follow in the following ordering rules:
|
||||
|
@ -112,6 +111,12 @@ message Recipient {
|
|||
}
|
||||
|
||||
message Contact {
|
||||
enum IdentityState {
|
||||
DEFAULT = 0;
|
||||
VERIFIED = 1;
|
||||
UNVERIFIED = 2;
|
||||
}
|
||||
|
||||
message Registered { }
|
||||
message NotRegistered {
|
||||
uint64 unregisteredTimestamp = 1;
|
||||
|
@ -140,6 +145,8 @@ message Contact {
|
|||
optional string profileGivenName = 11;
|
||||
optional string profileFamilyName = 12;
|
||||
bool hideStory = 13;
|
||||
optional bytes identityKey = 14;
|
||||
IdentityState identityState = 15;
|
||||
}
|
||||
|
||||
message Group {
|
||||
|
@ -240,9 +247,9 @@ message Chat {
|
|||
uint64 id = 1; // generated id for reference only within this file
|
||||
uint64 recipientId = 2;
|
||||
bool archived = 3;
|
||||
uint32 pinnedOrder = 4; // 0 = unpinned, otherwise chat is considered pinned and will be displayed in ascending order
|
||||
uint64 expirationTimerMs = 5; // 0 = no expire timer.
|
||||
uint64 muteUntilMs = 6;
|
||||
optional uint32 pinnedOrder = 4; // will be displayed in ascending order
|
||||
optional uint64 expirationTimerMs = 5;
|
||||
optional uint64 muteUntilMs = 6; // UINT64_MAX (2^63 - 1) = "always muted".
|
||||
bool markedUnread = 7;
|
||||
bool dontNotifyForMentionsIfMuted = 8;
|
||||
ChatStyle style = 9;
|
||||
|
@ -268,7 +275,7 @@ message CallLink {
|
|||
optional bytes adminKey = 2; // Only present if the user is an admin
|
||||
string name = 3;
|
||||
Restrictions restrictions = 4;
|
||||
uint64 expirationMs = 5;
|
||||
optional uint64 expirationMs = 5;
|
||||
}
|
||||
|
||||
message AdHocCall {
|
||||
|
@ -285,6 +292,8 @@ message AdHocCall {
|
|||
}
|
||||
|
||||
message DistributionListItem {
|
||||
// distribution ids are UUIDv4s. "My Story" is represented
|
||||
// by an all-0 UUID (00000000-0000-0000-0000-000000000000).
|
||||
bytes distributionId = 1; // distribution list ids are uuids
|
||||
|
||||
oneof item {
|
||||
|
@ -310,7 +319,7 @@ message DistributionList {
|
|||
message ChatItem {
|
||||
message IncomingMessageDetails {
|
||||
uint64 dateReceived = 1;
|
||||
uint64 dateServerSent = 2;
|
||||
optional uint64 dateServerSent = 2;
|
||||
bool read = 3;
|
||||
bool sealedSender = 4;
|
||||
}
|
||||
|
@ -325,8 +334,8 @@ message ChatItem {
|
|||
uint64 chatId = 1; // conversation id
|
||||
uint64 authorId = 2; // recipient id
|
||||
uint64 dateSent = 3;
|
||||
uint64 expireStartDate = 4; // timestamp of when expiration timer started ticking down
|
||||
uint64 expiresInMs = 5; // how long timer of message is (ms)
|
||||
optional uint64 expireStartDate = 4; // timestamp of when expiration timer started ticking down
|
||||
optional uint64 expiresInMs = 5; // how long timer of message is (ms)
|
||||
repeated ChatItem revisions = 6; // ordered from oldest to newest
|
||||
bool sms = 7;
|
||||
|
||||
|
@ -616,7 +625,7 @@ message FilePointer {
|
|||
message AttachmentLocator {
|
||||
string cdnKey = 1;
|
||||
uint32 cdnNumber = 2;
|
||||
uint64 uploadTimestamp = 3;
|
||||
optional uint64 uploadTimestamp = 3;
|
||||
bytes key = 4;
|
||||
bytes digest = 5;
|
||||
uint32 size = 6;
|
||||
|
@ -650,7 +659,8 @@ message Quote {
|
|||
enum Type {
|
||||
UNKNOWN = 0;
|
||||
NORMAL = 1;
|
||||
GIFTBADGE = 2;
|
||||
GIFT_BADGE = 2;
|
||||
VIEW_ONCE = 3;
|
||||
}
|
||||
|
||||
message QuotedAttachment {
|
||||
|
@ -768,8 +778,7 @@ message GroupCall {
|
|||
optional uint64 ringerRecipientId = 3;
|
||||
optional uint64 startedCallRecipientId = 4;
|
||||
uint64 startedCallTimestamp = 5;
|
||||
// The time the call ended. 0 indicates an unknown time.
|
||||
uint64 endedCallTimestamp = 6;
|
||||
optional uint64 endedCallTimestamp = 6; // The time the call ended.
|
||||
bool read = 7;
|
||||
}
|
||||
|
||||
|
@ -825,7 +834,6 @@ message SessionSwitchoverChatUpdate {
|
|||
|
||||
message GroupChangeChatUpdate {
|
||||
message Update {
|
||||
// Note: group expiration timer changes are represented as ExpirationTimerChatUpdate.
|
||||
oneof update {
|
||||
GenericGroupUpdate genericGroupUpdate = 1;
|
||||
GroupCreationUpdate groupCreationUpdate = 2;
|
||||
|
|
|
@ -17,7 +17,10 @@ import {
|
|||
pauseWriteAccess,
|
||||
resumeWriteAccess,
|
||||
} from '../../sql/Client';
|
||||
import type { PageMessagesCursorType } from '../../sql/Interface';
|
||||
import type {
|
||||
PageMessagesCursorType,
|
||||
IdentityKeyType,
|
||||
} from '../../sql/Interface';
|
||||
import * as log from '../../logging/log';
|
||||
import { GiftBadgeStates } from '../../components/conversation/Message';
|
||||
import { type CustomColorType } from '../../types/Colors';
|
||||
|
@ -290,6 +293,13 @@ export class BackupExportStream extends Readable {
|
|||
stickerPacks: 0,
|
||||
};
|
||||
|
||||
const identityKeys = await DataReader.getAllIdentityKeys();
|
||||
const identityKeysById = new Map(
|
||||
identityKeys.map(key => {
|
||||
return [key.id, key];
|
||||
})
|
||||
);
|
||||
|
||||
for (const { attributes } of window.ConversationController.getAll()) {
|
||||
const recipientId = this.getRecipientId({
|
||||
id: attributes.id,
|
||||
|
@ -297,7 +307,11 @@ export class BackupExportStream extends Readable {
|
|||
e164: attributes.e164,
|
||||
});
|
||||
|
||||
const recipient = this.toRecipient(recipientId, attributes);
|
||||
const recipient = this.toRecipient(
|
||||
recipientId,
|
||||
attributes,
|
||||
identityKeysById
|
||||
);
|
||||
if (recipient === undefined) {
|
||||
// Can't be backed up.
|
||||
continue;
|
||||
|
@ -804,7 +818,8 @@ export class BackupExportStream extends Readable {
|
|||
convo: Omit<
|
||||
ConversationAttributesType,
|
||||
'id' | 'version' | 'expireTimerVersion'
|
||||
>
|
||||
>,
|
||||
identityKeysById?: ReadonlyMap<IdentityKeyType['id'], IdentityKeyType>
|
||||
): Backups.IRecipient | undefined {
|
||||
const res: Backups.IRecipient = {
|
||||
id: recipientId,
|
||||
|
@ -824,6 +839,11 @@ export class BackupExportStream extends Readable {
|
|||
throw missingCaseError(convo.removalStage);
|
||||
}
|
||||
|
||||
let identityKey: IdentityKeyType | undefined;
|
||||
if (identityKeysById != null && convo.serviceId != null) {
|
||||
identityKey = identityKeysById.get(convo.serviceId);
|
||||
}
|
||||
|
||||
res.contact = {
|
||||
aci:
|
||||
convo.serviceId && convo.serviceId !== convo.pni
|
||||
|
@ -856,6 +876,10 @@ export class BackupExportStream extends Readable {
|
|||
profileGivenName: convo.profileName,
|
||||
profileFamilyName: convo.profileFamilyName,
|
||||
hideStory: convo.hideStory === true,
|
||||
identityKey: identityKey?.publicKey || null,
|
||||
|
||||
// Integer values match so we can use it as is
|
||||
identityState: identityKey?.verified ?? 0,
|
||||
};
|
||||
} else if (isGroupV2(convo) && convo.masterKey) {
|
||||
let storySendMode: Backups.Group.StorySendMode;
|
||||
|
@ -2092,6 +2116,15 @@ export class BackupExportStream extends Readable {
|
|||
return null;
|
||||
}
|
||||
|
||||
let quoteType: Backups.Quote.Type;
|
||||
if (quote.isGiftBadge) {
|
||||
quoteType = Backups.Quote.Type.GIFT_BADGE;
|
||||
} else if (quote.isViewOnce) {
|
||||
quoteType = Backups.Quote.Type.VIEW_ONCE;
|
||||
} else {
|
||||
quoteType = Backups.Quote.Type.NORMAL;
|
||||
}
|
||||
|
||||
return {
|
||||
targetSentTimestamp: Long.fromNumber(quote.id),
|
||||
authorId,
|
||||
|
@ -2123,9 +2156,7 @@ export class BackupExportStream extends Readable {
|
|||
}
|
||||
)
|
||||
),
|
||||
type: quote.isGiftBadge
|
||||
? Backups.Quote.Type.GIFTBADGE
|
||||
: Backups.Quote.Type.NORMAL,
|
||||
type: quoteType,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import { DataReader, DataWriter } from '../../sql/Client';
|
|||
import {
|
||||
AttachmentDownloadSource,
|
||||
type StoryDistributionWithMembersType,
|
||||
type IdentityKeyType,
|
||||
} from '../../sql/Interface';
|
||||
import * as log from '../../logging/log';
|
||||
import { GiftBadgeStates } from '../../components/conversation/Message';
|
||||
|
@ -209,6 +210,7 @@ export class BackupImportStream extends Writable {
|
|||
string,
|
||||
ConversationAttributesType
|
||||
>();
|
||||
private readonly identityKeys = new Map<ServiceIdString, IdentityKeyType>();
|
||||
private readonly saveMessageBatch = new Set<MessageAttributesType>();
|
||||
private readonly stickerPacks = new Array<StickerPackPointerType>();
|
||||
private ourConversation?: ConversationAttributesType;
|
||||
|
@ -489,10 +491,14 @@ export class BackupImportStream extends Writable {
|
|||
const saves = Array.from(this.conversations.values());
|
||||
this.conversations.clear();
|
||||
|
||||
const identityKeys = Array.from(this.identityKeys.values());
|
||||
this.identityKeys.clear();
|
||||
|
||||
// Queue writes at the same time to prevent races.
|
||||
await Promise.all([
|
||||
DataWriter.saveConversations(saves),
|
||||
DataWriter.updateConversations(updates),
|
||||
DataWriter.bulkAddIdentityKeys(identityKeys),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -817,11 +823,13 @@ export class BackupImportStream extends Writable {
|
|||
break;
|
||||
}
|
||||
|
||||
const serviceId = aci ?? pni;
|
||||
|
||||
const attrs: ConversationAttributesType = {
|
||||
id: generateUuid(),
|
||||
type: 'private',
|
||||
version: 2,
|
||||
serviceId: aci ?? pni,
|
||||
serviceId,
|
||||
pni,
|
||||
e164,
|
||||
removalStage,
|
||||
|
@ -836,6 +844,17 @@ export class BackupImportStream extends Writable {
|
|||
expireTimerVersion: 1,
|
||||
};
|
||||
|
||||
if (serviceId != null && Bytes.isNotEmpty(contact.identityKey)) {
|
||||
this.identityKeys.set(serviceId, {
|
||||
id: serviceId,
|
||||
publicKey: contact.identityKey,
|
||||
verified: contact.identityState || 0,
|
||||
firstUse: true,
|
||||
timestamp: this.now,
|
||||
nonblockingApproval: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (contact.notRegistered) {
|
||||
const timestamp = contact.notRegistered.unregisteredTimestamp?.toNumber();
|
||||
attrs.discoveredUnregisteredAt = timestamp || this.now;
|
||||
|
@ -848,7 +867,6 @@ export class BackupImportStream extends Writable {
|
|||
}
|
||||
|
||||
if (contact.blocked) {
|
||||
const serviceId = aci || pni;
|
||||
if (serviceId) {
|
||||
await window.storage.blocked.addBlockedServiceId(serviceId);
|
||||
}
|
||||
|
@ -1671,8 +1689,11 @@ export class BackupImportStream extends Writable {
|
|||
type: Backups.Quote.Type | null | undefined
|
||||
): SignalService.DataMessage.Quote.Type {
|
||||
switch (type) {
|
||||
case Backups.Quote.Type.GIFTBADGE:
|
||||
case Backups.Quote.Type.GIFT_BADGE:
|
||||
return SignalService.DataMessage.Quote.Type.GIFT_BADGE;
|
||||
case Backups.Quote.Type.VIEW_ONCE:
|
||||
// No special treatment, we'll compute it once we find the message
|
||||
return SignalService.DataMessage.Quote.Type.NORMAL;
|
||||
case Backups.Quote.Type.NORMAL:
|
||||
case Backups.Quote.Type.UNKNOWN:
|
||||
case null:
|
||||
|
|
Loading…
Add table
Reference in a new issue