diff options
Diffstat (limited to 'vendor/go.mau.fi/whatsmeow')
-rw-r--r-- | vendor/go.mau.fi/whatsmeow/client.go | 21 | ||||
-rw-r--r-- | vendor/go.mau.fi/whatsmeow/connectionevents.go | 1 | ||||
-rw-r--r-- | vendor/go.mau.fi/whatsmeow/errors.go | 34 | ||||
-rw-r--r-- | vendor/go.mau.fi/whatsmeow/mediaretry.go | 31 | ||||
-rw-r--r-- | vendor/go.mau.fi/whatsmeow/request.go | 33 | ||||
-rw-r--r-- | vendor/go.mau.fi/whatsmeow/retry.go | 10 | ||||
-rw-r--r-- | vendor/go.mau.fi/whatsmeow/send.go | 53 | ||||
-rw-r--r-- | vendor/go.mau.fi/whatsmeow/types/events/events.go | 7 |
8 files changed, 134 insertions, 56 deletions
diff --git a/vendor/go.mau.fi/whatsmeow/client.go b/vendor/go.mau.fi/whatsmeow/client.go index 2190e138..70c82130 100644 --- a/vendor/go.mau.fi/whatsmeow/client.go +++ b/vendor/go.mau.fi/whatsmeow/client.go @@ -112,6 +112,8 @@ type Client struct { AutoTrustIdentity bool DebugDecodeBeforeSend bool + OneMessageAtATime bool + messageSendLock sync.Mutex uniqueID string idCounter uint32 @@ -262,7 +264,7 @@ func (cli *Client) onDisconnect(ns *socket.NoiseSocket, remote bool) { defer cli.socketLock.Unlock() if cli.socket == ns { cli.socket = nil - cli.clearResponseWaiters() + cli.clearResponseWaiters(xmlStreamEndNode) if !cli.isExpectedDisconnect() && remote { cli.Log.Debugf("Emitting Disconnected event") go cli.dispatchEvent(&events.Disconnected{}) @@ -294,9 +296,9 @@ func (cli *Client) autoReconnect() { return } for { - cli.AutoReconnectErrors++ autoReconnectDelay := time.Duration(cli.AutoReconnectErrors) * 2 * time.Second cli.Log.Debugf("Automatically reconnecting after %v", autoReconnectDelay) + cli.AutoReconnectErrors++ time.Sleep(autoReconnectDelay) err := cli.Connect() if errors.Is(err, ErrAlreadyConnected) { @@ -489,30 +491,35 @@ func (cli *Client) handlerQueueLoop(ctx context.Context) { } } -func (cli *Client) sendNode(node waBinary.Node) error { +func (cli *Client) sendNodeDebug(node waBinary.Node) ([]byte, error) { cli.socketLock.RLock() sock := cli.socket cli.socketLock.RUnlock() if sock == nil { - return ErrNotConnected + return nil, ErrNotConnected } payload, err := waBinary.Marshal(node) if err != nil { - return fmt.Errorf("failed to marshal node: %w", err) + return nil, fmt.Errorf("failed to marshal node: %w", err) } if cli.DebugDecodeBeforeSend { var decoded *waBinary.Node decoded, err = waBinary.Unmarshal(payload[1:]) if err != nil { cli.Log.Infof("Malformed payload: %s", base64.URLEncoding.EncodeToString(payload)) - return fmt.Errorf("failed to decode the binary we just produced: %w", err) + return nil, fmt.Errorf("failed to decode the binary we just produced: %w", err) } node = *decoded } cli.sendLog.Debugf("%s", node.XMLString()) - return sock.SendFrame(payload) + return payload, sock.SendFrame(payload) +} + +func (cli *Client) sendNode(node waBinary.Node) error { + _, err := cli.sendNodeDebug(node) + return err } func (cli *Client) dispatchEvent(evt interface{}) { diff --git a/vendor/go.mau.fi/whatsmeow/connectionevents.go b/vendor/go.mau.fi/whatsmeow/connectionevents.go index 82a39184..2c8d27c0 100644 --- a/vendor/go.mau.fi/whatsmeow/connectionevents.go +++ b/vendor/go.mau.fi/whatsmeow/connectionevents.go @@ -17,6 +17,7 @@ import ( func (cli *Client) handleStreamError(node *waBinary.Node) { atomic.StoreUint32(&cli.isLoggedIn, 0) + cli.clearResponseWaiters(node) code, _ := node.Attrs["code"].(string) conflict, _ := node.GetOptionalChildByTag("conflict") conflictType := conflict.AttrGetter().OptionalString("type") diff --git a/vendor/go.mau.fi/whatsmeow/errors.go b/vendor/go.mau.fi/whatsmeow/errors.go index 8b68bd0e..d11cbb37 100644 --- a/vendor/go.mau.fi/whatsmeow/errors.go +++ b/vendor/go.mau.fi/whatsmeow/errors.go @@ -15,11 +15,10 @@ import ( // Miscellaneous errors var ( - ErrNoSession = errors.New("can't encrypt message for device: no signal session established") - ErrIQTimedOut = errors.New("info query timed out") - ErrIQDisconnected = errors.New("websocket disconnected before info query returned response") - ErrNotConnected = errors.New("websocket not connected") - ErrNotLoggedIn = errors.New("the store doesn't contain a device JID") + ErrNoSession = errors.New("can't encrypt message for device: no signal session established") + ErrIQTimedOut = errors.New("info query timed out") + ErrNotConnected = errors.New("websocket not connected") + ErrNotLoggedIn = errors.New("the store doesn't contain a device JID") ErrAlreadyConnected = errors.New("websocket is already connected") @@ -47,6 +46,10 @@ var ( ErrBusinessMessageLinkNotFound = errors.New("that business message link does not exist or has been revoked") // ErrInvalidImageFormat is returned by SetGroupPhoto if the given photo is not in the correct format. ErrInvalidImageFormat = errors.New("the given data is not a valid image") + // ErrMediaNotAvailableOnPhone is returned by DecryptMediaRetryNotification if the given event contains error code 2. + ErrMediaNotAvailableOnPhone = errors.New("media no longer available on phone") + // ErrUnknownMediaRetryError is returned by DecryptMediaRetryNotification if the given event contains an unknown error code. + ErrUnknownMediaRetryError = errors.New("unknown media retry error") ) // Some errors that Client.SendMessage can return @@ -54,7 +57,6 @@ var ( ErrBroadcastListUnsupported = errors.New("sending to broadcast lists is not yet supported") ErrUnknownServer = errors.New("can't send message to unknown server") ErrRecipientADJID = errors.New("message recipient must be normal (non-AD) JID") - ErrSendDisconnected = errors.New("websocket disconnected before message send returned response") ) // Some errors that Client.Download can return @@ -157,3 +159,23 @@ type ElementMissingError struct { func (eme *ElementMissingError) Error() string { return fmt.Sprintf("missing <%s> element in %s", eme.Tag, eme.In) } + +var ErrIQDisconnected = &DisconnectedError{Action: "info query"} + +// DisconnectedError is returned if the websocket disconnects before an info query or other request gets a response. +type DisconnectedError struct { + Action string + Node *waBinary.Node +} + +func (err *DisconnectedError) Error() string { + return fmt.Sprintf("websocket disconnected before %s returned response", err.Action) +} + +func (err *DisconnectedError) Is(other error) bool { + otherDisc, ok := other.(*DisconnectedError) + if !ok { + return false + } + return otherDisc.Action == err.Action +} diff --git a/vendor/go.mau.fi/whatsmeow/mediaretry.go b/vendor/go.mau.fi/whatsmeow/mediaretry.go index c392b907..ddee228e 100644 --- a/vendor/go.mau.fi/whatsmeow/mediaretry.go +++ b/vendor/go.mau.fi/whatsmeow/mediaretry.go @@ -105,20 +105,22 @@ func (cli *Client) SendMediaRetryReceipt(message *types.MessageInfo, mediaKey [] // DecryptMediaRetryNotification decrypts a media retry notification using the media key. func DecryptMediaRetryNotification(evt *events.MediaRetry, mediaKey []byte) (*waProto.MediaRetryNotification, error) { - gcm, err := prepareMediaRetryGCM(mediaKey) - if err != nil { + var notif waProto.MediaRetryNotification + var plaintext []byte + if evt.Error != nil && evt.Ciphertext == nil { + if evt.Error.Code == 2 { + return nil, ErrMediaNotAvailableOnPhone + } + return nil, fmt.Errorf("%w (code: %d)", ErrUnknownMediaRetryError, evt.Error.Code) + } else if gcm, err := prepareMediaRetryGCM(mediaKey); err != nil { return nil, err - } - plaintext, err := gcm.Open(nil, evt.IV, evt.Ciphertext, []byte(evt.MessageID)) - if err != nil { + } else if plaintext, err = gcm.Open(nil, evt.IV, evt.Ciphertext, []byte(evt.MessageID)); err != nil { return nil, fmt.Errorf("failed to decrypt notification: %w", err) - } - var notif waProto.MediaRetryNotification - err = proto.Unmarshal(plaintext, ¬if) - if err != nil { + } else if err = proto.Unmarshal(plaintext, ¬if); err != nil { return nil, fmt.Errorf("failed to unmarshal notification (invalid encryption key?): %w", err) + } else { + return ¬if, nil } - return ¬if, nil } func parseMediaRetryNotification(node *waBinary.Node) (*events.MediaRetry, error) { @@ -141,6 +143,14 @@ func parseMediaRetryNotification(node *waBinary.Node) (*events.MediaRetry, error return nil, fmt.Errorf("missing attributes in <rmr> tag: %w", rmrAG.Error()) } + errNode, ok := node.GetOptionalChildByTag("error") + if ok { + evt.Error = &events.MediaRetryError{ + Code: errNode.AttrGetter().Int("code"), + } + return &evt, nil + } + evt.Ciphertext, ok = node.GetChildByTag("encrypt", "enc_p").Content.([]byte) if !ok { return nil, &ElementMissingError{Tag: "enc_p", In: fmt.Sprintf("retry notification %s", evt.MessageID)} @@ -153,7 +163,6 @@ func parseMediaRetryNotification(node *waBinary.Node) (*events.MediaRetry, error } func (cli *Client) handleMediaRetryNotification(node *waBinary.Node) { - // TODO handle errors (e.g. <error code="2"/>) evt, err := parseMediaRetryNotification(node) if err != nil { cli.Log.Warnf("Failed to parse media retry notification: %v", err) diff --git a/vendor/go.mau.fi/whatsmeow/request.go b/vendor/go.mau.fi/whatsmeow/request.go index dc693983..444ae1ad 100644 --- a/vendor/go.mau.fi/whatsmeow/request.go +++ b/vendor/go.mau.fi/whatsmeow/request.go @@ -8,6 +8,7 @@ package whatsmeow import ( "context" + "encoding/base64" "strconv" "sync/atomic" "time" @@ -20,13 +21,17 @@ func (cli *Client) generateRequestID() string { return cli.uniqueID + strconv.FormatUint(uint64(atomic.AddUint32(&cli.idCounter, 1)), 10) } -var closedNode = &waBinary.Node{Tag: "xmlstreamend"} +var xmlStreamEndNode = &waBinary.Node{Tag: "xmlstreamend"} -func (cli *Client) clearResponseWaiters() { +func isDisconnectNode(node *waBinary.Node) bool { + return node == xmlStreamEndNode || node.Tag == "stream:error" +} + +func (cli *Client) clearResponseWaiters(node *waBinary.Node) { cli.responseWaitersLock.Lock() for _, waiter := range cli.responseWaiters { select { - case waiter <- closedNode: + case waiter <- node: default: close(waiter) } @@ -86,7 +91,7 @@ type infoQuery struct { Context context.Context } -func (cli *Client) sendIQAsync(query infoQuery) (<-chan *waBinary.Node, error) { +func (cli *Client) sendIQAsyncDebug(query infoQuery) (<-chan *waBinary.Node, []byte, error) { if len(query.ID) == 0 { query.ID = cli.generateRequestID() } @@ -102,20 +107,25 @@ func (cli *Client) sendIQAsync(query infoQuery) (<-chan *waBinary.Node, error) { if !query.Target.IsEmpty() { attrs["target"] = query.Target } - err := cli.sendNode(waBinary.Node{ + data, err := cli.sendNodeDebug(waBinary.Node{ Tag: "iq", Attrs: attrs, Content: query.Content, }) if err != nil { cli.cancelResponse(query.ID, waiter) - return nil, err + return nil, data, err } - return waiter, nil + return waiter, data, nil +} + +func (cli *Client) sendIQAsync(query infoQuery) (<-chan *waBinary.Node, error) { + ch, _, err := cli.sendIQAsyncDebug(query) + return ch, err } func (cli *Client) sendIQ(query infoQuery) (*waBinary.Node, error) { - resChan, err := cli.sendIQAsync(query) + resChan, data, err := cli.sendIQAsyncDebug(query) if err != nil { return nil, err } @@ -127,8 +137,11 @@ func (cli *Client) sendIQ(query infoQuery) (*waBinary.Node, error) { } select { case res := <-resChan: - if res == closedNode { - return nil, ErrIQDisconnected + if isDisconnectNode(res) { + if cli.DebugDecodeBeforeSend && res.Tag == "stream:error" && res.GetChildByTag("xml-not-well-formed").Tag != "" { + cli.Log.Debugf("Info query that was interrupted by xml-not-well-formed: %s", base64.URLEncoding.EncodeToString(data)) + } + return nil, &DisconnectedError{Action: "info query", Node: res} } resType, _ := res.Attrs["type"].(string) if res.Tag != "iq" || (resType != "result" && resType != "error") { diff --git a/vendor/go.mau.fi/whatsmeow/retry.go b/vendor/go.mau.fi/whatsmeow/retry.go index 5cc460f9..a5a56d8f 100644 --- a/vendor/go.mau.fi/whatsmeow/retry.go +++ b/vendor/go.mau.fi/whatsmeow/retry.go @@ -148,6 +148,13 @@ func (cli *Client) handleRetryReceipt(receipt *events.Receipt, node *waBinary.No } else if bundle == nil { return fmt.Errorf("didn't get prekey bundle for %s (response size: %d)", senderAD, len(keys)) } + if retryCount > 3 { + cli.Log.Debugf("Erasing existing session for %s due to retry receipt with count>3", receipt.Sender) + err = cli.Store.Sessions.DeleteSession(receipt.Sender.SignalAddress().String()) + if err != nil { + return fmt.Errorf("failed to delete session for %s: %w", senderAD, err) + } + } } encrypted, includeDeviceIdentity, err := cli.encryptMessageForDevice(plaintext, receipt.Sender, bundle) if err != nil { @@ -161,6 +168,9 @@ func (cli *Client) handleRetryReceipt(receipt *events.Receipt, node *waBinary.No "id": messageID, "t": timestamp.Unix(), } + if !receipt.IsGroup { + attrs["device_fanout"] = false + } if participant, ok := node.Attrs["participant"]; ok { attrs["participant"] = participant } diff --git a/vendor/go.mau.fi/whatsmeow/send.go b/vendor/go.mau.fi/whatsmeow/send.go index 041f7fdf..9e520fb0 100644 --- a/vendor/go.mau.fi/whatsmeow/send.go +++ b/vendor/go.mau.fi/whatsmeow/send.go @@ -73,15 +73,21 @@ func (cli *Client) SendMessage(to types.JID, id types.MessageID, message *waProt id = GenerateMessageID() } + if cli.OneMessageAtATime { + cli.messageSendLock.Lock() + defer cli.messageSendLock.Unlock() + } + cli.addRecentMessage(to, id, message) respChan := cli.waitResponse(id) var err error var phash string + var data []byte switch to.Server { case types.GroupServer: - phash, err = cli.sendGroup(to, id, message) + phash, data, err = cli.sendGroup(to, id, message) case types.DefaultUserServer: - err = cli.sendDM(to, id, message) + data, err = cli.sendDM(to, id, message) case types.BroadcastServer: err = ErrBroadcastListUnsupported default: @@ -92,8 +98,11 @@ func (cli *Client) SendMessage(to types.JID, id types.MessageID, message *waProt return time.Time{}, err } resp := <-respChan - if resp == closedNode { - return time.Time{}, ErrSendDisconnected + if isDisconnectNode(resp) { + if cli.DebugDecodeBeforeSend && resp.Tag == "stream:error" && resp.GetChildByTag("xml-not-well-formed").Tag != "" { + cli.Log.Debugf("Message that was interrupted by xml-not-well-formed: %s", base64.URLEncoding.EncodeToString(data)) + } + return time.Time{}, &DisconnectedError{Action: "message send", Node: resp} } ag := resp.AttrGetter() ts := time.Unix(ag.Int64("t"), 0) @@ -137,22 +146,22 @@ func participantListHashV2(participants []types.JID) string { return fmt.Sprintf("2:%s", base64.RawStdEncoding.EncodeToString(hash[:6])) } -func (cli *Client) sendGroup(to types.JID, id types.MessageID, message *waProto.Message) (string, error) { +func (cli *Client) sendGroup(to types.JID, id types.MessageID, message *waProto.Message) (string, []byte, error) { participants, err := cli.getGroupMembers(to) if err != nil { - return "", fmt.Errorf("failed to get group members: %w", err) + return "", nil, fmt.Errorf("failed to get group members: %w", err) } plaintext, _, err := marshalMessage(to, message) if err != nil { - return "", err + return "", nil, err } builder := groups.NewGroupSessionBuilder(cli.Store, pbSerializer) senderKeyName := protocol.NewSenderKeyName(to.String(), cli.Store.ID.SignalAddress()) signalSKDMessage, err := builder.Create(senderKeyName) if err != nil { - return "", fmt.Errorf("failed to create sender key distribution message to send %s to %s: %w", id, to, err) + return "", nil, fmt.Errorf("failed to create sender key distribution message to send %s to %s: %w", id, to, err) } skdMessage := &waProto.Message{ SenderKeyDistributionMessage: &waProto.SenderKeyDistributionMessage{ @@ -162,19 +171,19 @@ func (cli *Client) sendGroup(to types.JID, id types.MessageID, message *waProto. } skdPlaintext, err := proto.Marshal(skdMessage) if err != nil { - return "", fmt.Errorf("failed to marshal sender key distribution message to send %s to %s: %w", id, to, err) + return "", nil, fmt.Errorf("failed to marshal sender key distribution message to send %s to %s: %w", id, to, err) } cipher := groups.NewGroupCipher(builder, senderKeyName, cli.Store) encrypted, err := cipher.Encrypt(padMessage(plaintext)) if err != nil { - return "", fmt.Errorf("failed to encrypt group message to send %s to %s: %w", id, to, err) + return "", nil, fmt.Errorf("failed to encrypt group message to send %s to %s: %w", id, to, err) } ciphertext := encrypted.SignedSerialize() node, allDevices, err := cli.prepareMessageNode(to, id, message, participants, skdPlaintext, nil) if err != nil { - return "", err + return "", nil, err } phash := participantListHashV2(allDevices) @@ -185,28 +194,28 @@ func (cli *Client) sendGroup(to types.JID, id types.MessageID, message *waProto. Attrs: waBinary.Attrs{"v": "2", "type": "skmsg"}, }) - err = cli.sendNode(*node) + data, err := cli.sendNodeDebug(*node) if err != nil { - return "", fmt.Errorf("failed to send message node: %w", err) + return "", nil, fmt.Errorf("failed to send message node: %w", err) } - return phash, nil + return phash, data, nil } -func (cli *Client) sendDM(to types.JID, id types.MessageID, message *waProto.Message) error { +func (cli *Client) sendDM(to types.JID, id types.MessageID, message *waProto.Message) ([]byte, error) { messagePlaintext, deviceSentMessagePlaintext, err := marshalMessage(to, message) if err != nil { - return err + return nil, err } - node, _, err := cli.prepareMessageNode(to, id, message, []types.JID{to, *cli.Store.ID}, messagePlaintext, deviceSentMessagePlaintext) + node, _, err := cli.prepareMessageNode(to, id, message, []types.JID{to, cli.Store.ID.ToNonAD()}, messagePlaintext, deviceSentMessagePlaintext) if err != nil { - return err + return nil, err } - err = cli.sendNode(*node) + data, err := cli.sendNodeDebug(*node) if err != nil { - return fmt.Errorf("failed to send message node: %w", err) + return nil, fmt.Errorf("failed to send message node: %w", err) } - return nil + return data, nil } func (cli *Client) prepareMessageNode(to types.JID, id types.MessageID, message *waProto.Message, participants []types.JID, plaintext, dsmPlaintext []byte) (*waBinary.Node, []types.JID, error) { @@ -307,7 +316,7 @@ func (cli *Client) encryptMessageForDevices(allDevices []types.JID, id string, m if len(retryDevices) > 0 { bundles, err := cli.fetchPreKeys(retryDevices) if err != nil { - cli.Log.Warnf("Failed to fetch prekeys for %d to retry encryption: %v", retryDevices, err) + cli.Log.Warnf("Failed to fetch prekeys for %v to retry encryption: %v", retryDevices, err) } else { for _, jid := range retryDevices { resp := bundles[jid] diff --git a/vendor/go.mau.fi/whatsmeow/types/events/events.go b/vendor/go.mau.fi/whatsmeow/types/events/events.go index 01fc8dc9..8b9e9a4f 100644 --- a/vendor/go.mau.fi/whatsmeow/types/events/events.go +++ b/vendor/go.mau.fi/whatsmeow/types/events/events.go @@ -347,11 +347,18 @@ type OfflineSyncCompleted struct { Count int } +type MediaRetryError struct { + Code int +} + // MediaRetry is emitted when the phone sends a response to a media retry request. type MediaRetry struct { Ciphertext []byte IV []byte + // Sometimes there's an unencrypted media retry error. In these cases, Ciphertext and IV will be nil. + Error *MediaRetryError + Timestamp time.Time // The time of the response. MessageID types.MessageID // The ID of the message. |