container.go 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. // Copyright (c) 2022 Tulir Asokan
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. package sqlstore
  7. import (
  8. "context"
  9. "database/sql"
  10. "errors"
  11. "fmt"
  12. mathRand "math/rand/v2"
  13. "github.com/google/uuid"
  14. "go.mau.fi/util/dbutil"
  15. "go.mau.fi/util/random"
  16. "git.bobomao.top/joey/testwh/proto/waAdv"
  17. "git.bobomao.top/joey/testwh/store"
  18. "git.bobomao.top/joey/testwh/store/sqlstore/upgrades"
  19. "git.bobomao.top/joey/testwh/types"
  20. "git.bobomao.top/joey/testwh/util/keys"
  21. waLog "git.bobomao.top/joey/testwh/util/log"
  22. )
  23. // Container is a wrapper for a SQL database that can contain multiple whatsmeow sessions.
  24. type Container struct {
  25. db *dbutil.Database
  26. log waLog.Logger
  27. LIDMap *CachedLIDMap
  28. }
  29. var _ store.DeviceContainer = (*Container)(nil)
  30. // New connects to the given SQL database and wraps it in a Container.
  31. //
  32. // Only SQLite and Postgres are currently fully supported.
  33. //
  34. // The logger can be nil and will default to a no-op logger.
  35. //
  36. // When using SQLite, it's strongly recommended to enable foreign keys by adding `?_foreign_keys=true`:
  37. //
  38. // container, err := sqlstore.New(context.Background(), "sqlite3", "file:yoursqlitefile.db?_foreign_keys=on", nil)
  39. func New(ctx context.Context, dialect, address string, log waLog.Logger) (*Container, error) {
  40. db, err := sql.Open(dialect, address)
  41. if err != nil {
  42. return nil, fmt.Errorf("failed to open database: %w", err)
  43. }
  44. container := NewWithDB(db, dialect, log)
  45. err = container.Upgrade(ctx)
  46. if err != nil {
  47. return nil, fmt.Errorf("failed to upgrade database: %w", err)
  48. }
  49. return container, nil
  50. }
  51. // NewWithDB wraps an existing SQL connection in a Container.
  52. //
  53. // Only SQLite and Postgres are currently fully supported.
  54. //
  55. // The logger can be nil and will default to a no-op logger.
  56. //
  57. // When using SQLite, it's strongly recommended to enable foreign keys by adding `?_foreign_keys=true`:
  58. //
  59. // db, err := sql.Open("sqlite3", "file:yoursqlitefile.db?_foreign_keys=on")
  60. // if err != nil {
  61. // panic(err)
  62. // }
  63. // container := sqlstore.NewWithDB(db, "sqlite3", nil)
  64. //
  65. // This method does not call Upgrade automatically like New does, so you must call it yourself:
  66. //
  67. // container := sqlstore.NewWithDB(...)
  68. // err := container.Upgrade()
  69. func NewWithDB(db *sql.DB, dialect string, log waLog.Logger) *Container {
  70. wrapped, err := dbutil.NewWithDB(db, dialect)
  71. if err != nil {
  72. // This will only panic if the dialect is invalid
  73. panic(err)
  74. }
  75. wrapped.UpgradeTable = upgrades.Table
  76. wrapped.VersionTable = "whatsmeow_version"
  77. return NewWithWrappedDB(wrapped, log)
  78. }
  79. func NewWithWrappedDB(wrapped *dbutil.Database, log waLog.Logger) *Container {
  80. if log == nil {
  81. log = waLog.Noop
  82. }
  83. return &Container{
  84. db: wrapped,
  85. log: log,
  86. LIDMap: NewCachedLIDMap(wrapped),
  87. }
  88. }
  89. // Upgrade upgrades the database from the current to the latest version available.
  90. func (c *Container) Upgrade(ctx context.Context) error {
  91. if c.db.Dialect == dbutil.SQLite {
  92. var foreignKeysEnabled bool
  93. err := c.db.QueryRow(ctx, "PRAGMA foreign_keys").Scan(&foreignKeysEnabled)
  94. if err != nil {
  95. return fmt.Errorf("failed to check if foreign keys are enabled: %w", err)
  96. } else if !foreignKeysEnabled {
  97. return fmt.Errorf("foreign keys are not enabled")
  98. }
  99. }
  100. return c.db.Upgrade(ctx)
  101. }
  102. const getAllDevicesQuery = `
  103. SELECT jid, lid, registration_id, noise_key, identity_key,
  104. signed_pre_key, signed_pre_key_id, signed_pre_key_sig,
  105. adv_key, adv_details, adv_account_sig, adv_account_sig_key, adv_device_sig,
  106. platform, business_name, push_name, facebook_uuid, lid_migration_ts
  107. FROM whatsmeow_device
  108. `
  109. const getDeviceQuery = getAllDevicesQuery + " WHERE jid=$1"
  110. func (c *Container) scanDevice(row dbutil.Scannable) (*store.Device, error) {
  111. var device store.Device
  112. device.Log = c.log
  113. device.SignedPreKey = &keys.PreKey{}
  114. var noisePriv, identityPriv, preKeyPriv, preKeySig []byte
  115. var account waAdv.ADVSignedDeviceIdentity
  116. var fbUUID uuid.NullUUID
  117. err := row.Scan(
  118. &device.ID, &device.LID, &device.RegistrationID, &noisePriv, &identityPriv,
  119. &preKeyPriv, &device.SignedPreKey.KeyID, &preKeySig,
  120. &device.AdvSecretKey, &account.Details, &account.AccountSignature, &account.AccountSignatureKey, &account.DeviceSignature,
  121. &device.Platform, &device.BusinessName, &device.PushName, &fbUUID, &device.LIDMigrationTimestamp, &device.Mobile, &device.MobileInfo)
  122. if err != nil {
  123. return nil, fmt.Errorf("failed to scan session: %w", err)
  124. } else if len(noisePriv) != 32 || len(identityPriv) != 32 || len(preKeyPriv) != 32 || len(preKeySig) != 64 {
  125. return nil, ErrInvalidLength
  126. }
  127. device.NoiseKey = keys.NewKeyPairFromPrivateKey(*(*[32]byte)(noisePriv))
  128. device.IdentityKey = keys.NewKeyPairFromPrivateKey(*(*[32]byte)(identityPriv))
  129. device.SignedPreKey.KeyPair = *keys.NewKeyPairFromPrivateKey(*(*[32]byte)(preKeyPriv))
  130. device.SignedPreKey.Signature = (*[64]byte)(preKeySig)
  131. device.Account = &account
  132. device.FacebookUUID = fbUUID.UUID
  133. c.initializeDevice(&device)
  134. return &device, nil
  135. }
  136. // GetAllDevices finds all the devices in the database.
  137. func (c *Container) GetAllDevices(ctx context.Context) ([]*store.Device, error) {
  138. res, err := c.db.Query(ctx, getAllDevicesQuery)
  139. if err != nil {
  140. return nil, fmt.Errorf("failed to query sessions: %w", err)
  141. }
  142. sessions := make([]*store.Device, 0)
  143. for res.Next() {
  144. sess, scanErr := c.scanDevice(res)
  145. if scanErr != nil {
  146. return sessions, scanErr
  147. }
  148. sessions = append(sessions, sess)
  149. }
  150. return sessions, nil
  151. }
  152. // GetFirstDevice is a convenience method for getting the first device in the store. If there are
  153. // no devices, then a new device will be created. You should only use this if you don't want to
  154. // have multiple sessions simultaneously.
  155. func (c *Container) GetFirstDevice(ctx context.Context) (*store.Device, error) {
  156. devices, err := c.GetAllDevices(ctx)
  157. if err != nil {
  158. return nil, err
  159. }
  160. if len(devices) == 0 {
  161. return c.NewDevice(), nil
  162. } else {
  163. return devices[0], nil
  164. }
  165. }
  166. // GetDevice finds the device with the specified JID in the database.
  167. //
  168. // If the device is not found, nil is returned instead.
  169. //
  170. // Note that the parameter usually must be an AD-JID.
  171. func (c *Container) GetDevice(ctx context.Context, jid types.JID) (*store.Device, error) {
  172. sess, err := c.scanDevice(c.db.QueryRow(ctx, getDeviceQuery, jid))
  173. if errors.Is(err, sql.ErrNoRows) {
  174. return nil, nil
  175. }
  176. return sess, err
  177. }
  178. const (
  179. insertDeviceQuery = `
  180. INSERT INTO whatsmeow_device (jid, lid, registration_id, noise_key, identity_key,
  181. signed_pre_key, signed_pre_key_id, signed_pre_key_sig,
  182. adv_key, adv_details, adv_account_sig, adv_account_sig_key, adv_device_sig,
  183. platform, business_name, push_name, facebook_uuid, lid_migration_ts,mobile,mobile_info)
  184. VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18,$19,$20)
  185. ON CONFLICT (jid) DO UPDATE
  186. SET lid=excluded.lid,
  187. platform=excluded.platform,
  188. business_name=excluded.business_name,
  189. push_name=excluded.push_name,
  190. lid_migration_ts=excluded.lid_migration_ts
  191. `
  192. deleteDeviceQuery = `DELETE FROM whatsmeow_device WHERE jid=$1`
  193. )
  194. // NewDevice creates a new device in this database.
  195. //
  196. // No data is actually stored before Save is called. However, the pairing process will automatically
  197. // call Save after a successful pairing, so you most likely don't need to call it yourself.
  198. func (c *Container) NewDevice() *store.Device {
  199. device := &store.Device{
  200. Log: c.log,
  201. Container: c,
  202. NoiseKey: keys.NewKeyPair(),
  203. IdentityKey: keys.NewKeyPair(),
  204. RegistrationID: mathRand.Uint32(),
  205. AdvSecretKey: random.Bytes(32),
  206. }
  207. device.SignedPreKey = device.IdentityKey.CreateSignedPreKey(1)
  208. return device
  209. }
  210. // ErrDeviceIDMustBeSet is the error returned by PutDevice if you try to save a device before knowing its JID.
  211. var ErrDeviceIDMustBeSet = errors.New("device JID must be known before accessing database")
  212. // Close will close the container's database
  213. func (c *Container) Close() error {
  214. if c != nil && c.db != nil {
  215. return c.db.Close()
  216. }
  217. return nil
  218. }
  219. // PutDevice stores the given device in this database. This should be called through Device.Save()
  220. // (which usually doesn't need to be called manually, as the library does that automatically when relevant).
  221. func (c *Container) PutDevice(ctx context.Context, device *store.Device) error {
  222. if device.ID == nil {
  223. return ErrDeviceIDMustBeSet
  224. }
  225. _, err := c.db.Exec(ctx, insertDeviceQuery,
  226. device.ID, device.LID, device.RegistrationID, device.NoiseKey.Priv[:], device.IdentityKey.Priv[:],
  227. device.SignedPreKey.Priv[:], device.SignedPreKey.KeyID, device.SignedPreKey.Signature[:],
  228. device.AdvSecretKey, device.Account.Details, device.Account.AccountSignature, device.Account.AccountSignatureKey, device.Account.DeviceSignature,
  229. device.Platform, device.BusinessName, device.PushName, uuid.NullUUID{UUID: device.FacebookUUID, Valid: device.FacebookUUID != uuid.Nil},
  230. device.LIDMigrationTimestamp,
  231. device.Mobile, device.MobileInfo,
  232. )
  233. if !device.Initialized {
  234. c.initializeDevice(device)
  235. }
  236. return err
  237. }
  238. func (c *Container) initializeDevice(device *store.Device) {
  239. innerStore := NewSQLStore(c, *device.ID)
  240. device.Identities = innerStore
  241. device.Sessions = innerStore
  242. device.PreKeys = innerStore
  243. device.SenderKeys = innerStore
  244. device.AppStateKeys = innerStore
  245. device.AppState = innerStore
  246. device.Contacts = innerStore
  247. device.ChatSettings = innerStore
  248. device.MsgSecrets = innerStore
  249. device.PrivacyTokens = innerStore
  250. device.EventBuffer = innerStore
  251. device.LIDs = c.LIDMap
  252. device.Container = c
  253. device.Initialized = true
  254. }
  255. // DeleteDevice deletes the given device from this database. This should be called through Device.Delete()
  256. func (c *Container) DeleteDevice(ctx context.Context, store *store.Device) error {
  257. if store.ID == nil {
  258. return ErrDeviceIDMustBeSet
  259. }
  260. _, err := c.db.Exec(ctx, deleteDeviceQuery, store.ID)
  261. return err
  262. }