You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
206 lines
6.8 KiB
Go
206 lines
6.8 KiB
Go
3 years ago
|
// 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 whatsmeow
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"time"
|
||
|
|
||
|
"go.mau.fi/whatsmeow/appstate"
|
||
|
waBinary "go.mau.fi/whatsmeow/binary"
|
||
|
"go.mau.fi/whatsmeow/types"
|
||
|
"go.mau.fi/whatsmeow/types/events"
|
||
|
)
|
||
|
|
||
|
func (cli *Client) handleEncryptNotification(node *waBinary.Node) {
|
||
|
from := node.AttrGetter().JID("from")
|
||
|
if from == types.ServerJID {
|
||
|
count := node.GetChildByTag("count")
|
||
|
ag := count.AttrGetter()
|
||
|
otksLeft := ag.Int("value")
|
||
|
if !ag.OK() {
|
||
|
cli.Log.Warnf("Didn't get number of OTKs left in encryption notification %s", node.XMLString())
|
||
|
return
|
||
|
}
|
||
|
cli.Log.Infof("Got prekey count from server: %s", node.XMLString())
|
||
|
if otksLeft < MinPreKeyCount {
|
||
|
cli.uploadPreKeys()
|
||
|
}
|
||
|
} else if _, ok := node.GetOptionalChildByTag("identity"); ok {
|
||
|
cli.Log.Debugf("Got identity change for %s: %s, deleting all identities/sessions for that number", from, node.XMLString())
|
||
|
err := cli.Store.Identities.DeleteAllIdentities(from.User)
|
||
|
if err != nil {
|
||
|
cli.Log.Warnf("Failed to delete all identities of %s from store after identity change: %v", from, err)
|
||
|
}
|
||
|
err = cli.Store.Sessions.DeleteAllSessions(from.User)
|
||
|
if err != nil {
|
||
|
cli.Log.Warnf("Failed to delete all sessions of %s from store after identity change: %v", from, err)
|
||
|
}
|
||
|
ts := time.Unix(node.AttrGetter().Int64("t"), 0)
|
||
|
cli.dispatchEvent(&events.IdentityChange{JID: from, Timestamp: ts})
|
||
|
} else {
|
||
|
cli.Log.Debugf("Got unknown encryption notification from server: %s", node.XMLString())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (cli *Client) handleAppStateNotification(node *waBinary.Node) {
|
||
|
for _, collection := range node.GetChildrenByTag("collection") {
|
||
|
ag := collection.AttrGetter()
|
||
|
name := appstate.WAPatchName(ag.String("name"))
|
||
|
version := ag.Uint64("version")
|
||
|
cli.Log.Debugf("Got server sync notification that app state %s has updated to version %d", name, version)
|
||
|
err := cli.FetchAppState(name, false, false)
|
||
|
if errors.Is(err, ErrIQDisconnected) || errors.Is(err, ErrNotConnected) {
|
||
|
// There are some app state changes right before a remote logout, so stop syncing if we're disconnected.
|
||
|
cli.Log.Debugf("Failed to sync app state after notification: %v, not trying to sync other states", err)
|
||
|
return
|
||
|
} else if err != nil {
|
||
|
cli.Log.Errorf("Failed to sync app state after notification: %v", err)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (cli *Client) handlePictureNotification(node *waBinary.Node) {
|
||
|
ts := time.Unix(node.AttrGetter().Int64("t"), 0)
|
||
|
for _, child := range node.GetChildren() {
|
||
|
ag := child.AttrGetter()
|
||
|
var evt events.Picture
|
||
|
evt.Timestamp = ts
|
||
|
evt.JID = ag.JID("jid")
|
||
|
evt.Author = ag.OptionalJIDOrEmpty("author")
|
||
|
if child.Tag == "delete" {
|
||
|
evt.Remove = true
|
||
|
} else if child.Tag == "add" {
|
||
|
evt.PictureID = ag.String("id")
|
||
|
} else if child.Tag == "set" {
|
||
|
// TODO sometimes there's a hash and no ID?
|
||
|
evt.PictureID = ag.String("id")
|
||
|
} else {
|
||
|
continue
|
||
|
}
|
||
|
if !ag.OK() {
|
||
|
cli.Log.Debugf("Ignoring picture change notification with unexpected attributes: %v", ag.Error())
|
||
|
continue
|
||
|
}
|
||
|
cli.dispatchEvent(&evt)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (cli *Client) handleDeviceNotification(node *waBinary.Node) {
|
||
|
cli.userDevicesCacheLock.Lock()
|
||
|
defer cli.userDevicesCacheLock.Unlock()
|
||
|
ag := node.AttrGetter()
|
||
|
from := ag.JID("from")
|
||
|
cached, ok := cli.userDevicesCache[from]
|
||
|
if !ok {
|
||
|
cli.Log.Debugf("No device list cached for %s, ignoring device list notification", from)
|
||
|
return
|
||
|
}
|
||
|
cachedParticipantHash := participantListHashV2(cached)
|
||
|
for _, child := range node.GetChildren() {
|
||
|
if child.Tag != "add" && child.Tag != "remove" {
|
||
|
cli.Log.Debugf("Unknown device list change tag %s", child.Tag)
|
||
|
continue
|
||
|
}
|
||
|
cag := child.AttrGetter()
|
||
|
deviceHash := cag.String("device_hash")
|
||
|
deviceChild, _ := child.GetOptionalChildByTag("device")
|
||
|
changedDeviceJID := deviceChild.AttrGetter().JID("jid")
|
||
|
switch child.Tag {
|
||
|
case "add":
|
||
|
cached = append(cached, changedDeviceJID)
|
||
|
case "remove":
|
||
|
for i, jid := range cached {
|
||
|
if jid == changedDeviceJID {
|
||
|
cached = append(cached[:i], cached[i+1:]...)
|
||
|
}
|
||
|
}
|
||
|
case "update":
|
||
|
// ???
|
||
|
}
|
||
|
newParticipantHash := participantListHashV2(cached)
|
||
|
if newParticipantHash == deviceHash {
|
||
|
cli.Log.Debugf("%s's device list hash changed from %s to %s (%s). New hash matches", from, cachedParticipantHash, deviceHash, child.Tag)
|
||
|
cli.userDevicesCache[from] = cached
|
||
|
} else {
|
||
|
cli.Log.Warnf("%s's device list hash changed from %s to %s (%s). New hash doesn't match (%s)", from, cachedParticipantHash, deviceHash, child.Tag, newParticipantHash)
|
||
|
delete(cli.userDevicesCache, from)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (cli *Client) handleOwnDevicesNotification(node *waBinary.Node) {
|
||
|
cli.userDevicesCacheLock.Lock()
|
||
|
defer cli.userDevicesCacheLock.Unlock()
|
||
|
cached, ok := cli.userDevicesCache[cli.Store.ID.ToNonAD()]
|
||
|
if !ok {
|
||
|
cli.Log.Debugf("Ignoring own device change notification, device list not cached")
|
||
|
return
|
||
|
}
|
||
|
oldHash := participantListHashV2(cached)
|
||
|
expectedNewHash := node.AttrGetter().String("dhash")
|
||
|
var newDeviceList []types.JID
|
||
|
for _, child := range node.GetChildren() {
|
||
|
jid := child.AttrGetter().JID("jid")
|
||
|
if child.Tag == "device" && !jid.IsEmpty() {
|
||
|
jid.AD = true
|
||
|
newDeviceList = append(newDeviceList, jid)
|
||
|
}
|
||
|
}
|
||
|
newHash := participantListHashV2(newDeviceList)
|
||
|
if newHash != expectedNewHash {
|
||
|
cli.Log.Debugf("Received own device list change notification %s -> %s, but expected hash was %s", oldHash, newHash, expectedNewHash)
|
||
|
delete(cli.userDevicesCache, cli.Store.ID.ToNonAD())
|
||
|
} else {
|
||
|
cli.Log.Debugf("Received own device list change notification %s -> %s", oldHash, newHash)
|
||
|
cli.userDevicesCache[cli.Store.ID.ToNonAD()] = newDeviceList
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (cli *Client) handleAccountSyncNotification(node *waBinary.Node) {
|
||
|
for _, child := range node.GetChildren() {
|
||
|
switch child.Tag {
|
||
|
case "privacy":
|
||
|
cli.handlePrivacySettingsNotification(&child)
|
||
|
case "devices":
|
||
|
cli.handleOwnDevicesNotification(&child)
|
||
|
default:
|
||
|
cli.Log.Debugf("Unhandled account sync item %s", child.Tag)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (cli *Client) handleNotification(node *waBinary.Node) {
|
||
|
ag := node.AttrGetter()
|
||
|
notifType := ag.String("type")
|
||
|
if !ag.OK() {
|
||
|
return
|
||
|
}
|
||
|
go cli.sendAck(node)
|
||
|
switch notifType {
|
||
|
case "encrypt":
|
||
|
go cli.handleEncryptNotification(node)
|
||
|
case "server_sync":
|
||
|
go cli.handleAppStateNotification(node)
|
||
|
case "account_sync":
|
||
|
go cli.handleAccountSyncNotification(node)
|
||
|
case "devices":
|
||
|
go cli.handleDeviceNotification(node)
|
||
|
case "w:gp2":
|
||
|
evt, err := cli.parseGroupNotification(node)
|
||
|
if err != nil {
|
||
|
cli.Log.Errorf("Failed to parse group notification: %v", err)
|
||
|
} else {
|
||
|
go cli.dispatchEvent(evt)
|
||
|
}
|
||
|
case "picture":
|
||
|
go cli.handlePictureNotification(node)
|
||
|
default:
|
||
|
cli.Log.Debugf("Unhandled notification with type %s", notifType)
|
||
|
}
|
||
|
}
|