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.
- slidge_whatsapp/generated/_whatsapp.cpython-311-aarch64-linux-gnu.h +178 -178
- slidge_whatsapp/generated/_whatsapp.cpython-311-aarch64-linux-gnu.so +0 -0
- slidge_whatsapp/generated/build.py +145 -145
- slidge_whatsapp/generated/go.py +1 -1
- slidge_whatsapp/generated/whatsapp.c +1517 -1517
- slidge_whatsapp/generated/whatsapp.go +1151 -1151
- slidge_whatsapp/generated/whatsapp.py +1175 -1175
- slidge_whatsapp/generated/whatsapp_go.h +178 -178
- slidge_whatsapp/go.mod +9 -9
- slidge_whatsapp/go.sum +18 -18
- slidge_whatsapp/vendor/go.mau.fi/libsignal/session/SessionCipher.go +7 -2
- slidge_whatsapp/vendor/go.mau.fi/util/dbutil/module.go +2 -1
- slidge_whatsapp/vendor/go.mau.fi/util/dbutil/upgradetable.go +3 -0
- slidge_whatsapp/vendor/go.mau.fi/util/exsync/syncmap.go +48 -7
- slidge_whatsapp/vendor/go.mau.fi/util/exsync/syncset.go +13 -0
- slidge_whatsapp/vendor/go.mau.fi/util/jsontime/helpers.go +16 -5
- slidge_whatsapp/vendor/go.mau.fi/util/jsontime/integer.go +27 -12
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/appstate/encode.go +39 -28
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/appstate.go +17 -2
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/armadillomessage.go +2 -1
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/internals.go +18 -6
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/message.go +40 -16
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/pair.go +24 -21
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/prekeys.go +21 -0
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/proto/waE2E/WAWebProtobufsE2E.pb.go +3213 -2851
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/proto/waE2E/WAWebProtobufsE2E.proto +108 -74
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/proto/waStatusAttributions/WAStatusAttributions.pb.go +7 -3
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/proto/waStatusAttributions/WAStatusAttributions.proto +1 -0
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/proto/waSyncAction/WASyncAction.pb.go +7 -3
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/proto/waSyncAction/WASyncAction.proto +1 -0
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/proto/waWeb/WAWebProtobufsWeb.pb.go +35 -23
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/proto/waWeb/WAWebProtobufsWeb.proto +5 -3
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/receipt.go +47 -16
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/retry.go +4 -10
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/send.go +28 -42
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/sendfb.go +33 -32
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/store/clientpayload.go +1 -1
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/store/noop.go +5 -1
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/store/sessioncache.go +125 -0
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/store/signal.go +8 -0
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/store/sqlstore/store.go +34 -11
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/store/store.go +5 -3
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/types/jid.go +24 -9
- slidge_whatsapp/vendor/go.mau.fi/whatsmeow/user.go +12 -1
- slidge_whatsapp/vendor/golang.org/x/crypto/curve25519/curve25519.go +7 -4
- slidge_whatsapp/vendor/golang.org/x/net/http2/config.go +11 -6
- slidge_whatsapp/vendor/golang.org/x/net/http2/config_go125.go +15 -0
- slidge_whatsapp/vendor/golang.org/x/net/http2/config_go126.go +15 -0
- slidge_whatsapp/vendor/golang.org/x/net/http2/frame.go +24 -1
- slidge_whatsapp/vendor/golang.org/x/net/http2/http2.go +0 -1
- slidge_whatsapp/vendor/golang.org/x/net/http2/server.go +35 -26
- slidge_whatsapp/vendor/golang.org/x/net/http2/transport.go +4 -2
- slidge_whatsapp/vendor/golang.org/x/net/http2/writesched.go +2 -0
- slidge_whatsapp/vendor/golang.org/x/net/http2/{writesched_priority.go → writesched_priority_rfc7540.go} +52 -52
- slidge_whatsapp/vendor/golang.org/x/net/http2/writesched_priority_rfc9128.go +209 -0
- slidge_whatsapp/vendor/golang.org/x/net/http2/writesched_roundrobin.go +1 -1
- slidge_whatsapp/vendor/golang.org/x/net/internal/httpcommon/request.go +2 -2
- slidge_whatsapp/vendor/golang.org/x/net/internal/socks/socks.go +1 -1
- slidge_whatsapp/vendor/golang.org/x/sys/unix/affinity_linux.go +9 -0
- slidge_whatsapp/vendor/golang.org/x/sys/unix/fdset.go +1 -3
- slidge_whatsapp/vendor/golang.org/x/sys/unix/ifreq_linux.go +1 -3
- slidge_whatsapp/vendor/golang.org/x/sys/unix/mkall.sh +1 -0
- slidge_whatsapp/vendor/golang.org/x/sys/unix/syscall_linux.go +1 -3
- slidge_whatsapp/vendor/golang.org/x/sys/unix/syscall_netbsd.go +17 -0
- slidge_whatsapp/vendor/golang.org/x/sys/windows/syscall_windows.go +2 -0
- slidge_whatsapp/vendor/golang.org/x/sys/windows/types_windows.go +16 -0
- slidge_whatsapp/vendor/golang.org/x/sys/windows/zsyscall_windows.go +18 -0
- slidge_whatsapp/vendor/golang.org/x/text/unicode/bidi/core.go +2 -9
- slidge_whatsapp/vendor/modules.txt +10 -10
- {slidge_whatsapp-0.3.1.dist-info → slidge_whatsapp-0.3.4.dist-info}/METADATA +1 -1
- {slidge_whatsapp-0.3.1.dist-info → slidge_whatsapp-0.3.4.dist-info}/RECORD +74 -70
- {slidge_whatsapp-0.3.1.dist-info → slidge_whatsapp-0.3.4.dist-info}/WHEEL +0 -0
- {slidge_whatsapp-0.3.1.dist-info → slidge_whatsapp-0.3.4.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
|
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
|
-
|
|
1250
|
+
addr := encryptionIdentity.SignalAddress().String()
|
|
1251
|
+
sessionAddresses = append(sessionAddresses, addr)
|
|
1252
|
+
sessionAddressToJID[addr] = jid
|
|
1241
1253
|
}
|
|
1242
1254
|
|
|
1243
|
-
existingSessions, err := cli.Store.
|
|
1255
|
+
existingSessions, ctx, err := cli.Store.WithCachedSessions(ctx, sessionAddresses)
|
|
1244
1256
|
if err != nil {
|
|
1245
|
-
return nil, false, fmt.Errorf("failed to
|
|
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,
|
|
1276
|
+
ctx, plaintext, jid, encryptionIdentities[jid], bundles[jid], encAttrs, existingSessions,
|
|
1261
1277
|
)
|
|
1262
|
-
if
|
|
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
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
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,
|
|
533
|
-
if
|
|
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
|
-
|
|
544
|
-
|
|
545
|
-
|
|
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,
|
|
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)
|
|
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
|
-
|
|
115
|
-
|
|
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
|
-
|
|
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)
|
|
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,
|
|
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(
|
|
191
|
+
rows, err = s.db.Query(ctx, fmt.Sprintf(getManySessionQueryGeneric, strings.Join(placeholders, ",")), args...)
|
|
184
192
|
}
|
|
185
|
-
result := make(map[string]
|
|
193
|
+
result := make(map[string][]byte, len(addresses))
|
|
186
194
|
for _, addr := range addresses {
|
|
187
|
-
result[addr] =
|
|
195
|
+
result[addr] = nil
|
|
188
196
|
}
|
|
189
|
-
err =
|
|
190
|
-
result[
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
211
|
-
LID
|
|
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
|
|
79
|
+
return WhatsAppDomain
|
|
72
80
|
case HiddenUserServer:
|
|
73
|
-
return
|
|
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
|
|
124
|
-
server = DefaultUserServer
|
|
125
|
-
case 1:
|
|
136
|
+
case LIDDomain:
|
|
126
137
|
server = HiddenUserServer
|
|
127
138
|
agent = 0
|
|
128
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
6
|
+
// performs scalar multiplication on the elliptic curve known as Curve25519
|
|
7
|
+
// according to [RFC 7748].
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
+
}
|