slidge-whatsapp 0.3.1__cp311-cp311-manylinux_2_36_aarch64.whl → 0.3.4__cp311-cp311-manylinux_2_36_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of slidge-whatsapp might be problematic. Click here for more details.

Files changed (74) hide show
  1. slidge_whatsapp/generated/_whatsapp.cpython-311-aarch64-linux-gnu.h +178 -178
  2. slidge_whatsapp/generated/_whatsapp.cpython-311-aarch64-linux-gnu.so +0 -0
  3. slidge_whatsapp/generated/build.py +145 -145
  4. slidge_whatsapp/generated/go.py +1 -1
  5. slidge_whatsapp/generated/whatsapp.c +1517 -1517
  6. slidge_whatsapp/generated/whatsapp.go +1151 -1151
  7. slidge_whatsapp/generated/whatsapp.py +1175 -1175
  8. slidge_whatsapp/generated/whatsapp_go.h +178 -178
  9. slidge_whatsapp/go.mod +9 -9
  10. slidge_whatsapp/go.sum +18 -18
  11. slidge_whatsapp/vendor/go.mau.fi/libsignal/session/SessionCipher.go +7 -2
  12. slidge_whatsapp/vendor/go.mau.fi/util/dbutil/module.go +2 -1
  13. slidge_whatsapp/vendor/go.mau.fi/util/dbutil/upgradetable.go +3 -0
  14. slidge_whatsapp/vendor/go.mau.fi/util/exsync/syncmap.go +48 -7
  15. slidge_whatsapp/vendor/go.mau.fi/util/exsync/syncset.go +13 -0
  16. slidge_whatsapp/vendor/go.mau.fi/util/jsontime/helpers.go +16 -5
  17. slidge_whatsapp/vendor/go.mau.fi/util/jsontime/integer.go +27 -12
  18. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/appstate/encode.go +39 -28
  19. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/appstate.go +17 -2
  20. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/armadillomessage.go +2 -1
  21. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/internals.go +18 -6
  22. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/message.go +40 -16
  23. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/pair.go +24 -21
  24. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/prekeys.go +21 -0
  25. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/proto/waE2E/WAWebProtobufsE2E.pb.go +3213 -2851
  26. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/proto/waE2E/WAWebProtobufsE2E.proto +108 -74
  27. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/proto/waStatusAttributions/WAStatusAttributions.pb.go +7 -3
  28. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/proto/waStatusAttributions/WAStatusAttributions.proto +1 -0
  29. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/proto/waSyncAction/WASyncAction.pb.go +7 -3
  30. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/proto/waSyncAction/WASyncAction.proto +1 -0
  31. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/proto/waWeb/WAWebProtobufsWeb.pb.go +35 -23
  32. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/proto/waWeb/WAWebProtobufsWeb.proto +5 -3
  33. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/receipt.go +47 -16
  34. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/retry.go +4 -10
  35. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/send.go +28 -42
  36. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/sendfb.go +33 -32
  37. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/store/clientpayload.go +1 -1
  38. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/store/noop.go +5 -1
  39. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/store/sessioncache.go +125 -0
  40. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/store/signal.go +8 -0
  41. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/store/sqlstore/store.go +34 -11
  42. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/store/store.go +5 -3
  43. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/types/jid.go +24 -9
  44. slidge_whatsapp/vendor/go.mau.fi/whatsmeow/user.go +12 -1
  45. slidge_whatsapp/vendor/golang.org/x/crypto/curve25519/curve25519.go +7 -4
  46. slidge_whatsapp/vendor/golang.org/x/net/http2/config.go +11 -6
  47. slidge_whatsapp/vendor/golang.org/x/net/http2/config_go125.go +15 -0
  48. slidge_whatsapp/vendor/golang.org/x/net/http2/config_go126.go +15 -0
  49. slidge_whatsapp/vendor/golang.org/x/net/http2/frame.go +24 -1
  50. slidge_whatsapp/vendor/golang.org/x/net/http2/http2.go +0 -1
  51. slidge_whatsapp/vendor/golang.org/x/net/http2/server.go +35 -26
  52. slidge_whatsapp/vendor/golang.org/x/net/http2/transport.go +4 -2
  53. slidge_whatsapp/vendor/golang.org/x/net/http2/writesched.go +2 -0
  54. slidge_whatsapp/vendor/golang.org/x/net/http2/{writesched_priority.go → writesched_priority_rfc7540.go} +52 -52
  55. slidge_whatsapp/vendor/golang.org/x/net/http2/writesched_priority_rfc9128.go +209 -0
  56. slidge_whatsapp/vendor/golang.org/x/net/http2/writesched_roundrobin.go +1 -1
  57. slidge_whatsapp/vendor/golang.org/x/net/internal/httpcommon/request.go +2 -2
  58. slidge_whatsapp/vendor/golang.org/x/net/internal/socks/socks.go +1 -1
  59. slidge_whatsapp/vendor/golang.org/x/sys/unix/affinity_linux.go +9 -0
  60. slidge_whatsapp/vendor/golang.org/x/sys/unix/fdset.go +1 -3
  61. slidge_whatsapp/vendor/golang.org/x/sys/unix/ifreq_linux.go +1 -3
  62. slidge_whatsapp/vendor/golang.org/x/sys/unix/mkall.sh +1 -0
  63. slidge_whatsapp/vendor/golang.org/x/sys/unix/syscall_linux.go +1 -3
  64. slidge_whatsapp/vendor/golang.org/x/sys/unix/syscall_netbsd.go +17 -0
  65. slidge_whatsapp/vendor/golang.org/x/sys/windows/syscall_windows.go +2 -0
  66. slidge_whatsapp/vendor/golang.org/x/sys/windows/types_windows.go +16 -0
  67. slidge_whatsapp/vendor/golang.org/x/sys/windows/zsyscall_windows.go +18 -0
  68. slidge_whatsapp/vendor/golang.org/x/text/unicode/bidi/core.go +2 -9
  69. slidge_whatsapp/vendor/modules.txt +10 -10
  70. {slidge_whatsapp-0.3.1.dist-info → slidge_whatsapp-0.3.4.dist-info}/METADATA +1 -1
  71. {slidge_whatsapp-0.3.1.dist-info → slidge_whatsapp-0.3.4.dist-info}/RECORD +74 -70
  72. {slidge_whatsapp-0.3.1.dist-info → slidge_whatsapp-0.3.4.dist-info}/WHEEL +0 -0
  73. {slidge_whatsapp-0.3.1.dist-info → slidge_whatsapp-0.3.4.dist-info}/entry_points.txt +0 -0
  74. {slidge_whatsapp-0.3.1.dist-info → slidge_whatsapp-0.3.4.dist-info}/licenses/LICENSE +0 -0
@@ -14,6 +14,7 @@ import (
14
14
  "encoding/hex"
15
15
  "errors"
16
16
  "fmt"
17
+ "slices"
17
18
  "sort"
18
19
  "strconv"
19
20
  "strings"
@@ -366,6 +367,8 @@ func (cli *Client) SendMessage(ctx context.Context, to types.JID, message *waE2E
366
367
 
367
368
  start := time.Now()
368
369
  // Sending multiple messages at a time can cause weird issues and makes it harder to retry safely
370
+ // This is also required for the session prefetching that makes group sends faster
371
+ // (everything will explode if you send a message to the same user twice in parallel)
369
372
  cli.messageSendLock.Lock()
370
373
  resp.DebugTimings.Queue = time.Since(start)
371
374
  defer cli.messageSendLock.Unlock()
@@ -1120,6 +1123,12 @@ func (cli *Client) prepareMessageNode(
1120
1123
  return nil, nil, fmt.Errorf("failed to get device list: %w", err)
1121
1124
  }
1122
1125
 
1126
+ if to.Server == types.GroupServer {
1127
+ allDevices = slices.DeleteFunc(allDevices, func(jid types.JID) bool {
1128
+ return jid.Server == types.HostedServer || jid.Server == types.HostedLIDServer
1129
+ })
1130
+ }
1131
+
1123
1132
  msgType := getTypeFromMessage(message)
1124
1133
  encAttrs := waBinary.Attrs{}
1125
1134
  // Only include encMediaType for 1:1 messages (groups don't have a device-sent message plaintext)
@@ -1226,7 +1235,8 @@ func (cli *Client) encryptMessageForDevices(
1226
1235
  }
1227
1236
 
1228
1237
  encryptionIdentities := make(map[types.JID]types.JID, len(allDevices))
1229
- var sessionAddresses []string
1238
+ sessionAddressToJID := make(map[string]types.JID, len(allDevices))
1239
+ sessionAddresses := make([]string, 0, len(allDevices))
1230
1240
  for _, jid := range allDevices {
1231
1241
  encryptionIdentity := jid
1232
1242
  if jid.Server == types.DefaultUserServer {
@@ -1237,15 +1247,23 @@ func (cli *Client) encryptMessageForDevices(
1237
1247
  }
1238
1248
  }
1239
1249
  encryptionIdentities[jid] = encryptionIdentity
1240
- sessionAddresses = append(sessionAddresses, encryptionIdentity.SignalAddress().String())
1250
+ addr := encryptionIdentity.SignalAddress().String()
1251
+ sessionAddresses = append(sessionAddresses, addr)
1252
+ sessionAddressToJID[addr] = jid
1241
1253
  }
1242
1254
 
1243
- existingSessions, err := cli.Store.Sessions.HasManySessions(ctx, sessionAddresses)
1255
+ existingSessions, ctx, err := cli.Store.WithCachedSessions(ctx, sessionAddresses)
1244
1256
  if err != nil {
1245
- return nil, false, fmt.Errorf("failed to check which sessions exist: %w", err)
1257
+ return nil, false, fmt.Errorf("failed to prefetch sessions: %w", err)
1246
1258
  }
1259
+ var retryDevices []types.JID
1260
+ for addr, exists := range existingSessions {
1261
+ if !exists {
1262
+ retryDevices = append(retryDevices, sessionAddressToJID[addr])
1263
+ }
1264
+ }
1265
+ bundles := cli.fetchPreKeysNoError(ctx, retryDevices)
1247
1266
 
1248
- var retryDevices, retryEncryptionIdentities []types.JID
1249
1267
  for _, jid := range allDevices {
1250
1268
  plaintext := msgPlaintext
1251
1269
  if (jid.User == ownJID.User || jid.User == ownLID.User) && dsmPlaintext != nil {
@@ -1254,16 +1272,10 @@ func (cli *Client) encryptMessageForDevices(
1254
1272
  }
1255
1273
  plaintext = dsmPlaintext
1256
1274
  }
1257
- encryptionIdentity := encryptionIdentities[jid]
1258
-
1259
1275
  encrypted, isPreKey, err := cli.encryptMessageForDeviceAndWrap(
1260
- ctx, plaintext, jid, encryptionIdentity, nil, encAttrs, existingSessions,
1276
+ ctx, plaintext, jid, encryptionIdentities[jid], bundles[jid], encAttrs, existingSessions,
1261
1277
  )
1262
- if errors.Is(err, ErrNoSession) {
1263
- retryDevices = append(retryDevices, jid)
1264
- retryEncryptionIdentities = append(retryEncryptionIdentities, encryptionIdentity)
1265
- continue
1266
- } else if err != nil {
1278
+ if err != nil {
1267
1279
  // TODO return these errors if it's a fatal one (like context cancellation or database)
1268
1280
  cli.Log.Warnf("Failed to encrypt %s for %s: %v", id, jid, err)
1269
1281
  if ctx.Err() != nil {
@@ -1277,35 +1289,9 @@ func (cli *Client) encryptMessageForDevices(
1277
1289
  includeIdentity = true
1278
1290
  }
1279
1291
  }
1280
- if len(retryDevices) > 0 {
1281
- bundles, err := cli.fetchPreKeys(ctx, retryDevices)
1282
- if err != nil {
1283
- cli.Log.Warnf("Failed to fetch prekeys for %v to retry encryption: %v", retryDevices, err)
1284
- } else {
1285
- for i, jid := range retryDevices {
1286
- resp := bundles[jid]
1287
- if resp.err != nil {
1288
- cli.Log.Warnf("Failed to fetch prekey for %s: %v", jid, resp.err)
1289
- continue
1290
- }
1291
- plaintext := msgPlaintext
1292
- if (jid.User == ownJID.User || jid.User == ownLID.User) && dsmPlaintext != nil {
1293
- plaintext = dsmPlaintext
1294
- }
1295
- encrypted, isPreKey, err := cli.encryptMessageForDeviceAndWrap(
1296
- ctx, plaintext, jid, retryEncryptionIdentities[i], resp.bundle, encAttrs, nil,
1297
- )
1298
- if err != nil {
1299
- // TODO return these errors if it's a fatal one (like context cancellation or database)
1300
- cli.Log.Warnf("Failed to encrypt %s for %s (retry): %v", id, jid, err)
1301
- continue
1302
- }
1303
- participantNodes = append(participantNodes, *encrypted)
1304
- if isPreKey {
1305
- includeIdentity = true
1306
- }
1307
- }
1308
- }
1292
+ err = cli.Store.PutCachedSessions(ctx)
1293
+ if err != nil {
1294
+ return nil, false, fmt.Errorf("failed to save cached sessions: %w", err)
1309
1295
  }
1310
1296
  return participantNodes, includeIdentity, nil
1311
1297
  }
@@ -468,7 +468,10 @@ func (cli *Client) prepareMessageNodeV3(
468
468
  }
469
469
 
470
470
  start = time.Now()
471
- participantNodes := cli.encryptMessageForDevicesV3(ctx, allDevices, ownID, id, payload, skdm, dsm, encAttrs)
471
+ participantNodes, err := cli.encryptMessageForDevicesV3(ctx, allDevices, ownID, id, payload, skdm, dsm, encAttrs)
472
+ if err != nil {
473
+ return nil, nil, err
474
+ }
472
475
  timings.PeerEncrypt = time.Since(start)
473
476
  content := make([]waBinary.Node, 0, 4)
474
477
  content = append(content, waBinary.Node{
@@ -518,9 +521,28 @@ func (cli *Client) encryptMessageForDevicesV3(
518
521
  skdm *waMsgTransport.MessageTransport_Protocol_Ancillary_SenderKeyDistributionMessage,
519
522
  dsm *waMsgTransport.MessageTransport_Protocol_Integral_DeviceSentMessage,
520
523
  encAttrs waBinary.Attrs,
521
- ) []waBinary.Node {
524
+ ) ([]waBinary.Node, error) {
522
525
  participantNodes := make([]waBinary.Node, 0, len(allDevices))
526
+
527
+ sessionAddressToJID := make(map[string]types.JID, len(allDevices))
528
+ sessionAddresses := make([]string, 0, len(allDevices))
529
+ for _, jid := range allDevices {
530
+ addr := jid.SignalAddress().String()
531
+ sessionAddresses = append(sessionAddresses, addr)
532
+ sessionAddressToJID[addr] = jid
533
+ }
534
+ existingSessions, ctx, err := cli.Store.WithCachedSessions(ctx, sessionAddresses)
535
+ if err != nil {
536
+ return nil, fmt.Errorf("failed to prefetch sessions: %w", err)
537
+ }
523
538
  var retryDevices []types.JID
539
+ for addr, exists := range existingSessions {
540
+ if !exists {
541
+ retryDevices = append(retryDevices, sessionAddressToJID[addr])
542
+ }
543
+ }
544
+ bundles := cli.fetchPreKeysNoError(ctx, retryDevices)
545
+
524
546
  for _, jid := range allDevices {
525
547
  var dsmForDevice *waMsgTransport.MessageTransport_Protocol_Integral_DeviceSentMessage
526
548
  if jid.User == ownID.User {
@@ -529,43 +551,22 @@ func (cli *Client) encryptMessageForDevicesV3(
529
551
  }
530
552
  dsmForDevice = dsm
531
553
  }
532
- encrypted, err := cli.encryptMessageForDeviceAndWrapV3(ctx, payload, skdm, dsmForDevice, jid, nil, encAttrs)
533
- if errors.Is(err, ErrNoSession) {
534
- retryDevices = append(retryDevices, jid)
535
- continue
536
- } else if err != nil {
554
+ encrypted, err := cli.encryptMessageForDeviceAndWrapV3(ctx, payload, skdm, dsmForDevice, jid, bundles[jid], encAttrs)
555
+ if err != nil {
537
556
  // TODO return these errors if it's a fatal one (like context cancellation or database)
538
557
  cli.Log.Warnf("Failed to encrypt %s for %s: %v", id, jid, err)
558
+ if ctx.Err() != nil {
559
+ return nil, err
560
+ }
539
561
  continue
540
562
  }
541
563
  participantNodes = append(participantNodes, *encrypted)
542
564
  }
543
- if len(retryDevices) > 0 {
544
- bundles, err := cli.fetchPreKeys(ctx, retryDevices)
545
- if err != nil {
546
- cli.Log.Warnf("Failed to fetch prekeys for %v to retry encryption: %v", retryDevices, err)
547
- } else {
548
- for _, jid := range retryDevices {
549
- resp := bundles[jid]
550
- if resp.err != nil {
551
- cli.Log.Warnf("Failed to fetch prekey for %s: %v", jid, resp.err)
552
- continue
553
- }
554
- var dsmForDevice *waMsgTransport.MessageTransport_Protocol_Integral_DeviceSentMessage
555
- if jid.User == ownID.User {
556
- dsmForDevice = dsm
557
- }
558
- encrypted, err := cli.encryptMessageForDeviceAndWrapV3(ctx, payload, skdm, dsmForDevice, jid, resp.bundle, encAttrs)
559
- if err != nil {
560
- // TODO return these errors if it's a fatal one (like context cancellation or database)
561
- cli.Log.Warnf("Failed to encrypt %s for %s (retry): %v", id, jid, err)
562
- continue
563
- }
564
- participantNodes = append(participantNodes, *encrypted)
565
- }
566
- }
565
+ err = cli.Store.PutCachedSessions(ctx)
566
+ if err != nil {
567
+ return nil, fmt.Errorf("failed to save cached sessions: %w", err)
567
568
  }
568
- return participantNodes
569
+ return participantNodes, nil
569
570
  }
570
571
 
571
572
  func (cli *Client) encryptMessageForDeviceAndWrapV3(
@@ -76,7 +76,7 @@ func (vc WAVersionContainer) ProtoAppVersion() *waWa6.ClientPayload_UserAgent_Ap
76
76
  }
77
77
 
78
78
  // waVersion is the WhatsApp web client version
79
- var waVersion = WAVersionContainer{2, 3000, 1027949008}
79
+ var waVersion = WAVersionContainer{2, 3000, 1028259376}
80
80
 
81
81
  // waVersionHash is the md5 hash of a dot-separated waVersion
82
82
  var waVersionHash [16]byte
@@ -68,7 +68,7 @@ func (n *NoopStore) HasSession(ctx context.Context, address string) (bool, error
68
68
  return false, n.Error
69
69
  }
70
70
 
71
- func (n *NoopStore) HasManySessions(ctx context.Context, addresses []string) (map[string]bool, error) {
71
+ func (n *NoopStore) GetManySessions(ctx context.Context, addresses []string) (map[string][]byte, error) {
72
72
  return nil, n.Error
73
73
  }
74
74
 
@@ -76,6 +76,10 @@ func (n *NoopStore) PutSession(ctx context.Context, address string, session []by
76
76
  return n.Error
77
77
  }
78
78
 
79
+ func (n *NoopStore) PutManySessions(ctx context.Context, sessions map[string][]byte) error {
80
+ return n.Error
81
+ }
82
+
79
83
  func (n *NoopStore) DeleteAllSessions(ctx context.Context, phone string) error {
80
84
  return n.Error
81
85
  }
@@ -0,0 +1,125 @@
1
+ // Copyright (c) 2025 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
+
7
+ package store
8
+
9
+ import (
10
+ "context"
11
+ "fmt"
12
+
13
+ "github.com/rs/zerolog"
14
+ "go.mau.fi/libsignal/state/record"
15
+
16
+ "go.mau.fi/util/exsync"
17
+ )
18
+
19
+ type contextKey int
20
+
21
+ const (
22
+ contextKeySessionCache contextKey = iota
23
+ )
24
+
25
+ type sessionCacheEntry struct {
26
+ Dirty bool
27
+ Found bool
28
+ Record *record.Session
29
+ }
30
+
31
+ type sessionCache = exsync.Map[string, sessionCacheEntry]
32
+
33
+ func getSessionCache(ctx context.Context) *sessionCache {
34
+ if ctx == nil {
35
+ return nil
36
+ }
37
+ val := ctx.Value(contextKeySessionCache)
38
+ if val == nil {
39
+ return nil
40
+ }
41
+ if cache, ok := val.(*sessionCache); ok {
42
+ return cache
43
+ }
44
+ return nil
45
+ }
46
+
47
+ func getCachedSession(ctx context.Context, addr string) *record.Session {
48
+ cache := getSessionCache(ctx)
49
+ if cache == nil {
50
+ return nil
51
+ }
52
+ sess, ok := cache.Get(addr)
53
+ if !ok {
54
+ return nil
55
+ }
56
+ return sess.Record
57
+ }
58
+
59
+ func putCachedSession(ctx context.Context, addr string, record *record.Session) bool {
60
+ cache := getSessionCache(ctx)
61
+ if cache == nil {
62
+ return false
63
+ }
64
+ cache.Set(addr, sessionCacheEntry{
65
+ Dirty: true,
66
+ Found: true,
67
+ Record: record,
68
+ })
69
+ return true
70
+ }
71
+
72
+ func (device *Device) WithCachedSessions(ctx context.Context, addresses []string) (map[string]bool, context.Context, error) {
73
+ if len(addresses) == 0 {
74
+ return nil, ctx, nil
75
+ }
76
+
77
+ sessions, err := device.Sessions.GetManySessions(ctx, addresses)
78
+ if err != nil {
79
+ return nil, ctx, fmt.Errorf("failed to prefetch sessions: %w", err)
80
+ }
81
+ wrapped := make(map[string]sessionCacheEntry, len(sessions))
82
+ existingSessions := make(map[string]bool, len(sessions))
83
+ for addr, rawSess := range sessions {
84
+ var sessionRecord *record.Session
85
+ var found bool
86
+ if rawSess == nil {
87
+ sessionRecord = record.NewSession(SignalProtobufSerializer.Session, SignalProtobufSerializer.State)
88
+ } else {
89
+ found = true
90
+ sessionRecord, err = record.NewSessionFromBytes(rawSess, SignalProtobufSerializer.Session, SignalProtobufSerializer.State)
91
+ if err != nil {
92
+ zerolog.Ctx(ctx).Err(err).
93
+ Str("address", addr).
94
+ Msg("Failed to deserialize session")
95
+ continue
96
+ }
97
+ }
98
+ existingSessions[addr] = found
99
+ wrapped[addr] = sessionCacheEntry{Record: sessionRecord, Found: found}
100
+ }
101
+
102
+ ctx = context.WithValue(ctx, contextKeySessionCache, (*sessionCache)(exsync.NewMapWithData(wrapped)))
103
+ return existingSessions, ctx, nil
104
+ }
105
+
106
+ func (device *Device) PutCachedSessions(ctx context.Context) error {
107
+ cache := getSessionCache(ctx)
108
+ if cache == nil {
109
+ return nil
110
+ }
111
+ dirtySessions := make(map[string][]byte)
112
+ for addr, item := range cache.Iter() {
113
+ if item.Dirty {
114
+ dirtySessions[addr] = item.Record.Serialize()
115
+ }
116
+ }
117
+ if len(dirtySessions) > 0 {
118
+ err := device.Sessions.PutManySessions(ctx, dirtySessions)
119
+ if err != nil {
120
+ return fmt.Errorf("failed to store cached sessions: %w", err)
121
+ }
122
+ }
123
+ cache.Clear()
124
+ return nil
125
+ }
@@ -84,6 +84,10 @@ func (device *Device) ContainsPreKey(ctx context.Context, preKeyID uint32) (bool
84
84
 
85
85
  func (device *Device) LoadSession(ctx context.Context, address *protocol.SignalAddress) (*record.Session, error) {
86
86
  addrString := address.String()
87
+ if sess := getCachedSession(ctx, addrString); sess != nil {
88
+ return sess, nil
89
+ }
90
+
87
91
  rawSess, err := device.Sessions.GetSession(ctx, addrString)
88
92
  if err != nil {
89
93
  return nil, fmt.Errorf("failed to load session with %s: %w", addrString, err)
@@ -104,6 +108,10 @@ func (device *Device) GetSubDeviceSessions(ctx context.Context, name string) ([]
104
108
 
105
109
  func (device *Device) StoreSession(ctx context.Context, address *protocol.SignalAddress, record *record.Session) error {
106
110
  addrString := address.String()
111
+ if putCachedSession(ctx, addrString, record) {
112
+ return nil
113
+ }
114
+
107
115
  err := device.Sessions.PutSession(ctx, addrString, record.Serialize())
108
116
  if err != nil {
109
117
  return fmt.Errorf("failed to store session with %s: %w", addrString, err)
@@ -111,8 +111,8 @@ func (s *SQLStore) IsTrustedIdentity(ctx context.Context, address string, key [3
111
111
  const (
112
112
  getSessionQuery = `SELECT session FROM whatsmeow_sessions WHERE our_jid=$1 AND their_id=$2`
113
113
  hasSessionQuery = `SELECT true FROM whatsmeow_sessions WHERE our_jid=$1 AND their_id=$2`
114
- hasManySessionQueryPostgres = `SELECT their_id FROM whatsmeow_sessions WHERE our_jid=$1 AND their_id = ANY($2)`
115
- hasManySessionQueryGeneric = `SELECT their_id FROM whatsmeow_sessions WHERE our_jid=$1 AND their_id IN (%s)`
114
+ getManySessionQueryPostgres = `SELECT their_id, session FROM whatsmeow_sessions WHERE our_jid=$1 AND their_id = ANY($2)`
115
+ getManySessionQueryGeneric = `SELECT their_id, session FROM whatsmeow_sessions WHERE our_jid=$1 AND their_id IN (%s)`
116
116
  putSessionQuery = `
117
117
  INSERT INTO whatsmeow_sessions (our_jid, their_id, session) VALUES ($1, $2, $3)
118
118
  ON CONFLICT (our_jid, their_id) DO UPDATE SET session=excluded.session
@@ -161,9 +161,17 @@ func (s *SQLStore) HasSession(ctx context.Context, address string) (has bool, er
161
161
  return
162
162
  }
163
163
 
164
- var stringScanner = dbutil.ConvertRowFn[string](dbutil.ScanSingleColumn[string])
164
+ type addressSessionTuple struct {
165
+ Address string
166
+ Session []byte
167
+ }
168
+
169
+ var sessionScanner = dbutil.ConvertRowFn[addressSessionTuple](func(row dbutil.Scannable) (out addressSessionTuple, err error) {
170
+ err = row.Scan(&out.Address, &out.Session)
171
+ return
172
+ })
165
173
 
166
- func (s *SQLStore) HasManySessions(ctx context.Context, addresses []string) (map[string]bool, error) {
174
+ func (s *SQLStore) GetManySessions(ctx context.Context, addresses []string) (map[string][]byte, error) {
167
175
  if len(addresses) == 0 {
168
176
  return nil, nil
169
177
  }
@@ -171,7 +179,7 @@ func (s *SQLStore) HasManySessions(ctx context.Context, addresses []string) (map
171
179
  var rows dbutil.Rows
172
180
  var err error
173
181
  if s.db.Dialect == dbutil.Postgres && PostgresArrayWrapper != nil {
174
- rows, err = s.db.Query(ctx, hasManySessionQueryPostgres, s.JID, PostgresArrayWrapper(addresses))
182
+ rows, err = s.db.Query(ctx, getManySessionQueryPostgres, s.JID, PostgresArrayWrapper(addresses))
175
183
  } else {
176
184
  args := make([]any, len(addresses)+1)
177
185
  placeholders := make([]string, len(addresses))
@@ -180,17 +188,32 @@ func (s *SQLStore) HasManySessions(ctx context.Context, addresses []string) (map
180
188
  args[i+1] = addr
181
189
  placeholders[i] = fmt.Sprintf("$%d", i+2)
182
190
  }
183
- rows, err = s.db.Query(ctx, fmt.Sprintf(hasManySessionQueryGeneric, strings.Join(placeholders, ",")), args...)
191
+ rows, err = s.db.Query(ctx, fmt.Sprintf(getManySessionQueryGeneric, strings.Join(placeholders, ",")), args...)
184
192
  }
185
- result := make(map[string]bool, len(addresses))
193
+ result := make(map[string][]byte, len(addresses))
186
194
  for _, addr := range addresses {
187
- result[addr] = false
195
+ result[addr] = nil
188
196
  }
189
- err = stringScanner.NewRowIter(rows, err).Iter(func(s string) (bool, error) {
190
- result[s] = true
197
+ err = sessionScanner.NewRowIter(rows, err).Iter(func(tuple addressSessionTuple) (bool, error) {
198
+ result[tuple.Address] = tuple.Session
191
199
  return true, nil
192
200
  })
193
- return result, err
201
+ if err != nil {
202
+ return nil, err
203
+ }
204
+ return result, nil
205
+ }
206
+
207
+ func (s *SQLStore) PutManySessions(ctx context.Context, sessions map[string][]byte) error {
208
+ return s.db.DoTxn(ctx, nil, func(ctx context.Context) error {
209
+ for addr, sess := range sessions {
210
+ err := s.PutSession(ctx, addr, sess)
211
+ if err != nil {
212
+ return err
213
+ }
214
+ }
215
+ return nil
216
+ })
194
217
  }
195
218
 
196
219
  func (s *SQLStore) PutSession(ctx context.Context, address string, session []byte) error {
@@ -29,8 +29,9 @@ type IdentityStore interface {
29
29
  type SessionStore interface {
30
30
  GetSession(ctx context.Context, address string) ([]byte, error)
31
31
  HasSession(ctx context.Context, address string) (bool, error)
32
- HasManySessions(ctx context.Context, addresses []string) (map[string]bool, error)
32
+ GetManySessions(ctx context.Context, addresses []string) (map[string][]byte, error)
33
33
  PutSession(ctx context.Context, address string, session []byte) error
34
+ PutManySessions(ctx context.Context, sessions map[string][]byte) error
34
35
  DeleteAllSessions(ctx context.Context, phone string) error
35
36
  DeleteSession(ctx context.Context, address string) error
36
37
  MigratePNToLID(ctx context.Context, pn, lid types.JID) error
@@ -207,8 +208,9 @@ type Device struct {
207
208
  RegistrationID uint32
208
209
  AdvSecretKey []byte
209
210
 
210
- ID *types.JID
211
- LID types.JID
211
+ ID *types.JID
212
+ LID types.JID
213
+
212
214
  Account *waAdv.ADVSignedDeviceIdentity
213
215
  Platform string
214
216
  BusinessName string
@@ -29,6 +29,7 @@ const (
29
29
  InteropServer = "interop"
30
30
  NewsletterServer = "newsletter"
31
31
  HostedServer = "hosted"
32
+ HostedLIDServer = "hosted.lid"
32
33
  BotServer = "bot"
33
34
  )
34
35
 
@@ -46,6 +47,13 @@ var (
46
47
  NewMetaAIJID = NewJID("867051314767696", BotServer)
47
48
  )
48
49
 
50
+ var (
51
+ WhatsAppDomain = uint8(0) // This is the main domain type that whatsapp uses
52
+ LIDDomain = uint8(1) // This is the domain for LID type JIDs
53
+ HostedDomain = uint8(128) // This is the domain for Hosted type JIDs
54
+ HostedLIDDomain = uint8(129) // This is the domain for Hosted LID type JIDs
55
+ )
56
+
49
57
  // MessageID is the internal ID of a WhatsApp message.
50
58
  type MessageID = string
51
59
 
@@ -68,9 +76,13 @@ type JID struct {
68
76
  func (jid JID) ActualAgent() uint8 {
69
77
  switch jid.Server {
70
78
  case DefaultUserServer:
71
- return 0
79
+ return WhatsAppDomain
72
80
  case HiddenUserServer:
73
- return 1
81
+ return LIDDomain
82
+ case HostedServer:
83
+ return HostedDomain
84
+ case HostedLIDServer:
85
+ return HostedLIDDomain
74
86
  default:
75
87
  return jid.RawAgent
76
88
  }
@@ -119,17 +131,20 @@ func (jid JID) IsBot() bool {
119
131
  // NewADJID creates a new AD JID.
120
132
  func NewADJID(user string, agent, device uint8) JID {
121
133
  var server string
134
+ // agent terminology isn't 100% correct here, these are the domainType, but whatsapp usually places them in the same place (if the switch case below doesn't process it, then it is an agent instead)
122
135
  switch agent {
123
- case 0:
124
- server = DefaultUserServer
125
- case 1:
136
+ case LIDDomain:
126
137
  server = HiddenUserServer
127
138
  agent = 0
128
- default:
129
- if (agent&0x01) != 0 || (agent&0x80) == 0 { // agent % 2 == 0 || agent < 128?
130
- // TODO invalid JID?
131
- }
139
+ case HostedDomain:
132
140
  server = HostedServer
141
+ agent = 0
142
+ case HostedLIDDomain:
143
+ server = HostedLIDServer
144
+ agent = 0
145
+ default:
146
+ case WhatsAppDomain:
147
+ server = DefaultUserServer // will just default to the normal server
133
148
  }
134
149
  return JID{
135
150
  User: user,
@@ -704,11 +704,22 @@ func parseDeviceList(user types.JID, deviceNode waBinary.Node) []types.JID {
704
704
  devices := make([]types.JID, 0, len(children))
705
705
  for _, device := range children {
706
706
  deviceID, ok := device.AttrGetter().GetInt64("id", true)
707
+ isHosted := device.AttrGetter().Bool("is_hosted")
707
708
  if device.Tag != "device" || !ok {
708
709
  continue
709
710
  }
710
711
  user.Device = uint16(deviceID)
711
- devices = append(devices, user)
712
+ if isHosted {
713
+ hostedUser := user
714
+ if user.Server == types.HiddenUserServer {
715
+ hostedUser.Server = types.HostedLIDServer
716
+ } else {
717
+ hostedUser.Server = types.HostedServer
718
+ }
719
+ devices = append(devices, hostedUser)
720
+ } else {
721
+ devices = append(devices, user)
722
+ }
712
723
  }
713
724
  return devices
714
725
  }
@@ -3,11 +3,14 @@
3
3
  // license that can be found in the LICENSE file.
4
4
 
5
5
  // Package curve25519 provides an implementation of the X25519 function, which
6
- // performs scalar multiplication on the elliptic curve known as Curve25519.
7
- // See RFC 7748.
6
+ // performs scalar multiplication on the elliptic curve known as Curve25519
7
+ // according to [RFC 7748].
8
8
  //
9
- // This package is a wrapper for the X25519 implementation
10
- // in the crypto/ecdh package.
9
+ // The curve25519 package is a wrapper for the X25519 implementation in the
10
+ // crypto/ecdh package. It is [frozen] and is not accepting new features.
11
+ //
12
+ // [RFC 7748]: https://datatracker.ietf.org/doc/html/rfc7748
13
+ // [frozen]: https://go.dev/wiki/Frozen
11
14
  package curve25519
12
15
 
13
16
  import "crypto/ecdh"
@@ -27,6 +27,7 @@ import (
27
27
  // - If the resulting value is zero or out of range, use a default.
28
28
  type http2Config struct {
29
29
  MaxConcurrentStreams uint32
30
+ StrictMaxConcurrentRequests bool
30
31
  MaxDecoderHeaderTableSize uint32
31
32
  MaxEncoderHeaderTableSize uint32
32
33
  MaxReadFrameSize uint32
@@ -64,12 +65,13 @@ func configFromServer(h1 *http.Server, h2 *Server) http2Config {
64
65
  // (the net/http Transport).
65
66
  func configFromTransport(h2 *Transport) http2Config {
66
67
  conf := http2Config{
67
- MaxEncoderHeaderTableSize: h2.MaxEncoderHeaderTableSize,
68
- MaxDecoderHeaderTableSize: h2.MaxDecoderHeaderTableSize,
69
- MaxReadFrameSize: h2.MaxReadFrameSize,
70
- SendPingTimeout: h2.ReadIdleTimeout,
71
- PingTimeout: h2.PingTimeout,
72
- WriteByteTimeout: h2.WriteByteTimeout,
68
+ StrictMaxConcurrentRequests: h2.StrictMaxConcurrentStreams,
69
+ MaxEncoderHeaderTableSize: h2.MaxEncoderHeaderTableSize,
70
+ MaxDecoderHeaderTableSize: h2.MaxDecoderHeaderTableSize,
71
+ MaxReadFrameSize: h2.MaxReadFrameSize,
72
+ SendPingTimeout: h2.ReadIdleTimeout,
73
+ PingTimeout: h2.PingTimeout,
74
+ WriteByteTimeout: h2.WriteByteTimeout,
73
75
  }
74
76
 
75
77
  // Unlike most config fields, where out-of-range values revert to the default,
@@ -128,6 +130,9 @@ func fillNetHTTPConfig(conf *http2Config, h2 *http.HTTP2Config) {
128
130
  if h2.MaxConcurrentStreams != 0 {
129
131
  conf.MaxConcurrentStreams = uint32(h2.MaxConcurrentStreams)
130
132
  }
133
+ if http2ConfigStrictMaxConcurrentRequests(h2) {
134
+ conf.StrictMaxConcurrentRequests = true
135
+ }
131
136
  if h2.MaxEncoderHeaderTableSize != 0 {
132
137
  conf.MaxEncoderHeaderTableSize = uint32(h2.MaxEncoderHeaderTableSize)
133
138
  }
@@ -0,0 +1,15 @@
1
+ // Copyright 2025 The Go Authors. All rights reserved.
2
+ // Use of this source code is governed by a BSD-style
3
+ // license that can be found in the LICENSE file.
4
+
5
+ //go:build !go1.26
6
+
7
+ package http2
8
+
9
+ import (
10
+ "net/http"
11
+ )
12
+
13
+ func http2ConfigStrictMaxConcurrentRequests(h2 *http.HTTP2Config) bool {
14
+ return false
15
+ }
@@ -0,0 +1,15 @@
1
+ // Copyright 2025 The Go Authors. All rights reserved.
2
+ // Use of this source code is governed by a BSD-style
3
+ // license that can be found in the LICENSE file.
4
+
5
+ //go:build go1.26
6
+
7
+ package http2
8
+
9
+ import (
10
+ "net/http"
11
+ )
12
+
13
+ func http2ConfigStrictMaxConcurrentRequests(h2 *http.HTTP2Config) bool {
14
+ return h2.StrictMaxConcurrentRequests
15
+ }