summaryrefslogtreecommitdiffstats
path: root/vendor/go.mau.fi/whatsmeow/store
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/go.mau.fi/whatsmeow/store')
-rw-r--r--vendor/go.mau.fi/whatsmeow/store/clientpayload.go118
-rw-r--r--vendor/go.mau.fi/whatsmeow/store/signal.go168
-rw-r--r--vendor/go.mau.fi/whatsmeow/store/sqlstore/container.go245
-rw-r--r--vendor/go.mau.fi/whatsmeow/store/sqlstore/store.go610
-rw-r--r--vendor/go.mau.fi/whatsmeow/store/sqlstore/upgrade.go214
-rw-r--r--vendor/go.mau.fi/whatsmeow/store/store.go132
6 files changed, 1487 insertions, 0 deletions
diff --git a/vendor/go.mau.fi/whatsmeow/store/clientpayload.go b/vendor/go.mau.fi/whatsmeow/store/clientpayload.go
new file mode 100644
index 00000000..81519ee2
--- /dev/null
+++ b/vendor/go.mau.fi/whatsmeow/store/clientpayload.go
@@ -0,0 +1,118 @@
+// 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 store
+
+import (
+ "crypto/md5"
+ "encoding/binary"
+ "fmt"
+ "strconv"
+ "strings"
+
+ "google.golang.org/protobuf/proto"
+
+ "go.mau.fi/libsignal/ecc"
+
+ waProto "go.mau.fi/whatsmeow/binary/proto"
+)
+
+// waVersion is the WhatsApp web client version
+var waVersion = []uint32{2, 2202, 9}
+
+// waVersionHash is the md5 hash of a dot-separated waVersion
+var waVersionHash [16]byte
+
+func init() {
+ waVersionParts := make([]string, len(waVersion))
+ for i, part := range waVersion {
+ waVersionParts[i] = strconv.Itoa(int(part))
+ }
+ waVersionString := strings.Join(waVersionParts, ".")
+ waVersionHash = md5.Sum([]byte(waVersionString))
+}
+
+var BaseClientPayload = &waProto.ClientPayload{
+ UserAgent: &waProto.UserAgent{
+ Platform: waProto.UserAgent_WEB.Enum(),
+ ReleaseChannel: waProto.UserAgent_RELEASE.Enum(),
+ AppVersion: &waProto.AppVersion{
+ Primary: &waVersion[0],
+ Secondary: &waVersion[1],
+ Tertiary: &waVersion[2],
+ },
+ Mcc: proto.String("000"),
+ Mnc: proto.String("000"),
+ OsVersion: proto.String("0.1.0"),
+ Manufacturer: proto.String(""),
+ Device: proto.String("Desktop"),
+ OsBuildNumber: proto.String("0.1.0"),
+ LocaleLanguageIso6391: proto.String("en"),
+ LocaleCountryIso31661Alpha2: proto.String("en"),
+ },
+ WebInfo: &waProto.WebInfo{
+ WebSubPlatform: waProto.WebInfo_WEB_BROWSER.Enum(),
+ },
+ ConnectType: waProto.ClientPayload_WIFI_UNKNOWN.Enum(),
+ ConnectReason: waProto.ClientPayload_USER_ACTIVATED.Enum(),
+}
+
+var CompanionProps = &waProto.CompanionProps{
+ Os: proto.String("whatsmeow"),
+ Version: &waProto.AppVersion{
+ Primary: proto.Uint32(0),
+ Secondary: proto.Uint32(1),
+ Tertiary: proto.Uint32(0),
+ },
+ PlatformType: waProto.CompanionProps_UNKNOWN.Enum(),
+ RequireFullSync: proto.Bool(false),
+}
+
+func SetOSInfo(name string, version [3]uint32) {
+ CompanionProps.Os = &name
+ CompanionProps.Version.Primary = &version[0]
+ CompanionProps.Version.Secondary = &version[1]
+ CompanionProps.Version.Tertiary = &version[2]
+ BaseClientPayload.UserAgent.OsVersion = proto.String(fmt.Sprintf("%d.%d.%d", version[0], version[1], version[2]))
+ BaseClientPayload.UserAgent.OsBuildNumber = BaseClientPayload.UserAgent.OsVersion
+}
+
+func (device *Device) getRegistrationPayload() *waProto.ClientPayload {
+ payload := proto.Clone(BaseClientPayload).(*waProto.ClientPayload)
+ regID := make([]byte, 4)
+ binary.BigEndian.PutUint32(regID, device.RegistrationID)
+ preKeyID := make([]byte, 4)
+ binary.BigEndian.PutUint32(preKeyID, device.SignedPreKey.KeyID)
+ companionProps, _ := proto.Marshal(CompanionProps)
+ payload.RegData = &waProto.CompanionRegData{
+ ERegid: regID,
+ EKeytype: []byte{ecc.DjbType},
+ EIdent: device.IdentityKey.Pub[:],
+ ESkeyId: preKeyID[1:],
+ ESkeyVal: device.SignedPreKey.Pub[:],
+ ESkeySig: device.SignedPreKey.Signature[:],
+ BuildHash: waVersionHash[:],
+ CompanionProps: companionProps,
+ }
+ payload.Passive = proto.Bool(false)
+ return payload
+}
+
+func (device *Device) getLoginPayload() *waProto.ClientPayload {
+ payload := proto.Clone(BaseClientPayload).(*waProto.ClientPayload)
+ payload.Username = proto.Uint64(device.ID.UserInt())
+ payload.Device = proto.Uint32(uint32(device.ID.Device))
+ payload.Passive = proto.Bool(true)
+ return payload
+}
+
+func (device *Device) GetClientPayload() *waProto.ClientPayload {
+ if device.ID != nil {
+ return device.getLoginPayload()
+ } else {
+ return device.getRegistrationPayload()
+ }
+}
diff --git a/vendor/go.mau.fi/whatsmeow/store/signal.go b/vendor/go.mau.fi/whatsmeow/store/signal.go
new file mode 100644
index 00000000..4c2a4e92
--- /dev/null
+++ b/vendor/go.mau.fi/whatsmeow/store/signal.go
@@ -0,0 +1,168 @@
+// 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 store
+
+import (
+ "go.mau.fi/libsignal/ecc"
+ groupRecord "go.mau.fi/libsignal/groups/state/record"
+ "go.mau.fi/libsignal/keys/identity"
+ "go.mau.fi/libsignal/protocol"
+ "go.mau.fi/libsignal/serialize"
+ "go.mau.fi/libsignal/state/record"
+ "go.mau.fi/libsignal/state/store"
+)
+
+var SignalProtobufSerializer = serialize.NewProtoBufSerializer()
+
+var _ store.SignalProtocol = (*Device)(nil)
+
+func (device *Device) GetIdentityKeyPair() *identity.KeyPair {
+ return identity.NewKeyPair(
+ identity.NewKey(ecc.NewDjbECPublicKey(*device.IdentityKey.Pub)),
+ ecc.NewDjbECPrivateKey(*device.IdentityKey.Priv),
+ )
+}
+
+func (device *Device) GetLocalRegistrationId() uint32 {
+ return device.RegistrationID
+}
+
+func (device *Device) SaveIdentity(address *protocol.SignalAddress, identityKey *identity.Key) {
+ err := device.Identities.PutIdentity(address.String(), identityKey.PublicKey().PublicKey())
+ if err != nil {
+ device.Log.Errorf("Failed to save identity of %s: %v", address.String(), err)
+ }
+}
+
+func (device *Device) IsTrustedIdentity(address *protocol.SignalAddress, identityKey *identity.Key) bool {
+ isTrusted, err := device.Identities.IsTrustedIdentity(address.String(), identityKey.PublicKey().PublicKey())
+ if err != nil {
+ device.Log.Errorf("Failed to check if %s's identity is trusted: %v", address.String(), err)
+ }
+ return isTrusted
+}
+
+func (device *Device) LoadPreKey(id uint32) *record.PreKey {
+ preKey, err := device.PreKeys.GetPreKey(id)
+ if err != nil {
+ device.Log.Errorf("Failed to load prekey %d: %v", id, err)
+ return nil
+ } else if preKey == nil {
+ return nil
+ }
+ return record.NewPreKey(preKey.KeyID, ecc.NewECKeyPair(
+ ecc.NewDjbECPublicKey(*preKey.Pub),
+ ecc.NewDjbECPrivateKey(*preKey.Priv),
+ ), nil)
+}
+
+func (device *Device) RemovePreKey(id uint32) {
+ err := device.PreKeys.RemovePreKey(id)
+ if err != nil {
+ device.Log.Errorf("Failed to remove prekey %d: %v", id, err)
+ }
+}
+
+func (device *Device) StorePreKey(preKeyID uint32, preKeyRecord *record.PreKey) {
+ panic("not implemented")
+}
+
+func (device *Device) ContainsPreKey(preKeyID uint32) bool {
+ panic("not implemented")
+}
+
+func (device *Device) LoadSession(address *protocol.SignalAddress) *record.Session {
+ rawSess, err := device.Sessions.GetSession(address.String())
+ if err != nil {
+ device.Log.Errorf("Failed to load session with %s: %v", address.String(), err)
+ return record.NewSession(SignalProtobufSerializer.Session, SignalProtobufSerializer.State)
+ } else if rawSess == nil {
+ return record.NewSession(SignalProtobufSerializer.Session, SignalProtobufSerializer.State)
+ }
+ sess, err := record.NewSessionFromBytes(rawSess, SignalProtobufSerializer.Session, SignalProtobufSerializer.State)
+ if err != nil {
+ device.Log.Errorf("Failed to deserialize session with %s: %v", address.String(), err)
+ return record.NewSession(SignalProtobufSerializer.Session, SignalProtobufSerializer.State)
+ }
+ return sess
+}
+
+func (device *Device) GetSubDeviceSessions(name string) []uint32 {
+ panic("not implemented")
+}
+
+func (device *Device) StoreSession(address *protocol.SignalAddress, record *record.Session) {
+ err := device.Sessions.PutSession(address.String(), record.Serialize())
+ if err != nil {
+ device.Log.Errorf("Failed to store session with %s: %v", address.String(), err)
+ }
+}
+
+func (device *Device) ContainsSession(remoteAddress *protocol.SignalAddress) bool {
+ hasSession, err := device.Sessions.HasSession(remoteAddress.String())
+ if err != nil {
+ device.Log.Warnf("Failed to check if store has session for %s: %v", remoteAddress.String(), err)
+ }
+ return hasSession
+}
+
+func (device *Device) DeleteSession(remoteAddress *protocol.SignalAddress) {
+ panic("not implemented")
+}
+
+func (device *Device) DeleteAllSessions() {
+ panic("not implemented")
+}
+
+func (device *Device) LoadSignedPreKey(signedPreKeyID uint32) *record.SignedPreKey {
+ if signedPreKeyID == device.SignedPreKey.KeyID {
+ return record.NewSignedPreKey(signedPreKeyID, 0, ecc.NewECKeyPair(
+ ecc.NewDjbECPublicKey(*device.SignedPreKey.Pub),
+ ecc.NewDjbECPrivateKey(*device.SignedPreKey.Priv),
+ ), *device.SignedPreKey.Signature, nil)
+ }
+ return nil
+}
+
+func (device *Device) LoadSignedPreKeys() []*record.SignedPreKey {
+ panic("not implemented")
+}
+
+func (device *Device) StoreSignedPreKey(signedPreKeyID uint32, record *record.SignedPreKey) {
+ panic("not implemented")
+}
+
+func (device *Device) ContainsSignedPreKey(signedPreKeyID uint32) bool {
+ panic("not implemented")
+}
+
+func (device *Device) RemoveSignedPreKey(signedPreKeyID uint32) {
+ panic("not implemented")
+}
+
+func (device *Device) StoreSenderKey(senderKeyName *protocol.SenderKeyName, keyRecord *groupRecord.SenderKey) {
+ err := device.SenderKeys.PutSenderKey(senderKeyName.GroupID(), senderKeyName.Sender().String(), keyRecord.Serialize())
+ if err != nil {
+ device.Log.Errorf("Failed to store sender key from %s for %s: %v", senderKeyName.Sender().String(), senderKeyName.GroupID(), err)
+ }
+}
+
+func (device *Device) LoadSenderKey(senderKeyName *protocol.SenderKeyName) *groupRecord.SenderKey {
+ rawKey, err := device.SenderKeys.GetSenderKey(senderKeyName.GroupID(), senderKeyName.Sender().String())
+ if err != nil {
+ device.Log.Errorf("Failed to load sender key from %s for %s: %v", senderKeyName.Sender().String(), senderKeyName.GroupID(), err)
+ return groupRecord.NewSenderKey(SignalProtobufSerializer.SenderKeyRecord, SignalProtobufSerializer.SenderKeyState)
+ } else if rawKey == nil {
+ return groupRecord.NewSenderKey(SignalProtobufSerializer.SenderKeyRecord, SignalProtobufSerializer.SenderKeyState)
+ }
+ key, err := groupRecord.NewSenderKeyFromBytes(rawKey, SignalProtobufSerializer.SenderKeyRecord, SignalProtobufSerializer.SenderKeyState)
+ if err != nil {
+ device.Log.Errorf("Failed to deserialize sender key from %s for %s: %v", senderKeyName.Sender().String(), senderKeyName.GroupID(), err)
+ return groupRecord.NewSenderKey(SignalProtobufSerializer.SenderKeyRecord, SignalProtobufSerializer.SenderKeyState)
+ }
+ return key
+}
diff --git a/vendor/go.mau.fi/whatsmeow/store/sqlstore/container.go b/vendor/go.mau.fi/whatsmeow/store/sqlstore/container.go
new file mode 100644
index 00000000..30d91971
--- /dev/null
+++ b/vendor/go.mau.fi/whatsmeow/store/sqlstore/container.go
@@ -0,0 +1,245 @@
+// 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 sqlstore
+
+import (
+ "crypto/rand"
+ "database/sql"
+ "errors"
+ "fmt"
+ mathRand "math/rand"
+
+ waProto "go.mau.fi/whatsmeow/binary/proto"
+ "go.mau.fi/whatsmeow/store"
+ "go.mau.fi/whatsmeow/types"
+ "go.mau.fi/whatsmeow/util/keys"
+ waLog "go.mau.fi/whatsmeow/util/log"
+)
+
+// Container is a wrapper for a SQL database that can contain multiple whatsmeow sessions.
+type Container struct {
+ db *sql.DB
+ dialect string
+ log waLog.Logger
+}
+
+var _ store.DeviceContainer = (*Container)(nil)
+
+// New connects to the given SQL database and wraps it in a Container.
+//
+// Only SQLite and Postgres are currently fully supported.
+//
+// The logger can be nil and will default to a no-op logger.
+//
+// When using SQLite, it's strongly recommended to enable foreign keys by adding `?_foreign_keys=true`:
+// container, err := sqlstore.New("sqlite3", "file:yoursqlitefile.db?_foreign_keys=on", nil)
+func New(dialect, address string, log waLog.Logger) (*Container, error) {
+ db, err := sql.Open(dialect, address)
+ if err != nil {
+ return nil, fmt.Errorf("failed to open database: %w", err)
+ }
+ container := NewWithDB(db, dialect, log)
+ err = container.Upgrade()
+ if err != nil {
+ return nil, fmt.Errorf("failed to upgrade database: %w", err)
+ }
+ return container, nil
+}
+
+// NewWithDB wraps an existing SQL connection in a Container.
+//
+// Only SQLite and Postgres are currently fully supported.
+//
+// The logger can be nil and will default to a no-op logger.
+//
+// When using SQLite, it's strongly recommended to enable foreign keys by adding `?_foreign_keys=true`:
+// db, err := sql.Open("sqlite3", "file:yoursqlitefile.db?_foreign_keys=on")
+// if err != nil {
+// panic(err)
+// }
+// container, err := sqlstore.NewWithDB(db, "sqlite3", nil)
+func NewWithDB(db *sql.DB, dialect string, log waLog.Logger) *Container {
+ if log == nil {
+ log = waLog.Noop
+ }
+ return &Container{
+ db: db,
+ dialect: dialect,
+ log: log,
+ }
+}
+
+const getAllDevicesQuery = `
+SELECT jid, registration_id, noise_key, identity_key,
+ signed_pre_key, signed_pre_key_id, signed_pre_key_sig,
+ adv_key, adv_details, adv_account_sig, adv_device_sig,
+ platform, business_name, push_name
+FROM whatsmeow_device
+`
+
+const getDeviceQuery = getAllDevicesQuery + " WHERE jid=$1"
+
+type scannable interface {
+ Scan(dest ...interface{}) error
+}
+
+func (c *Container) scanDevice(row scannable) (*store.Device, error) {
+ var device store.Device
+ device.Log = c.log
+ device.SignedPreKey = &keys.PreKey{}
+ var noisePriv, identityPriv, preKeyPriv, preKeySig []byte
+ var account waProto.ADVSignedDeviceIdentity
+
+ err := row.Scan(
+ &device.ID, &device.RegistrationID, &noisePriv, &identityPriv,
+ &preKeyPriv, &device.SignedPreKey.KeyID, &preKeySig,
+ &device.AdvSecretKey, &account.Details, &account.AccountSignature, &account.DeviceSignature,
+ &device.Platform, &device.BusinessName, &device.PushName)
+ if err != nil {
+ return nil, fmt.Errorf("failed to scan session: %w", err)
+ } else if len(noisePriv) != 32 || len(identityPriv) != 32 || len(preKeyPriv) != 32 || len(preKeySig) != 64 {
+ return nil, ErrInvalidLength
+ }
+
+ device.NoiseKey = keys.NewKeyPairFromPrivateKey(*(*[32]byte)(noisePriv))
+ device.IdentityKey = keys.NewKeyPairFromPrivateKey(*(*[32]byte)(identityPriv))
+ device.SignedPreKey.KeyPair = *keys.NewKeyPairFromPrivateKey(*(*[32]byte)(preKeyPriv))
+ device.SignedPreKey.Signature = (*[64]byte)(preKeySig)
+ device.Account = &account
+
+ innerStore := NewSQLStore(c, *device.ID)
+ device.Identities = innerStore
+ device.Sessions = innerStore
+ device.PreKeys = innerStore
+ device.SenderKeys = innerStore
+ device.AppStateKeys = innerStore
+ device.AppState = innerStore
+ device.Contacts = innerStore
+ device.ChatSettings = innerStore
+ device.Container = c
+ device.Initialized = true
+
+ return &device, nil
+}
+
+// GetAllDevices finds all the devices in the database.
+func (c *Container) GetAllDevices() ([]*store.Device, error) {
+ res, err := c.db.Query(getAllDevicesQuery)
+ if err != nil {
+ return nil, fmt.Errorf("failed to query sessions: %w", err)
+ }
+ sessions := make([]*store.Device, 0)
+ for res.Next() {
+ sess, scanErr := c.scanDevice(res)
+ if scanErr != nil {
+ return sessions, scanErr
+ }
+ sessions = append(sessions, sess)
+ }
+ return sessions, nil
+}
+
+// GetFirstDevice is a convenience method for getting the first device in the store. If there are
+// no devices, then a new device will be created. You should only use this if you don't want to
+// have multiple sessions simultaneously.
+func (c *Container) GetFirstDevice() (*store.Device, error) {
+ devices, err := c.GetAllDevices()
+ if err != nil {
+ return nil, err
+ }
+ if len(devices) == 0 {
+ return c.NewDevice(), nil
+ } else {
+ return devices[0], nil
+ }
+}
+
+// GetDevice finds the device with the specified JID in the database.
+//
+// If the device is not found, nil is returned instead.
+//
+// Note that the parameter usually must be an AD-JID.
+func (c *Container) GetDevice(jid types.JID) (*store.Device, error) {
+ sess, err := c.scanDevice(c.db.QueryRow(getDeviceQuery, jid))
+ if errors.Is(err, sql.ErrNoRows) {
+ return nil, nil
+ }
+ return sess, err
+}
+
+const (
+ insertDeviceQuery = `
+ INSERT INTO whatsmeow_device (jid, registration_id, noise_key, identity_key,
+ signed_pre_key, signed_pre_key_id, signed_pre_key_sig,
+ adv_key, adv_details, adv_account_sig, adv_device_sig,
+ platform, business_name, push_name)
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
+ ON CONFLICT (jid) DO UPDATE SET platform=$12, business_name=$13, push_name=$14
+ `
+ deleteDeviceQuery = `DELETE FROM whatsmeow_device WHERE jid=$1`
+)
+
+// NewDevice creates a new device in this database.
+//
+// No data is actually stored before Save is called. However, the pairing process will automatically
+// call Save after a successful pairing, so you most likely don't need to call it yourself.
+func (c *Container) NewDevice() *store.Device {
+ device := &store.Device{
+ Log: c.log,
+ Container: c,
+
+ NoiseKey: keys.NewKeyPair(),
+ IdentityKey: keys.NewKeyPair(),
+ RegistrationID: mathRand.Uint32(),
+ AdvSecretKey: make([]byte, 32),
+ }
+ _, err := rand.Read(device.AdvSecretKey)
+ if err != nil {
+ panic(err)
+ }
+ device.SignedPreKey = device.IdentityKey.CreateSignedPreKey(1)
+ return device
+}
+
+// ErrDeviceIDMustBeSet is the error returned by PutDevice if you try to save a device before knowing its JID.
+var ErrDeviceIDMustBeSet = errors.New("device JID must be known before accessing database")
+
+// PutDevice stores the given device in this database. This should be called through Device.Save()
+// (which usually doesn't need to be called manually, as the library does that automatically when relevant).
+func (c *Container) PutDevice(device *store.Device) error {
+ if device.ID == nil {
+ return ErrDeviceIDMustBeSet
+ }
+ _, err := c.db.Exec(insertDeviceQuery,
+ device.ID.String(), device.RegistrationID, device.NoiseKey.Priv[:], device.IdentityKey.Priv[:],
+ device.SignedPreKey.Priv[:], device.SignedPreKey.KeyID, device.SignedPreKey.Signature[:],
+ device.AdvSecretKey, device.Account.Details, device.Account.AccountSignature, device.Account.DeviceSignature,
+ device.Platform, device.BusinessName, device.PushName)
+
+ if !device.Initialized {
+ innerStore := NewSQLStore(c, *device.ID)
+ device.Identities = innerStore
+ device.Sessions = innerStore
+ device.PreKeys = innerStore
+ device.SenderKeys = innerStore
+ device.AppStateKeys = innerStore
+ device.AppState = innerStore
+ device.Contacts = innerStore
+ device.ChatSettings = innerStore
+ device.Initialized = true
+ }
+ return err
+}
+
+// DeleteDevice deletes the given device from this database. This should be called through Device.Delete()
+func (c *Container) DeleteDevice(store *store.Device) error {
+ if store.ID == nil {
+ return ErrDeviceIDMustBeSet
+ }
+ _, err := c.db.Exec(deleteDeviceQuery, store.ID.String())
+ return err
+}
diff --git a/vendor/go.mau.fi/whatsmeow/store/sqlstore/store.go b/vendor/go.mau.fi/whatsmeow/store/sqlstore/store.go
new file mode 100644
index 00000000..ad1eea7e
--- /dev/null
+++ b/vendor/go.mau.fi/whatsmeow/store/sqlstore/store.go
@@ -0,0 +1,610 @@
+// 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 sqlstore contains an SQL-backed implementation of the interfaces in the store package.
+package sqlstore
+
+import (
+ "database/sql"
+ "database/sql/driver"
+ "errors"
+ "fmt"
+ "strings"
+ "sync"
+ "time"
+
+ "go.mau.fi/whatsmeow/store"
+ "go.mau.fi/whatsmeow/types"
+ "go.mau.fi/whatsmeow/util/keys"
+)
+
+// ErrInvalidLength is returned by some database getters if the database returned a byte array with an unexpected length.
+// This should be impossible, as the database schema contains CHECK()s for all the relevant columns.
+var ErrInvalidLength = errors.New("database returned byte array with illegal length")
+
+// PostgresArrayWrapper is a function to wrap array values before passing them to the sql package.
+//
+// When using github.com/lib/pq, you should set
+// whatsmeow.PostgresArrayWrapper = pq.Array
+var PostgresArrayWrapper func(interface{}) interface {
+ driver.Valuer
+ sql.Scanner
+}
+
+type SQLStore struct {
+ *Container
+ JID string
+
+ preKeyLock sync.Mutex
+
+ contactCache map[types.JID]*types.ContactInfo
+ contactCacheLock sync.Mutex
+}
+
+// NewSQLStore creates a new SQLStore with the given database container and user JID.
+// It contains implementations of all the different stores in the store package.
+//
+// In general, you should use Container.NewDevice or Container.GetDevice instead of this.
+func NewSQLStore(c *Container, jid types.JID) *SQLStore {
+ return &SQLStore{
+ Container: c,
+ JID: jid.String(),
+ contactCache: make(map[types.JID]*types.ContactInfo),
+ }
+}
+
+var _ store.IdentityStore = (*SQLStore)(nil)
+var _ store.SessionStore = (*SQLStore)(nil)
+var _ store.PreKeyStore = (*SQLStore)(nil)
+var _ store.SenderKeyStore = (*SQLStore)(nil)
+var _ store.AppStateSyncKeyStore = (*SQLStore)(nil)
+var _ store.AppStateStore = (*SQLStore)(nil)
+var _ store.ContactStore = (*SQLStore)(nil)
+
+const (
+ putIdentityQuery = `
+ INSERT INTO whatsmeow_identity_keys (our_jid, their_id, identity) VALUES ($1, $2, $3)
+ ON CONFLICT (our_jid, their_id) DO UPDATE SET identity=$3
+ `
+ deleteAllIdentitiesQuery = `DELETE FROM whatsmeow_identity_keys WHERE our_jid=$1 AND their_id LIKE $2`
+ deleteIdentityQuery = `DELETE FROM whatsmeow_identity_keys WHERE our_jid=$1 AND their_id=$2`
+ getIdentityQuery = `SELECT identity FROM whatsmeow_identity_keys WHERE our_jid=$1 AND their_id=$2`
+)
+
+func (s *SQLStore) PutIdentity(address string, key [32]byte) error {
+ _, err := s.db.Exec(putIdentityQuery, s.JID, address, key[:])
+ return err
+}
+
+func (s *SQLStore) DeleteAllIdentities(phone string) error {
+ _, err := s.db.Exec(deleteAllIdentitiesQuery, s.JID, phone+":%")
+ return err
+}
+
+func (s *SQLStore) DeleteIdentity(address string) error {
+ _, err := s.db.Exec(deleteAllIdentitiesQuery, s.JID, address)
+ return err
+}
+
+func (s *SQLStore) IsTrustedIdentity(address string, key [32]byte) (bool, error) {
+ var existingIdentity []byte
+ err := s.db.QueryRow(getIdentityQuery, s.JID, address).Scan(&existingIdentity)
+ if errors.Is(err, sql.ErrNoRows) {
+ // Trust if not known, it'll be saved automatically later
+ return true, nil
+ } else if err != nil {
+ return false, err
+ } else if len(existingIdentity) != 32 {
+ return false, ErrInvalidLength
+ }
+ return *(*[32]byte)(existingIdentity) == key, nil
+}
+
+const (
+ getSessionQuery = `SELECT session FROM whatsmeow_sessions WHERE our_jid=$1 AND their_id=$2`
+ hasSessionQuery = `SELECT true FROM whatsmeow_sessions WHERE our_jid=$1 AND their_id=$2`
+ putSessionQuery = `
+ INSERT INTO whatsmeow_sessions (our_jid, their_id, session) VALUES ($1, $2, $3)
+ ON CONFLICT (our_jid, their_id) DO UPDATE SET session=$3
+ `
+ deleteAllSessionsQuery = `DELETE FROM whatsmeow_sessions WHERE our_jid=$1 AND their_id LIKE $2`
+ deleteSessionQuery = `DELETE FROM whatsmeow_sessions WHERE our_jid=$1 AND their_id=$2`
+)
+
+func (s *SQLStore) GetSession(address string) (session []byte, err error) {
+ err = s.db.QueryRow(getSessionQuery, s.JID, address).Scan(&session)
+ if errors.Is(err, sql.ErrNoRows) {
+ err = nil
+ }
+ return
+}
+
+func (s *SQLStore) HasSession(address string) (has bool, err error) {
+ err = s.db.QueryRow(hasSessionQuery, s.JID, address).Scan(&has)
+ if errors.Is(err, sql.ErrNoRows) {
+ err = nil
+ }
+ return
+}
+
+func (s *SQLStore) PutSession(address string, session []byte) error {
+ _, err := s.db.Exec(putSessionQuery, s.JID, address, session)
+ return err
+}
+
+func (s *SQLStore) DeleteAllSessions(phone string) error {
+ _, err := s.db.Exec(deleteAllSessionsQuery, s.JID, phone+":%")
+ return err
+}
+
+func (s *SQLStore) DeleteSession(address string) error {
+ _, err := s.db.Exec(deleteSessionQuery, s.JID, address)
+ return err
+}
+
+const (
+ getLastPreKeyIDQuery = `SELECT MAX(key_id) FROM whatsmeow_pre_keys WHERE jid=$1`
+ insertPreKeyQuery = `INSERT INTO whatsmeow_pre_keys (jid, key_id, key, uploaded) VALUES ($1, $2, $3, $4)`
+ getUnuploadedPreKeysQuery = `SELECT key_id, key FROM whatsmeow_pre_keys WHERE jid=$1 AND uploaded=false ORDER BY key_id LIMIT $2`
+ getPreKeyQuery = `SELECT key_id, key FROM whatsmeow_pre_keys WHERE jid=$1 AND key_id=$2`
+ deletePreKeyQuery = `DELETE FROM whatsmeow_pre_keys WHERE jid=$1 AND key_id=$2`
+ markPreKeysAsUploadedQuery = `UPDATE whatsmeow_pre_keys SET uploaded=true WHERE jid=$1 AND key_id<=$2`
+ getUploadedPreKeyCountQuery = `SELECT COUNT(*) FROM whatsmeow_pre_keys WHERE jid=$1 AND uploaded=true`
+)
+
+func (s *SQLStore) genOnePreKey(id uint32, markUploaded bool) (*keys.PreKey, error) {
+ key := keys.NewPreKey(id)
+ _, err := s.db.Exec(insertPreKeyQuery, s.JID, key.KeyID, key.Priv[:], markUploaded)
+ return key, err
+}
+
+func (s *SQLStore) getNextPreKeyID() (uint32, error) {
+ var lastKeyID sql.NullInt32
+ err := s.db.QueryRow(getLastPreKeyIDQuery, s.JID).Scan(&lastKeyID)
+ if err != nil {
+ return 0, fmt.Errorf("failed to query next prekey ID: %w", err)
+ }
+ return uint32(lastKeyID.Int32) + 1, nil
+}
+
+func (s *SQLStore) GenOnePreKey() (*keys.PreKey, error) {
+ s.preKeyLock.Lock()
+ defer s.preKeyLock.Unlock()
+ nextKeyID, err := s.getNextPreKeyID()
+ if err != nil {
+ return nil, err
+ }
+ return s.genOnePreKey(nextKeyID, true)
+}
+
+func (s *SQLStore) GetOrGenPreKeys(count uint32) ([]*keys.PreKey, error) {
+ s.preKeyLock.Lock()
+ defer s.preKeyLock.Unlock()
+
+ res, err := s.db.Query(getUnuploadedPreKeysQuery, s.JID, count)
+ if err != nil {
+ return nil, fmt.Errorf("failed to query existing prekeys: %w", err)
+ }
+ newKeys := make([]*keys.PreKey, count)
+ var existingCount uint32
+ for res.Next() {
+ var key *keys.PreKey
+ key, err = scanPreKey(res)
+ if err != nil {
+ return nil, err
+ } else if key != nil {
+ newKeys[existingCount] = key
+ existingCount++
+ }
+ }
+
+ if existingCount < uint32(len(newKeys)) {
+ var nextKeyID uint32
+ nextKeyID, err = s.getNextPreKeyID()
+ if err != nil {
+ return nil, err
+ }
+ for i := existingCount; i < count; i++ {
+ newKeys[i], err = s.genOnePreKey(nextKeyID, false)
+ if err != nil {
+ return nil, fmt.Errorf("failed to generate prekey: %w", err)
+ }
+ nextKeyID++
+ }
+ }
+
+ return newKeys, nil
+}
+
+func scanPreKey(row scannable) (*keys.PreKey, error) {
+ var priv []byte
+ var id uint32
+ err := row.Scan(&id, &priv)
+ if errors.Is(err, sql.ErrNoRows) {
+ return nil, nil
+ } else if err != nil {
+ return nil, err
+ } else if len(priv) != 32 {
+ return nil, ErrInvalidLength
+ }
+ return &keys.PreKey{
+ KeyPair: *keys.NewKeyPairFromPrivateKey(*(*[32]byte)(priv)),
+ KeyID: id,
+ }, nil
+}
+
+func (s *SQLStore) GetPreKey(id uint32) (*keys.PreKey, error) {
+ return scanPreKey(s.db.QueryRow(getPreKeyQuery, s.JID, id))
+}
+
+func (s *SQLStore) RemovePreKey(id uint32) error {
+ _, err := s.db.Exec(deletePreKeyQuery, s.JID, id)
+ return err
+}
+
+func (s *SQLStore) MarkPreKeysAsUploaded(upToID uint32) error {
+ _, err := s.db.Exec(markPreKeysAsUploadedQuery, s.JID, upToID)
+ return err
+}
+
+func (s *SQLStore) UploadedPreKeyCount() (count int, err error) {
+ err = s.db.QueryRow(getUploadedPreKeyCountQuery, s.JID).Scan(&count)
+ return
+}
+
+const (
+ getSenderKeyQuery = `SELECT sender_key FROM whatsmeow_sender_keys WHERE our_jid=$1 AND chat_id=$2 AND sender_id=$3`
+ putSenderKeyQuery = `
+ INSERT INTO whatsmeow_sender_keys (our_jid, chat_id, sender_id, sender_key) VALUES ($1, $2, $3, $4)
+ ON CONFLICT (our_jid, chat_id, sender_id) DO UPDATE SET sender_key=$4
+ `
+)
+
+func (s *SQLStore) PutSenderKey(group, user string, session []byte) error {
+ _, err := s.db.Exec(putSenderKeyQuery, s.JID, group, user, session)
+ return err
+}
+
+func (s *SQLStore) GetSenderKey(group, user string) (key []byte, err error) {
+ err = s.db.QueryRow(getSenderKeyQuery, s.JID, group, user).Scan(&key)
+ if errors.Is(err, sql.ErrNoRows) {
+ err = nil
+ }
+ return
+}
+
+const (
+ putAppStateSyncKeyQuery = `
+ INSERT INTO whatsmeow_app_state_sync_keys (jid, key_id, key_data, timestamp, fingerprint) VALUES ($1, $2, $3, $4, $5)
+ ON CONFLICT (jid, key_id) DO UPDATE SET key_data=$3, timestamp=$4, fingerprint=$5
+ `
+ getAppStateSyncKeyQuery = `SELECT key_data, timestamp, fingerprint FROM whatsmeow_app_state_sync_keys WHERE jid=$1 AND key_id=$2`
+)
+
+func (s *SQLStore) PutAppStateSyncKey(id []byte, key store.AppStateSyncKey) error {
+ _, err := s.db.Exec(putAppStateSyncKeyQuery, s.JID, id, key.Data, key.Timestamp, key.Fingerprint)
+ return err
+}
+
+func (s *SQLStore) GetAppStateSyncKey(id []byte) (*store.AppStateSyncKey, error) {
+ var key store.AppStateSyncKey
+ err := s.db.QueryRow(getAppStateSyncKeyQuery, s.JID, id).Scan(&key.Data, &key.Timestamp, &key.Fingerprint)
+ if errors.Is(err, sql.ErrNoRows) {
+ return nil, nil
+ }
+ return &key, err
+}
+
+const (
+ putAppStateVersionQuery = `
+ INSERT INTO whatsmeow_app_state_version (jid, name, version, hash) VALUES ($1, $2, $3, $4)
+ ON CONFLICT (jid, name) DO UPDATE SET version=$3, hash=$4
+ `
+ getAppStateVersionQuery = `SELECT version, hash FROM whatsmeow_app_state_version WHERE jid=$1 AND name=$2`
+ deleteAppStateVersionQuery = `DELETE FROM whatsmeow_app_state_version WHERE jid=$1 AND name=$2`
+ putAppStateMutationMACsQuery = `INSERT INTO whatsmeow_app_state_mutation_macs (jid, name, version, index_mac, value_mac) VALUES `
+ deleteAppStateMutationMACsQueryPostgres = `DELETE FROM whatsmeow_app_state_mutation_macs WHERE jid=$1 AND name=$2 AND index_mac=ANY($3::bytea[])`
+ deleteAppStateMutationMACsQueryGeneric = `DELETE FROM whatsmeow_app_state_mutation_macs WHERE jid=$1 AND name=$2 AND index_mac IN `
+ getAppStateMutationMACQuery = `SELECT value_mac FROM whatsmeow_app_state_mutation_macs WHERE jid=$1 AND name=$2 AND index_mac=$3 ORDER BY version DESC LIMIT 1`
+)
+
+func (s *SQLStore) PutAppStateVersion(name string, version uint64, hash [128]byte) error {
+ _, err := s.db.Exec(putAppStateVersionQuery, s.JID, name, version, hash[:])
+ return err
+}
+
+func (s *SQLStore) GetAppStateVersion(name string) (version uint64, hash [128]byte, err error) {
+ var uncheckedHash []byte
+ err = s.db.QueryRow(getAppStateVersionQuery, s.JID, name).Scan(&version, &uncheckedHash)
+ if errors.Is(err, sql.ErrNoRows) {
+ // version will be 0 and hash will be an empty array, which is the correct initial state
+ err = nil
+ } else if err != nil {
+ // There's an error, just return it
+ } else if len(uncheckedHash) != 128 {
+ // This shouldn't happen
+ err = ErrInvalidLength
+ } else {
+ // No errors, convert hash slice to array
+ hash = *(*[128]byte)(uncheckedHash)
+ }
+ return
+}
+
+func (s *SQLStore) DeleteAppStateVersion(name string) error {
+ _, err := s.db.Exec(deleteAppStateVersionQuery, s.JID, name)
+ return err
+}
+
+type execable interface {
+ Exec(query string, args ...interface{}) (sql.Result, error)
+}
+
+func (s *SQLStore) putAppStateMutationMACs(tx execable, name string, version uint64, mutations []store.AppStateMutationMAC) error {
+ values := make([]interface{}, 3+len(mutations)*2)
+ queryParts := make([]string, len(mutations))
+ values[0] = s.JID
+ values[1] = name
+ values[2] = version
+ for i, mutation := range mutations {
+ baseIndex := 3 + i*2
+ values[baseIndex] = mutation.IndexMAC
+ values[baseIndex+1] = mutation.ValueMAC
+ if s.dialect == "sqlite3" {
+ queryParts[i] = fmt.Sprintf("(?1, ?2, ?3, ?%d, ?%d)", baseIndex+1, baseIndex+2)
+ } else {
+ queryParts[i] = fmt.Sprintf("($1, $2, $3, $%d, $%d)", baseIndex+1, baseIndex+2)
+ }
+ }
+ _, err := tx.Exec(putAppStateMutationMACsQuery+strings.Join(queryParts, ","), values...)
+ return err
+}
+
+const mutationBatchSize = 400
+
+func (s *SQLStore) PutAppStateMutationMACs(name string, version uint64, mutations []store.AppStateMutationMAC) error {
+ if len(mutations) > mutationBatchSize {
+ tx, err := s.db.Begin()
+ if err != nil {
+ return fmt.Errorf("failed to start transaction: %w", err)
+ }
+ for i := 0; i < len(mutations); i += mutationBatchSize {
+ var mutationSlice []store.AppStateMutationMAC
+ if len(mutations) > i+mutationBatchSize {
+ mutationSlice = mutations[i : i+mutationBatchSize]
+ } else {
+ mutationSlice = mutations[i:]
+ }
+ err = s.putAppStateMutationMACs(tx, name, version, mutationSlice)
+ if err != nil {
+ _ = tx.Rollback()
+ return err
+ }
+ }
+ err = tx.Commit()
+ if err != nil {
+ return fmt.Errorf("failed to commit transaction: %w", err)
+ }
+ return nil
+ } else if len(mutations) > 0 {
+ return s.putAppStateMutationMACs(s.db, name, version, mutations)
+ }
+ return nil
+}
+
+func (s *SQLStore) DeleteAppStateMutationMACs(name string, indexMACs [][]byte) (err error) {
+ if len(indexMACs) == 0 {
+ return
+ }
+ if s.dialect == "postgres" && PostgresArrayWrapper != nil {
+ _, err = s.db.Exec(deleteAppStateMutationMACsQueryPostgres, s.JID, name, PostgresArrayWrapper(indexMACs))
+ } else {
+ args := make([]interface{}, 2+len(indexMACs))
+ args[0] = s.JID
+ args[1] = name
+ queryParts := make([]string, len(indexMACs))
+ for i, item := range indexMACs {
+ args[2+i] = item
+ queryParts[i] = fmt.Sprintf("$%d", i+3)
+ }
+ _, err = s.db.Exec(deleteAppStateMutationMACsQueryGeneric+"("+strings.Join(queryParts, ",")+")", args...)
+ }
+ return
+}
+
+func (s *SQLStore) GetAppStateMutationMAC(name string, indexMAC []byte) (valueMAC []byte, err error) {
+ err = s.db.QueryRow(getAppStateMutationMACQuery, s.JID, name, indexMAC).Scan(&valueMAC)
+ if errors.Is(err, sql.ErrNoRows) {
+ err = nil
+ }
+ return
+}
+
+const (
+ putContactNameQuery = `
+ INSERT INTO whatsmeow_contacts (our_jid, their_jid, first_name, full_name) VALUES ($1, $2, $3, $4)
+ ON CONFLICT (our_jid, their_jid) DO UPDATE SET first_name=$3, full_name=$4
+ `
+ putPushNameQuery = `
+ INSERT INTO whatsmeow_contacts (our_jid, their_jid, push_name) VALUES ($1, $2, $3)
+ ON CONFLICT (our_jid, their_jid) DO UPDATE SET push_name=$3
+ `
+ putBusinessNameQuery = `
+ INSERT INTO whatsmeow_contacts (our_jid, their_jid, business_name) VALUES ($1, $2, $3)
+ ON CONFLICT (our_jid, their_jid) DO UPDATE SET business_name=$3
+ `
+ getContactQuery = `
+ SELECT first_name, full_name, push_name, business_name FROM whatsmeow_contacts WHERE our_jid=$1 AND their_jid=$2
+ `
+ getAllContactsQuery = `
+ SELECT their_jid, first_name, full_name, push_name, business_name FROM whatsmeow_contacts WHERE our_jid=$1
+ `
+)
+
+func (s *SQLStore) PutPushName(user types.JID, pushName string) (bool, string, error) {
+ s.contactCacheLock.Lock()
+ defer s.contactCacheLock.Unlock()
+
+ cached, err := s.getContact(user)
+ if err != nil {
+ return false, "", err
+ }
+ if cached.PushName != pushName {
+ _, err = s.db.Exec(putPushNameQuery, s.JID, user, pushName)
+ if err != nil {
+ return false, "", err
+ }
+ previousName := cached.PushName
+ cached.PushName = pushName
+ cached.Found = true
+ return true, previousName, nil
+ }
+ return false, "", nil
+}
+
+func (s *SQLStore) PutBusinessName(user types.JID, businessName string) error {
+ s.contactCacheLock.Lock()
+ defer s.contactCacheLock.Unlock()
+
+ cached, err := s.getContact(user)
+ if err != nil {
+ return err
+ }
+ if cached.BusinessName != businessName {
+ _, err = s.db.Exec(putBusinessNameQuery, s.JID, user, businessName)
+ if err != nil {
+ return err
+ }
+ cached.BusinessName = businessName
+ cached.Found = true
+ }
+ return nil
+}
+
+func (s *SQLStore) PutContactName(user types.JID, firstName, fullName string) error {
+ s.contactCacheLock.Lock()
+ defer s.contactCacheLock.Unlock()
+
+ cached, err := s.getContact(user)
+ if err != nil {
+ return err
+ }
+ if cached.FirstName != firstName || cached.FullName != fullName {
+ _, err = s.db.Exec(putContactNameQuery, s.JID, user, firstName, fullName)
+ if err != nil {
+ return err
+ }
+ cached.FirstName = firstName
+ cached.FullName = fullName
+ cached.Found = true
+ }
+ return nil
+}
+
+func (s *SQLStore) getContact(user types.JID) (*types.ContactInfo, error) {
+ cached, ok := s.contactCache[user]
+ if ok {
+ return cached, nil
+ }
+
+ var first, full, push, business sql.NullString
+ err := s.db.QueryRow(getContactQuery, s.JID, user).Scan(&first, &full, &push, &business)
+ if err != nil && !errors.Is(err, sql.ErrNoRows) {
+ return nil, err
+ }
+ info := &types.ContactInfo{
+ Found: err == nil,
+ FirstName: first.String,
+ FullName: full.String,
+ PushName: push.String,
+ BusinessName: business.String,
+ }
+ s.contactCache[user] = info
+ return info, nil
+}
+
+func (s *SQLStore) GetContact(user types.JID) (types.ContactInfo, error) {
+ s.contactCacheLock.Lock()
+ info, err := s.getContact(user)
+ s.contactCacheLock.Unlock()
+ if err != nil {
+ return types.ContactInfo{}, err
+ }
+ return *info, nil
+}
+
+func (s *SQLStore) GetAllContacts() (map[types.JID]types.ContactInfo, error) {
+ s.contactCacheLock.Lock()
+ defer s.contactCacheLock.Unlock()
+ rows, err := s.db.Query(getAllContactsQuery, s.JID)
+ if err != nil {
+ return nil, err
+ }
+ output := make(map[types.JID]types.ContactInfo, len(s.contactCache))
+ for rows.Next() {
+ var jid types.JID
+ var first, full, push, business sql.NullString
+ err = rows.Scan(&jid, &first, &full, &push, &business)
+ if err != nil {
+ return nil, fmt.Errorf("error scanning row: %w", err)
+ }
+ info := types.ContactInfo{
+ Found: true,
+ FirstName: first.String,
+ FullName: full.String,
+ PushName: push.String,
+ BusinessName: business.String,
+ }
+ output[jid] = info
+ s.contactCache[jid] = &info
+ }
+ return output, nil
+}
+
+const (
+ putChatSettingQuery = `
+ INSERT INTO whatsmeow_chat_settings (our_jid, chat_jid, %[1]s) VALUES ($1, $2, $3)
+ ON CONFLICT (our_jid, chat_jid) DO UPDATE SET %[1]s=$3
+ `
+ getChatSettingsQuery = `
+ SELECT muted_until, pinned, archived FROM whatsmeow_chat_settings WHERE our_jid=$1 AND chat_jid=$2
+ `
+)
+
+func (s *SQLStore) PutMutedUntil(chat types.JID, mutedUntil time.Time) error {
+ var val int64
+ if !mutedUntil.IsZero() {
+ val = mutedUntil.Unix()
+ }
+ _, err := s.db.Exec(fmt.Sprintf(putChatSettingQuery, "muted_until"), s.JID, chat, val)
+ return err
+}
+
+func (s *SQLStore) PutPinned(chat types.JID, pinned bool) error {
+ _, err := s.db.Exec(fmt.Sprintf(putChatSettingQuery, "pinned"), s.JID, chat, pinned)
+ return err
+}
+
+func (s *SQLStore) PutArchived(chat types.JID, archived bool) error {
+ _, err := s.db.Exec(fmt.Sprintf(putChatSettingQuery, "archived"), s.JID, chat, archived)
+ return err
+}
+
+func (s *SQLStore) GetChatSettings(chat types.JID) (settings types.LocalChatSettings, err error) {
+ var mutedUntil int64
+ err = s.db.QueryRow(getChatSettingsQuery, s.JID, chat).Scan(&mutedUntil, &settings.Pinned, &settings.Archived)
+ if errors.Is(err, sql.ErrNoRows) {
+ err = nil
+ } else if err != nil {
+ return
+ } else {
+ settings.Found = true
+ }
+ if mutedUntil != 0 {
+ settings.MutedUntil = time.Unix(mutedUntil, 0)
+ }
+ return
+}
diff --git a/vendor/go.mau.fi/whatsmeow/store/sqlstore/upgrade.go b/vendor/go.mau.fi/whatsmeow/store/sqlstore/upgrade.go
new file mode 100644
index 00000000..b98f2d61
--- /dev/null
+++ b/vendor/go.mau.fi/whatsmeow/store/sqlstore/upgrade.go
@@ -0,0 +1,214 @@
+// 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 sqlstore
+
+import (
+ "database/sql"
+)
+
+type upgradeFunc func(*sql.Tx, *Container) error
+
+// Upgrades is a list of functions that will upgrade a database to the latest version.
+//
+// This may be of use if you want to manage the database fully manually, but in most cases you
+// should just call Container.Upgrade to let the library handle everything.
+var Upgrades = [...]upgradeFunc{upgradeV1}
+
+func (c *Container) getVersion() (int, error) {
+ _, err := c.db.Exec("CREATE TABLE IF NOT EXISTS whatsmeow_version (version INTEGER)")
+ if err != nil {
+ return -1, err
+ }
+
+ version := 0
+ row := c.db.QueryRow("SELECT version FROM whatsmeow_version LIMIT 1")
+ if row != nil {
+ _ = row.Scan(&version)
+ }
+ return version, nil
+}
+
+func (c *Container) setVersion(tx *sql.Tx, version int) error {
+ _, err := tx.Exec("DELETE FROM whatsmeow_version")
+ if err != nil {
+ return err
+ }
+ _, err = tx.Exec("INSERT INTO whatsmeow_version (version) VALUES ($1)", version)
+ return err
+}
+
+// Upgrade upgrades the database from the current to the latest version available.
+func (c *Container) Upgrade() error {
+ version, err := c.getVersion()
+ if err != nil {
+ return err
+ }
+
+ for ; version < len(Upgrades); version++ {
+ var tx *sql.Tx
+ tx, err = c.db.Begin()
+ if err != nil {
+ return err
+ }
+
+ migrateFunc := Upgrades[version]
+ err = migrateFunc(tx, c)
+ if err != nil {
+ _ = tx.Rollback()
+ return err
+ }
+
+ if err = c.setVersion(tx, version+1); err != nil {
+ return err
+ }
+
+ if err = tx.Commit(); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func upgradeV1(tx *sql.Tx, _ *Container) error {
+ _, err := tx.Exec(`CREATE TABLE whatsmeow_device (
+ jid TEXT PRIMARY KEY,
+
+ registration_id BIGINT NOT NULL CHECK ( registration_id >= 0 AND registration_id < 4294967296 ),
+
+ noise_key bytea NOT NULL CHECK ( length(noise_key) = 32 ),
+ identity_key bytea NOT NULL CHECK ( length(identity_key) = 32 ),
+
+ signed_pre_key bytea NOT NULL CHECK ( length(signed_pre_key) = 32 ),
+ signed_pre_key_id INTEGER NOT NULL CHECK ( signed_pre_key_id >= 0 AND signed_pre_key_id < 16777216 ),
+ signed_pre_key_sig bytea NOT NULL CHECK ( length(signed_pre_key_sig) = 64 ),
+
+ adv_key bytea NOT NULL,
+ adv_details bytea NOT NULL,
+ adv_account_sig bytea NOT NULL CHECK ( length(adv_account_sig) = 64 ),
+ adv_device_sig bytea NOT NULL CHECK ( length(adv_device_sig) = 64 ),
+
+ platform TEXT NOT NULL DEFAULT '',
+ business_name TEXT NOT NULL DEFAULT '',
+ push_name TEXT NOT NULL DEFAULT ''
+ )`)
+ if err != nil {
+ return err
+ }
+ _, err = tx.Exec(`CREATE TABLE whatsmeow_identity_keys (
+ our_jid TEXT,
+ their_id TEXT,
+ identity bytea NOT NULL CHECK ( length(identity) = 32 ),
+
+ PRIMARY KEY (our_jid, their_id),
+ FOREIGN KEY (our_jid) REFERENCES whatsmeow_device(jid) ON DELETE CASCADE ON UPDATE CASCADE
+ )`)
+ if err != nil {
+ return err
+ }
+ _, err = tx.Exec(`CREATE TABLE whatsmeow_pre_keys (
+ jid TEXT,
+ key_id INTEGER CHECK ( key_id >= 0 AND key_id < 16777216 ),
+ key bytea NOT NULL CHECK ( length(key) = 32 ),
+ uploaded BOOLEAN NOT NULL,
+
+ PRIMARY KEY (jid, key_id),
+ FOREIGN KEY (jid) REFERENCES whatsmeow_device(jid) ON DELETE CASCADE ON UPDATE CASCADE
+ )`)
+ if err != nil {
+ return err
+ }
+ _, err = tx.Exec(`CREATE TABLE whatsmeow_sessions (
+ our_jid TEXT,
+ their_id TEXT,
+ session bytea,
+
+ PRIMARY KEY (our_jid, their_id),
+ FOREIGN KEY (our_jid) REFERENCES whatsmeow_device(jid) ON DELETE CASCADE ON UPDATE CASCADE
+ )`)
+ if err != nil {
+ return err
+ }
+ _, err = tx.Exec(`CREATE TABLE whatsmeow_sender_keys (
+ our_jid TEXT,
+ chat_id TEXT,
+ sender_id TEXT,
+ sender_key bytea NOT NULL,
+
+ PRIMARY KEY (our_jid, chat_id, sender_id),
+ FOREIGN KEY (our_jid) REFERENCES whatsmeow_device(jid) ON DELETE CASCADE ON UPDATE CASCADE
+ )`)
+ if err != nil {
+ return err
+ }
+ _, err = tx.Exec(`CREATE TABLE whatsmeow_app_state_sync_keys (
+ jid TEXT,
+ key_id bytea,
+ key_data bytea NOT NULL,
+ timestamp BIGINT NOT NULL,
+ fingerprint bytea NOT NULL,
+
+ PRIMARY KEY (jid, key_id),
+ FOREIGN KEY (jid) REFERENCES whatsmeow_device(jid) ON DELETE CASCADE ON UPDATE CASCADE
+ )`)
+ if err != nil {
+ return err
+ }
+ _, err = tx.Exec(`CREATE TABLE whatsmeow_app_state_version (
+ jid TEXT,
+ name TEXT,
+ version BIGINT NOT NULL,
+ hash bytea NOT NULL CHECK ( length(hash) = 128 ),
+
+ PRIMARY KEY (jid, name),
+ FOREIGN KEY (jid) REFERENCES whatsmeow_device(jid) ON DELETE CASCADE ON UPDATE CASCADE
+ )`)
+ if err != nil {
+ return err
+ }
+ _, err = tx.Exec(`CREATE TABLE whatsmeow_app_state_mutation_macs (
+ jid TEXT,
+ name TEXT,
+ version BIGINT,
+ index_mac bytea CHECK ( length(index_mac) = 32 ),
+ value_mac bytea NOT NULL CHECK ( length(value_mac) = 32 ),
+
+ PRIMARY KEY (jid, name, version, index_mac),
+ FOREIGN KEY (jid, name) REFERENCES whatsmeow_app_state_version(jid, name) ON DELETE CASCADE ON UPDATE CASCADE
+ )`)
+ if err != nil {
+ return err
+ }
+ _, err = tx.Exec(`CREATE TABLE whatsmeow_contacts (
+ our_jid TEXT,
+ their_jid TEXT,
+ first_name TEXT,
+ full_name TEXT,
+ push_name TEXT,
+ business_name TEXT,
+
+ PRIMARY KEY (our_jid, their_jid),
+ FOREIGN KEY (our_jid) REFERENCES whatsmeow_device(jid) ON DELETE CASCADE ON UPDATE CASCADE
+ )`)
+ if err != nil {
+ return err
+ }
+ _, err = tx.Exec(`CREATE TABLE whatsmeow_chat_settings (
+ our_jid TEXT,
+ chat_jid TEXT,
+ muted_until BIGINT NOT NULL DEFAULT 0,
+ pinned BOOLEAN NOT NULL DEFAULT false,
+ archived BOOLEAN NOT NULL DEFAULT false,
+
+ PRIMARY KEY (our_jid, chat_jid),
+ FOREIGN KEY (our_jid) REFERENCES whatsmeow_device(jid) ON DELETE CASCADE ON UPDATE CASCADE
+ )`)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/vendor/go.mau.fi/whatsmeow/store/store.go b/vendor/go.mau.fi/whatsmeow/store/store.go
new file mode 100644
index 00000000..65624b9f
--- /dev/null
+++ b/vendor/go.mau.fi/whatsmeow/store/store.go
@@ -0,0 +1,132 @@
+// 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 store contains interfaces for storing data needed for WhatsApp multidevice.
+package store
+
+import (
+ "time"
+
+ waProto "go.mau.fi/whatsmeow/binary/proto"
+ "go.mau.fi/whatsmeow/types"
+ "go.mau.fi/whatsmeow/util/keys"
+ waLog "go.mau.fi/whatsmeow/util/log"
+)
+
+type IdentityStore interface {
+ PutIdentity(address string, key [32]byte) error
+ DeleteAllIdentities(phone string) error
+ DeleteIdentity(address string) error
+ IsTrustedIdentity(address string, key [32]byte) (bool, error)
+}
+
+type SessionStore interface {
+ GetSession(address string) ([]byte, error)
+ HasSession(address string) (bool, error)
+ PutSession(address string, session []byte) error
+ DeleteAllSessions(phone string) error
+ DeleteSession(address string) error
+}
+
+type PreKeyStore interface {
+ GetOrGenPreKeys(count uint32) ([]*keys.PreKey, error)
+ GenOnePreKey() (*keys.PreKey, error)
+ GetPreKey(id uint32) (*keys.PreKey, error)
+ RemovePreKey(id uint32) error
+ MarkPreKeysAsUploaded(upToID uint32) error
+ UploadedPreKeyCount() (int, error)
+}
+
+type SenderKeyStore interface {
+ PutSenderKey(group, user string, session []byte) error
+ GetSenderKey(group, user string) ([]byte, error)
+}
+
+type AppStateSyncKey struct {
+ Data []byte
+ Fingerprint []byte
+ Timestamp int64
+}
+
+type AppStateSyncKeyStore interface {
+ PutAppStateSyncKey(id []byte, key AppStateSyncKey) error
+ GetAppStateSyncKey(id []byte) (*AppStateSyncKey, error)
+}
+
+type AppStateMutationMAC struct {
+ IndexMAC []byte
+ ValueMAC []byte
+}
+
+type AppStateStore interface {
+ PutAppStateVersion(name string, version uint64, hash [128]byte) error
+ GetAppStateVersion(name string) (uint64, [128]byte, error)
+ DeleteAppStateVersion(name string) error
+
+ PutAppStateMutationMACs(name string, version uint64, mutations []AppStateMutationMAC) error
+ DeleteAppStateMutationMACs(name string, indexMACs [][]byte) error
+ GetAppStateMutationMAC(name string, indexMAC []byte) (valueMAC []byte, err error)
+}
+
+type ContactStore interface {
+ PutPushName(user types.JID, pushName string) (bool, string, error)
+ PutBusinessName(user types.JID, businessName string) error
+ PutContactName(user types.JID, fullName, firstName string) error
+ GetContact(user types.JID) (types.ContactInfo, error)
+ GetAllContacts() (map[types.JID]types.ContactInfo, error)
+}
+
+type ChatSettingsStore interface {
+ PutMutedUntil(chat types.JID, mutedUntil time.Time) error
+ PutPinned(chat types.JID, pinned bool) error
+ PutArchived(chat types.JID, archived bool) error
+ GetChatSettings(chat types.JID) (types.LocalChatSettings, error)
+}
+
+type DeviceContainer interface {
+ PutDevice(store *Device) error
+ DeleteDevice(store *Device) error
+}
+
+type Device struct {
+ Log waLog.Logger
+
+ NoiseKey *keys.KeyPair
+ IdentityKey *keys.KeyPair
+ SignedPreKey *keys.PreKey
+ RegistrationID uint32
+ AdvSecretKey []byte
+
+ ID *types.JID
+ Account *waProto.ADVSignedDeviceIdentity
+ Platform string
+ BusinessName string
+ PushName string
+
+ Initialized bool
+ Identities IdentityStore
+ Sessions SessionStore
+ PreKeys PreKeyStore
+ SenderKeys SenderKeyStore
+ AppStateKeys AppStateSyncKeyStore
+ AppState AppStateStore
+ Contacts ContactStore
+ ChatSettings ChatSettingsStore
+ Container DeviceContainer
+}
+
+func (device *Device) Save() error {
+ return device.Container.PutDevice(device)
+}
+
+func (device *Device) Delete() error {
+ err := device.Container.DeleteDevice(device)
+ if err != nil {
+ return err
+ }
+ device.ID = nil
+ return nil
+}