| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- // Copyright (c) 2021 Tulir Asokan
- //
- // This Source Code Form is subject to the terms of the Mozilla Public
- // License, v. 2.0. If a copy of the MPL was not distributed with this
- // file, You can obtain one at http://mozilla.org/MPL/2.0/.
- // Package appstate implements encoding and decoding WhatsApp's app state patches.
- package appstate
- import (
- "context"
- "encoding/base64"
- "sync"
- "go.mau.fi/whatsmeow/store"
- "go.mau.fi/whatsmeow/util/hkdfutil"
- waLog "go.mau.fi/whatsmeow/util/log"
- )
- // WAPatchName represents a type of app state patch.
- type WAPatchName string
- const (
- // WAPatchCriticalBlock contains the user's settings like push name and locale.
- WAPatchCriticalBlock WAPatchName = "critical_block"
- // WAPatchCriticalUnblockLow contains the user's contact list.
- WAPatchCriticalUnblockLow WAPatchName = "critical_unblock_low"
- // WAPatchRegularLow contains some local chat settings like pin, archive status, and the setting of whether to unarchive chats when messages come in.
- WAPatchRegularLow WAPatchName = "regular_low"
- // WAPatchRegularHigh contains more local chat settings like mute status and starred messages.
- WAPatchRegularHigh WAPatchName = "regular_high"
- // WAPatchRegular contains protocol info about app state patches like key expiration.
- WAPatchRegular WAPatchName = "regular"
- )
- // AllPatchNames contains all currently known patch state names.
- var AllPatchNames = [...]WAPatchName{WAPatchCriticalBlock, WAPatchCriticalUnblockLow, WAPatchRegularHigh, WAPatchRegular, WAPatchRegularLow}
- // Constants for the first part of app state indexes.
- const (
- IndexMute = "mute"
- IndexPin = "pin_v1"
- IndexArchive = "archive"
- IndexContact = "contact"
- IndexClearChat = "clearChat"
- IndexDeleteChat = "deleteChat"
- IndexStar = "star"
- IndexDeleteMessageForMe = "deleteMessageForMe"
- IndexMarkChatAsRead = "markChatAsRead"
- IndexSettingPushName = "setting_pushName"
- IndexSettingUnarchiveChats = "setting_unarchiveChats"
- IndexUserStatusMute = "userStatusMute"
- IndexLabelEdit = "label_edit"
- IndexLabelAssociationChat = "label_jid"
- IndexLabelAssociationMessage = "label_message"
- )
- type Processor struct {
- keyCache map[string]ExpandedAppStateKeys
- keyCacheLock sync.Mutex
- Store *store.Device
- Log waLog.Logger
- }
- func NewProcessor(store *store.Device, log waLog.Logger) *Processor {
- return &Processor{
- keyCache: make(map[string]ExpandedAppStateKeys),
- Store: store,
- Log: log,
- }
- }
- type ExpandedAppStateKeys struct {
- Index []byte
- ValueEncryption []byte
- ValueMAC []byte
- SnapshotMAC []byte
- PatchMAC []byte
- }
- func expandAppStateKeys(keyData []byte) (keys ExpandedAppStateKeys) {
- appStateKeyExpanded := hkdfutil.SHA256(keyData, nil, []byte("WhatsApp Mutation Keys"), 160)
- return ExpandedAppStateKeys{appStateKeyExpanded[0:32], appStateKeyExpanded[32:64], appStateKeyExpanded[64:96], appStateKeyExpanded[96:128], appStateKeyExpanded[128:160]}
- }
- func (proc *Processor) getAppStateKey(ctx context.Context, keyID []byte) (keys ExpandedAppStateKeys, err error) {
- keyCacheID := base64.RawStdEncoding.EncodeToString(keyID)
- var ok bool
- proc.keyCacheLock.Lock()
- defer proc.keyCacheLock.Unlock()
- keys, ok = proc.keyCache[keyCacheID]
- if !ok {
- var keyData *store.AppStateSyncKey
- keyData, err = proc.Store.AppStateKeys.GetAppStateSyncKey(ctx, keyID)
- if keyData != nil {
- keys = expandAppStateKeys(keyData.Data)
- proc.keyCache[keyCacheID] = keys
- } else if err == nil {
- err = ErrKeyNotFound
- }
- }
- return
- }
- func (proc *Processor) GetMissingKeyIDs(ctx context.Context, pl *PatchList) [][]byte {
- cache := make(map[string]bool)
- var missingKeys [][]byte
- checkMissing := func(keyID []byte) {
- if keyID == nil {
- return
- }
- stringKeyID := base64.RawStdEncoding.EncodeToString(keyID)
- _, alreadyAdded := cache[stringKeyID]
- if !alreadyAdded {
- keyData, err := proc.Store.AppStateKeys.GetAppStateSyncKey(ctx, keyID)
- if err != nil {
- proc.Log.Warnf("Error fetching key %X while checking if it's missing: %v", keyID, err)
- }
- missing := keyData == nil && err == nil
- cache[stringKeyID] = missing
- if missing {
- missingKeys = append(missingKeys, keyID)
- }
- }
- }
- if pl.Snapshot != nil {
- checkMissing(pl.Snapshot.GetKeyID().GetID())
- for _, record := range pl.Snapshot.GetRecords() {
- checkMissing(record.GetKeyID().GetID())
- }
- }
- for _, patch := range pl.Patches {
- checkMissing(patch.GetKeyID().GetID())
- }
- return missingKeys
- }
|