// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

package signalservice;

option java_package         = "org.whispersystems.signalservice.internal.storage";
option java_outer_classname = "SignalStorageProtos";

enum OptionalBool {
  UNSET = 0;
  ENABLED = 1;
  DISABLED = 2;
}

message StorageManifest  {
  optional uint64 version = 1;
  optional bytes  value   = 2;
}

message StorageItem {
  optional bytes key   = 1;
  optional bytes value = 2;
}

message StorageItems {
  repeated StorageItem items = 1;
}

message ReadOperation {
  repeated bytes readKey = 1;
}

message WriteOperation {
  optional StorageManifest manifest   = 1;
  repeated StorageItem     insertItem = 2;
  repeated bytes           deleteKey  = 3;
  optional bool            clearAll   = 4;
}

message ManifestRecord {
  message Identifier {
    enum Type {
      UNKNOWN = 0;
      CONTACT = 1;
      GROUPV1 = 2;
      GROUPV2 = 3;
      ACCOUNT = 4;
      STORY_DISTRIBUTION_LIST = 5;
      STICKER_PACK = 6;
      CALL_LINK = 7;
    }

    optional bytes raw  = 1;
    optional Type  type = 2;
  }

  optional uint64      version = 1;
  optional uint32 sourceDevice = 3;
  repeated Identifier     keys = 2;
  optional bytes recordIkm = 4;
  // Next ID: 5
}

message StorageRecord {
  oneof record {
    ContactRecord contact = 1;
    GroupV1Record groupV1 = 2;
    GroupV2Record groupV2 = 3;
    AccountRecord account = 4;
    StoryDistributionListRecord storyDistributionList = 5;
    StickerPackRecord stickerPack = 6;
    CallLinkRecord callLink = 7;
  }
}

// If unset - computed as the value of the first byte of SHA-256(msg=CONTACT_ID)
// modulo the count of colors. Once set the avatar color for a recipient is
// never recomputed or changed.
//
// `CONTACT_ID` is the first available identifier from the list:
// - ServiceIdToBinary(ACI)
// - E164
// - ServiceIdToBinary(PNI)
// - Group Id
enum AvatarColor {
  A100 = 0;
  A110 = 1;
  A120 = 2;
  A130 = 3;
  A140 = 4;
  A150 = 5;
  A160 = 6;
  A170 = 7;
  A180 = 8;
  A190 = 9;
  A200 = 10;
  A210 = 11;
}

message ContactRecord {
  enum IdentityState {
    DEFAULT = 0;
    VERIFIED = 1;
    UNVERIFIED = 2;
  }

  message Name {
    optional string given = 1;
    optional string family = 2;
  }

  optional string aci = 1;
  optional string serviceE164 = 2;
  optional string pni = 15;
  optional bytes profileKey = 3;
  optional bytes identityKey = 4;
  optional IdentityState identityState = 5;
  optional string givenName = 6;
  optional string familyName = 7;
  optional string username = 8;
  optional bool blocked = 9;
  optional bool whitelisted = 10;
  optional bool archived = 11;
  optional bool markedUnread = 12;
  optional uint64 mutedUntilTimestamp = 13;
  optional bool hideStory = 14;
  optional uint64 unregisteredAtTimestamp = 16;
  optional string systemGivenName = 17;
  optional string systemFamilyName = 18;
  optional string systemNickname = 19;
  optional bool hidden = 20;
  optional bool pniSignatureVerified = 21;
  optional Name nickname = 22;
  optional string note = 23;
  optional AvatarColor avatarColor = 24;
}

message GroupV1Record {
  optional bytes  id                  = 1;
  optional bool   blocked             = 2;
  optional bool   whitelisted         = 3;
  optional bool   archived            = 4;
  optional bool   markedUnread        = 5;
  optional uint64 mutedUntilTimestamp = 6;
}

message GroupV2Record {
  enum StorySendMode {
    DEFAULT = 0;
    DISABLED = 1;
    ENABLED = 2;
  }

  optional bytes masterKey = 1;
  optional bool blocked = 2;
  optional bool whitelisted = 3;
  optional bool archived = 4;
  optional bool markedUnread = 5;
  optional uint64 mutedUntilTimestamp = 6;
  optional bool dontNotifyForMentionsIfMuted = 7;
  optional bool hideStory = 8;
  reserved /* storySendEnabled */ 9; // removed
  optional StorySendMode storySendMode = 10;
  optional AvatarColor avatarColor = 11;
}

message AccountRecord {
  enum PhoneNumberSharingMode {
    UNKNOWN     = 0;
    EVERYBODY = 1;
    NOBODY        = 2;
  }

  message PinnedConversation {
    message Contact {
      optional string serviceId = 1;
      optional string e164 = 2;
    }

    oneof identifier {
      Contact contact        = 1;
      bytes   legacyGroupId  = 3;
      bytes   groupMasterKey = 4;
    }
  }

  message UsernameLink {
    enum Color {
      UNKNOWN = 0;
      BLUE = 1;
      WHITE = 2;
      GREY = 3;
      OLIVE = 4;
      GREEN = 5;
      ORANGE = 6;
      PINK = 7;
      PURPLE = 8;
    }

    optional bytes entropy = 1; // 32 bytes of entropy used for encryption
    optional bytes serverId = 2; // 16 bytes of encoded UUID provided by the server
    optional Color color = 3; // color of the QR code itself
  }

  message IAPSubscriberData {
    optional bytes subscriberId = 1;

    oneof iapSubscriptionId {
      // Identifies an Android Play Store IAP subscription.
      string purchaseToken = 2;
      // Identifies an iOS App Store IAP subscription.
      uint64 originalTransactionId = 3;
    }
  }

  optional bytes profileKey = 1;
  optional string givenName = 2;
  optional string familyName = 3;
  optional string avatarUrl = 4;
  optional bool noteToSelfArchived = 5;
  optional bool readReceipts = 6;
  optional bool sealedSenderIndicators = 7;
  optional bool typingIndicators = 8;
  reserved 9; // proxiedLinkPreviews
  optional bool noteToSelfMarkedUnread = 10;
  optional bool linkPreviews = 11;
  optional PhoneNumberSharingMode phoneNumberSharingMode = 12;
  optional bool notDiscoverableByPhoneNumber = 13;
  repeated PinnedConversation pinnedConversations = 14;
  optional bool preferContactAvatars = 15;
  optional uint32 universalExpireTimer = 17;
  reserved 18; // primarySendsSms
  reserved 19; // deprecatedE164
  repeated string preferredReactionEmoji = 20;
  optional bytes subscriberId = 21;
  optional string subscriberCurrencyCode = 22;
  optional bool displayBadgesOnProfile = 23;
  optional bool donorSubscriptionManuallyCancelled = 24;
  optional bool keepMutedChatsArchived = 25;
  optional bool hasSetMyStoriesPrivacy = 26;
  optional bool hasViewedOnboardingStory = 27;
  reserved 28; // deprecatedStoriesDisabled
  optional bool storiesDisabled = 29;
  optional OptionalBool storyViewReceiptsEnabled = 30;
  reserved 31; // hasReadOnboardingStory
  optional bool hasSeenGroupStoryEducationSheet = 32;
  optional string username = 33;
  optional bool hasCompletedUsernameOnboarding = 34;
  optional UsernameLink usernameLink = 35;
  reserved /*backupsSubscriberId*/ 36;
  reserved /*backupsSubscriberCurrencyCode*/ 37;
  reserved /*backupsSubscriptionManuallyCancelled*/ 38;
  // Set to true after backups are enabled and one is uploaded.
  optional bool hasBackup = 39;
  // See zkgroup for integer particular values
  optional uint64 backupTier = 40;
  optional IAPSubscriberData backupSubscriberData = 41;
  optional AvatarColor avatarColor = 42;
}

message StoryDistributionListRecord {
  optional bytes identifier = 1;
  optional string name = 2;
  repeated string recipientServiceIds = 3;
  optional uint64 deletedAtTimestamp = 4;
  optional bool allowsReplies = 5;
  optional bool isBlockList = 6;
}

message StickerPackRecord {
  optional bytes packId = 1; // 16 bytes
  optional bytes packKey = 2; // 32 bytes, used to derive the AES-256 key
                              // aesKey = HKDF(
                              //   input = packKey,
                              //   salt = 32 zero bytes,
                              //   info = "Sticker Pack"
                              // )
  optional uint32 position = 3; // When displayed sticker packs should be first sorted
                                // in descending order by zero-based `position` and
                                // then by ascending `packId` (lexicographically,
                                // packId can be treated as a hex string).
                                // When installing a sticker pack the client should find
                                // the maximum `position` among currently known stickers
                                // and use `max_position + 1` as the value for the new
                                // `position`.
  optional uint64 deletedAtTimestamp = 4; // Timestamp in milliseconds. When present and
                                          // non-zero - `packKey` and `position` should
                                          // be unset
}

message CallLinkRecord {
  optional bytes rootKey = 1; // 16 bytes
  optional bytes adminPasskey = 2; // Non-empty when the current user is an admin
  optional uint64 deletedAtTimestampMs = 3; // When present and non-zero, `adminPasskey`
                                            // should be cleared
}
