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

syntax = "proto3";

package signalservice;

option java_package = "org.signal.storageservice.storage.protos.contacts";
option java_outer_classname = "StorageProtos";
option java_multiple_files = true;

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

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

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

message StorageItems {
  repeated StorageItem items = 1;
}

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

message ReadOperation {
  repeated bytes readKey = 1;
}

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;
      CHAT_FOLDER = 8;
      NOTIFICATION_PROFILE = 9;
    }

    bytes raw = 1;
    Type type = 2;
  }

  uint64 version = 1;
  uint32 sourceDevice = 3;
  repeated Identifier identifiers = 2;
  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;
    ChatFolderRecord chatFolder = 8;
    NotificationProfile notificationProfile = 9;
  }
}

// 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 {
    string given = 1;
    string family = 2;
  }

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

message GroupV1Record {
  bytes id = 1;
  reserved /*blocked*/ 2;
  reserved /*whitelisted*/ 3;
  reserved /*archived*/ 4;
  reserved /*markedUnread*/ 5;
  reserved /*mutedUntilTimestamp*/ 6;
}

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

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

message Payments {
  bool enabled = 1;
  bytes entropy = 2;
}

message AccountRecord {

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

  message PinnedConversation {
    message Contact {
      string serviceId = 1;
      string e164 = 2;
      bytes serviceIdBinary = 3; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
    }

    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;
    }

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

  message IAPSubscriberData {
    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;
    }
  }

  message BackupTierHistory {
    // See zkgroup for integer particular values. Unset if backups are not enabled.
    optional uint64 backupTier = 1;
    optional uint64 endedAtTimestamp = 2;
  }

  message NotificationProfileManualOverride {
    message ManuallyEnabled {
      bytes id = 1;

      // This will be unset if no timespan was chosen in the UI.
      uint64 endAtTimestampMs = 3;
    }

    oneof override {
      uint64 disabledAtTimestampMs = 1;
      ManuallyEnabled enabled = 2;
    }
  }

  bytes profileKey = 1;
  string givenName = 2;
  string familyName = 3;
  string avatarUrlPath = 4;
  bool noteToSelfArchived = 5;
  bool readReceipts = 6;
  bool sealedSenderIndicators = 7;
  bool typingIndicators = 8;
  reserved 9; // proxiedLinkPreviews
  bool noteToSelfMarkedUnread = 10;
  bool linkPreviews = 11;
  PhoneNumberSharingMode phoneNumberSharingMode = 12;
  bool unlistedPhoneNumber = 13;
  repeated PinnedConversation pinnedConversations = 14;
  bool preferContactAvatars = 15;
  uint32 universalExpireTimer = 17;
  reserved 18; // primarySendsSms
  reserved 19; // deprecatedE164
  repeated string preferredReactionEmoji = 20;
  bytes donorSubscriberId = 21;
  string donorSubscriberCurrencyCode = 22;
  bool displayBadgesOnProfile = 23;
  bool donorSubscriptionManuallyCancelled = 24;
  bool keepMutedChatsArchived = 25;
  bool hasSetMyStoriesPrivacy = 26;
  bool hasViewedOnboardingStory = 27; // Whether the user has opened and played back the
                                      // onboarding story in the story viewer.
  reserved 28; // deprecatedStoriesDisabled
  bool storiesDisabled = 29;
  OptionalBool storyViewReceiptsEnabled = 30;
  reserved 31; // hasReadOnboardingStory
  bool hasSeenGroupStoryEducationSheet = 32; // Whether the user has seen the group story education
                                             // sheet. This is a sticky value.
  string username = 33; // Format: `nickname.discriminator`, e.g. `signalapp.123`
                        // Updated only when username is confirmed or deleted on server.
  bool hasCompletedUsernameOnboarding = 34; // Whether the user has completed username
                                            // onboarding.
  UsernameLink usernameLink = 35;
  reserved /*backupsSubscriberId*/ 36;
  reserved /*backupsSubscriberCurrencyCode*/ 37;
  reserved /*backupsSubscriptionManuallyCancelled*/ 38;
  // Set to true after backups are enabled and one is uploaded.
  reserved /*hasBackup*/ 39;
  // See zkgroup for integer particular values. Unset if backups are not enabled.
  optional uint64 backupTier = 40;
  IAPSubscriberData backupSubscriberData = 41;
  optional AvatarColor avatarColor = 42;
  NotificationProfileManualOverride notificationProfileManualOverride = 44;
  bool notificationProfileSyncDisabled = 45;
  bool automaticKeyVerificationDisabled = 46;
  bool hasSeenAdminDeleteEducationDialog = 47;
}

message StoryDistributionListRecord {
  bytes identifier = 1;
  string name = 2;
  repeated string recipientServiceIds = 3;
  uint64 deletedAtTimestamp = 4;
  bool allowsReplies = 5;
  bool isBlockList = 6;
  repeated bytes recipientServiceIdsBinary = 7; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
}

message StickerPackRecord {
  bytes packId = 1; // 16 bytes
  bytes packKey = 2; // 32 bytes, used to derive the AES-256 key
                     // aesKey = HKDF(
                     //   input = packKey,
                     //   salt = 32 zero bytes,
                     //   info = "Sticker Pack"
                     // )
  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`.
  uint64 deletedAtTimestamp = 4; // Timestamp in milliseconds. When present and
                                 // non-zero - `packKey` and `position` should
                                 // be unset
}

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

message Recipient {
  message Contact {
    string serviceId = 1;
    string e164 = 2;
    bytes serviceIdBinary = 3; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
  }

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

message ChatFolderRecord {
  // Represents the default "All chats" folder record vs all other custom folders
  enum FolderType {
    UNKNOWN = 0;
    ALL = 1;
    CUSTOM = 2;
  }

  bytes id = 1;
  string name = 2;
  uint32 position = 3;                // Position order of folder, low-to-high from start-to-end
  bool showOnlyUnread = 4;
  bool showMutedChats = 5;
  bool includeAllIndividualChats = 6; // Folder includes all 1:1 chats, unless excluded
  bool includeAllGroupChats = 7;      // Folder includes all group chats, unless excluded
  FolderType folderType = 8;
  repeated Recipient includedRecipients = 9;
  repeated Recipient excludedRecipients = 10;
  uint64 deletedAtTimestampMs = 11;   // When non-zero, `position` should be set to 4294967295 (2^32-1) and `includedRecipients` should be empty
}

message NotificationProfile {
  enum DayOfWeek {
    UNKNOWN = 0; // Interpret as "Monday"
    MONDAY = 1;
    TUESDAY = 2;
    WEDNESDAY = 3;
    THURSDAY = 4;
    FRIDAY = 5;
    SATURDAY = 6;
    SUNDAY = 7;
  }

  bytes id = 1;
  string name = 2;
  optional string emoji = 3;
  fixed32 color = 4; // 0xAARRGGBB
  uint64 createdAtMs = 5;
  bool allowAllCalls = 6;
  bool allowAllMentions = 7;
  repeated Recipient allowedMembers = 8;
  bool scheduleEnabled = 9;
  uint32 scheduleStartTime = 10; // 24-hour clock int, 0000-2359 (e.g., 15, 900, 1130, 2345)
  uint32 scheduleEndTime = 11; // 24-hour clock int, 0000-2359 (e.g., 15, 900, 1130, 2345)
  repeated DayOfWeek scheduleDaysEnabled = 12;
  uint64 deletedAtTimestampMs = 13;
}
