diff options
Diffstat (limited to 'vendor/go.mau.fi/whatsmeow/appstate')
-rw-r--r-- | vendor/go.mau.fi/whatsmeow/appstate/decode.go | 2 | ||||
-rw-r--r-- | vendor/go.mau.fi/whatsmeow/appstate/encode.go | 200 | ||||
-rw-r--r-- | vendor/go.mau.fi/whatsmeow/appstate/hash.go | 4 | ||||
-rw-r--r-- | vendor/go.mau.fi/whatsmeow/appstate/keys.go | 16 |
4 files changed, 219 insertions, 3 deletions
diff --git a/vendor/go.mau.fi/whatsmeow/appstate/decode.go b/vendor/go.mau.fi/whatsmeow/appstate/decode.go index 5c895470..980c20de 100644 --- a/vendor/go.mau.fi/whatsmeow/appstate/decode.go +++ b/vendor/go.mau.fi/whatsmeow/appstate/decode.go @@ -290,7 +290,7 @@ func (proc *Processor) DecodePatches(list *PatchList, initialState HashState, va if err != nil { return } - patchMAC := generatePatchMAC(patch, list.Name, keys.PatchMAC) + patchMAC := generatePatchMAC(patch, list.Name, keys.PatchMAC, patch.GetVersion().GetVersion()) if !bytes.Equal(patchMAC, patch.GetPatchMac()) { err = fmt.Errorf("failed to verify patch v%d: %w", version, ErrMismatchingPatchMAC) return diff --git a/vendor/go.mau.fi/whatsmeow/appstate/encode.go b/vendor/go.mau.fi/whatsmeow/appstate/encode.go new file mode 100644 index 00000000..1cb7d659 --- /dev/null +++ b/vendor/go.mau.fi/whatsmeow/appstate/encode.go @@ -0,0 +1,200 @@ +package appstate + +import ( + "crypto/sha256" + "encoding/json" + "fmt" + "time" + + "google.golang.org/protobuf/proto" + + waProto "go.mau.fi/whatsmeow/binary/proto" + "go.mau.fi/whatsmeow/types" + "go.mau.fi/whatsmeow/util/cbcutil" +) + +// MutationInfo contains information about a single mutation to the app state. +type MutationInfo struct { + // Index contains the thing being mutated (like `mute` or `pin_v1`), followed by parameters like the target JID. + Index []string + // Version is a static number that depends on the thing being mutated. + Version int32 + // Value contains the data for the mutation. + Value *waProto.SyncActionValue +} + +// PatchInfo contains information about a patch to the app state. +// A patch can contain multiple mutations, as long as all mutations are in the same app state type. +type PatchInfo struct { + // Timestamp is the time when the patch was created. This will be filled automatically in EncodePatch if it's zero. + Timestamp time.Time + // Type is the app state type being mutated. + Type WAPatchName + // Mutations contains the individual mutations to apply to the app state in this patch. + Mutations []MutationInfo +} + +// BuildMute builds an app state patch for muting or unmuting a chat. +// +// If mute is true and the mute duration is zero, the chat is muted forever. +func BuildMute(target types.JID, mute bool, muteDuration time.Duration) PatchInfo { + var muteEndTimestamp *int64 + if muteDuration > 0 { + muteEndTimestamp = proto.Int64(time.Now().Add(muteDuration).UnixMilli()) + } + + return PatchInfo{ + Type: WAPatchRegularHigh, + Mutations: []MutationInfo{{ + Index: []string{IndexMute, target.String()}, + Version: 2, + Value: &waProto.SyncActionValue{ + MuteAction: &waProto.MuteAction{ + Muted: proto.Bool(mute), + MuteEndTimestamp: muteEndTimestamp, + }, + }, + }}, + } +} + +func newPinMutationInfo(target types.JID, pin bool) MutationInfo { + return MutationInfo{ + Index: []string{IndexPin, target.String()}, + Version: 5, + Value: &waProto.SyncActionValue{ + PinAction: &waProto.PinAction{ + Pinned: &pin, + }, + }, + } +} + +// BuildPin builds an app state patch for pinning or unpinning a chat. +func BuildPin(target types.JID, pin bool) PatchInfo { + return PatchInfo{ + Type: WAPatchRegularLow, + Mutations: []MutationInfo{ + newPinMutationInfo(target, pin), + }, + } +} + +// BuildArchive builds an app state patch for archiving or unarchiving a chat. +// +// The last message timestamp and last message key are optional and can be set to zero values (`time.Time{}` and `nil`). +// +// Archiving a chat will also unpin it automatically. +func BuildArchive(target types.JID, archive bool, lastMessageTimestamp time.Time, lastMessageKey *waProto.MessageKey) PatchInfo { + if lastMessageTimestamp.IsZero() { + lastMessageTimestamp = time.Now() + } + archiveMutationInfo := MutationInfo{ + Index: []string{IndexArchive, target.String()}, + Version: 3, + Value: &waProto.SyncActionValue{ + ArchiveChatAction: &waProto.ArchiveChatAction{ + Archived: &archive, + MessageRange: &waProto.SyncActionMessageRange{ + LastMessageTimestamp: proto.Int64(lastMessageTimestamp.Unix()), + // TODO set LastSystemMessageTimestamp? + }, + }, + }, + } + + if lastMessageKey != nil { + archiveMutationInfo.Value.ArchiveChatAction.MessageRange.Messages = []*waProto.SyncActionMessage{{ + Key: lastMessageKey, + Timestamp: proto.Int64(lastMessageTimestamp.Unix()), + }} + } + + mutations := []MutationInfo{archiveMutationInfo} + if archive { + mutations = append(mutations, newPinMutationInfo(target, false)) + } + + result := PatchInfo{ + Type: WAPatchRegularLow, + Mutations: mutations, + } + + return result +} + +func (proc *Processor) EncodePatch(keyID []byte, state HashState, patchInfo PatchInfo) ([]byte, error) { + keys, err := proc.getAppStateKey(keyID) + if err != nil { + return nil, fmt.Errorf("failed to get app state key details with key ID %x: %w", keyID, err) + } + + if patchInfo.Timestamp.IsZero() { + patchInfo.Timestamp = time.Now() + } + + mutations := make([]*waProto.SyncdMutation, 0, len(patchInfo.Mutations)) + for _, mutationInfo := range patchInfo.Mutations { + mutationInfo.Value.Timestamp = proto.Int64(patchInfo.Timestamp.UnixMilli()) + + indexBytes, err := json.Marshal(mutationInfo.Index) + if err != nil { + return nil, fmt.Errorf("failed to marshal mutation index: %w", err) + } + + pbObj := &waProto.SyncActionData{ + Index: indexBytes, + Value: mutationInfo.Value, + Padding: []byte{}, + Version: &mutationInfo.Version, + } + + content, err := proto.Marshal(pbObj) + if err != nil { + return nil, fmt.Errorf("failed to marshal mutation: %w", err) + } + + encryptedContent, err := cbcutil.Encrypt(keys.ValueEncryption, nil, content) + if err != nil { + return nil, fmt.Errorf("failed to encrypt mutation: %w", err) + } + + valueMac := generateContentMAC(waProto.SyncdMutation_SET, encryptedContent, keyID, keys.ValueMAC) + indexMac := concatAndHMAC(sha256.New, keys.Index, indexBytes) + + mutations = append(mutations, &waProto.SyncdMutation{ + Operation: waProto.SyncdMutation_SET.Enum(), + Record: &waProto.SyncdRecord{ + Index: &waProto.SyncdIndex{Blob: indexMac}, + Value: &waProto.SyncdValue{Blob: append(encryptedContent, valueMac...)}, + KeyId: &waProto.KeyId{Id: keyID}, + }, + }) + } + + warn, err := state.updateHash(mutations, func(indexMAC []byte, _ int) ([]byte, error) { + return proc.Store.AppState.GetAppStateMutationMAC(string(patchInfo.Type), indexMAC) + }) + if len(warn) > 0 { + proc.Log.Warnf("Warnings while updating hash for %s (sending new app state): %+v", patchInfo.Type, warn) + } + if err != nil { + return nil, fmt.Errorf("failed to update state hash: %w", err) + } + + state.Version += 1 + + syncdPatch := &waProto.SyncdPatch{ + SnapshotMac: state.generateSnapshotMAC(patchInfo.Type, keys.SnapshotMAC), + KeyId: &waProto.KeyId{Id: keyID}, + Mutations: mutations, + } + syncdPatch.PatchMac = generatePatchMAC(syncdPatch, patchInfo.Type, keys.PatchMAC, state.Version) + + result, err := proto.Marshal(syncdPatch) + if err != nil { + return nil, fmt.Errorf("failed to marshal compiled patch: %w", err) + } + + return result, nil +} diff --git a/vendor/go.mau.fi/whatsmeow/appstate/hash.go b/vendor/go.mau.fi/whatsmeow/appstate/hash.go index bb17eeac..2bb0924a 100644 --- a/vendor/go.mau.fi/whatsmeow/appstate/hash.go +++ b/vendor/go.mau.fi/whatsmeow/appstate/hash.go @@ -77,14 +77,14 @@ func (hs *HashState) generateSnapshotMAC(name WAPatchName, key []byte) []byte { return concatAndHMAC(sha256.New, key, hs.Hash[:], uint64ToBytes(hs.Version), []byte(name)) } -func generatePatchMAC(patch *waProto.SyncdPatch, name WAPatchName, key []byte) []byte { +func generatePatchMAC(patch *waProto.SyncdPatch, name WAPatchName, key []byte, version uint64) []byte { dataToHash := make([][]byte, len(patch.GetMutations())+3) dataToHash[0] = patch.GetSnapshotMac() for i, mutation := range patch.Mutations { val := mutation.GetRecord().GetValue().GetBlob() dataToHash[i+1] = val[len(val)-32:] } - dataToHash[len(dataToHash)-2] = uint64ToBytes(patch.GetVersion().GetVersion()) + dataToHash[len(dataToHash)-2] = uint64ToBytes(version) dataToHash[len(dataToHash)-1] = []byte(name) return concatAndHMAC(sha256.New, key, dataToHash...) } diff --git a/vendor/go.mau.fi/whatsmeow/appstate/keys.go b/vendor/go.mau.fi/whatsmeow/appstate/keys.go index ec19dc26..95f7d134 100644 --- a/vendor/go.mau.fi/whatsmeow/appstate/keys.go +++ b/vendor/go.mau.fi/whatsmeow/appstate/keys.go @@ -35,6 +35,22 @@ const ( // 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" +) + type Processor struct { keyCache map[string]ExpandedAppStateKeys keyCacheLock sync.Mutex |