diff --git a/internals.go b/internals.go index e0d86847c..2986c40a4 100644 --- a/internals.go +++ b/internals.go @@ -603,8 +603,8 @@ func (int *DangerousInternalClient) ClearDelayedMessageRequests() { int.c.clearDelayedMessageRequests() } -func (int *DangerousInternalClient) SendRetryReceipt(ctx context.Context, node *waBinary.Node, info *types.MessageInfo, forceIncludeIdentity bool) { - int.c.sendRetryReceipt(ctx, node, info, forceIncludeIdentity) +func (int *DangerousInternalClient) SendRetryReceipt(ctx context.Context, node *waBinary.Node, info *types.MessageInfo, forceIncludeIdentity bool, errorCode int) { + int.c.sendRetryReceipt(ctx, node, info, forceIncludeIdentity, errorCode) } func (int *DangerousInternalClient) SendGroupV3(ctx context.Context, to, ownID types.JID, id types.MessageID, messageApp []byte, msgAttrs messageAttrs, frankingTag []byte, timings *MessageDebugTimings) (string, []byte, error) { diff --git a/message.go b/message.go index f7895b792..5db36746b 100644 --- a/message.go +++ b/message.go @@ -379,16 +379,19 @@ func (cli *Client) decryptMessages(ctx context.Context, info *types.MessageInfo, return } isUnavailable := encType == "skmsg" && !containsDirectMsg && errors.Is(err, signalerror.ErrNoSenderKeyForUser) + + retryReason := getRetryReasonFromError(err) + if encType == "msmsg" { cli.backgroundIfAsyncAck(func() { cli.sendAck(node, NackMissingMessageSecret) }) } else if cli.SynchronousAck { - cli.sendRetryReceipt(ctx, node, info, isUnavailable) + cli.sendRetryReceipt(ctx, node, info, isUnavailable, retryReason) // TODO this probably isn't supposed to ack cli.sendAck(node, 0) } else { - go cli.sendRetryReceipt(context.WithoutCancel(ctx), node, info, isUnavailable) + go cli.sendRetryReceipt(context.WithoutCancel(ctx), node, info, isUnavailable, retryReason) go cli.sendAck(node, 0) } cli.dispatchEvent(&events.UndecryptableMessage{ diff --git a/retry.go b/retry.go index a7c2ffee8..f768cb6f6 100644 --- a/retry.go +++ b/retry.go @@ -11,9 +11,12 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/binary" + "errors" "fmt" "time" + "go.mau.fi/libsignal/signalerror" + "go.mau.fi/libsignal/ecc" "go.mau.fi/libsignal/groups" "go.mau.fi/libsignal/keys/prekey" @@ -43,6 +46,48 @@ type RecentMessage struct { fb *waMsgApplication.MessageApplication } +var ( + RetryReasonUnknownError = 0 + RetryReasonSignalErrorNoSession = 1 + RetryReasonSignalErrorInvalidKey = 2 + RetryReasonSignalErrorInvalidKeyId = 3 + RetryReasonSignalErrorInvalidMessage = 4 + RetryReasonSignalErrorInvalidSignature = 5 + RetryReasonSignalErrorFutureMessage = 6 + RetryReasonSignalErrorBadMac = 7 + RetryReasonSignalErrorInvalidSession = 8 + RetryReasonSignalErrorInvalidMsgKey = 9 + RetryReasonBadBroadcastEphemeralSetting = 10 + RetryReasonUnknownCompanionNoPrekey = 11 + RetryReasonAdvFailure = 12 + RetryReasonStatusRevokeDelay = 13 +) + +func getRetryReasonFromError(err error) int { + switch { + case errors.Is(err, signalerror.ErrBadMAC): + return RetryReasonSignalErrorBadMac + case errors.Is(err, signalerror.ErrNoSessionForUser): + case errors.Is(err, signalerror.ErrNoSenderKeyForUser): + return RetryReasonSignalErrorNoSession + case errors.Is(err, signalerror.ErrWrongMessageVersion): + case errors.Is(err, signalerror.ErrOldMessageVersion): + case errors.Is(err, signalerror.ErrUnknownMessageVersion): + case errors.Is(err, signalerror.ErrIncompleteMessage): + return RetryReasonSignalErrorInvalidMessage + case errors.Is(err, signalerror.ErrInvalidSignature): + case errors.Is(err, signalerror.ErrSenderKeyStateVerificationFailed): + return RetryReasonSignalErrorInvalidSignature + case errors.Is(err, signalerror.ErrNoSignedPreKey): + return RetryReasonSignalErrorInvalidKey + case errors.Is(err, signalerror.ErrNoSenderKeyStateForID): + return RetryReasonSignalErrorInvalidKeyId + case errors.Is(err, signalerror.ErrTooFarIntoFuture): + return RetryReasonSignalErrorFutureMessage + } + return RetryReasonUnknownError +} + func (rm RecentMessage) IsEmpty() bool { return rm.wa == nil && rm.fb == nil } @@ -376,7 +421,7 @@ func (cli *Client) clearDelayedMessageRequests() { } // sendRetryReceipt sends a retry receipt for an incoming message. -func (cli *Client) sendRetryReceipt(ctx context.Context, node *waBinary.Node, info *types.MessageInfo, forceIncludeIdentity bool) { +func (cli *Client) sendRetryReceipt(ctx context.Context, node *waBinary.Node, info *types.MessageInfo, forceIncludeIdentity bool, errorCode int) { id, _ := node.Attrs["id"].(string) children := node.GetChildren() var retryCountInMsg int @@ -412,16 +457,23 @@ func (cli *Client) sendRetryReceipt(ctx context.Context, node *waBinary.Node, in if info.Type == "peer_msg" && info.IsFromMe { attrs["category"] = "peer" } + + retryAttrs := waBinary.Attrs{ + "count": retryCount, + "id": id, + "t": node.Attrs["t"], + "v": 1, + } + + if errorCode != 0 { + retryAttrs["error"] = errorCode + } + payload := waBinary.Node{ Tag: "receipt", Attrs: attrs, Content: []waBinary.Node{ - {Tag: "retry", Attrs: waBinary.Attrs{ - "count": retryCount, - "id": id, - "t": node.Attrs["t"], - "v": 1, - }}, + {Tag: "retry", Attrs: retryAttrs}, {Tag: "registration", Content: registrationIDBytes[:]}, }, }