162 lines
5.4 KiB
Go
162 lines
5.4 KiB
Go
package mtprotoedge
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/iamxvbaba/td/bin"
|
|
"github.com/iamxvbaba/td/mt"
|
|
"github.com/iamxvbaba/td/proto"
|
|
"github.com/iamxvbaba/td/tg"
|
|
"github.com/iamxvbaba/td/tlprofile"
|
|
)
|
|
|
|
// preparedOnlyLayerRPCResult is intentionally incapable of encoding. The
|
|
// binding guard must reject it solely from immutable admission identity before
|
|
// any result method other than Prepared can be observed.
|
|
type preparedOnlyLayerRPCResult struct {
|
|
tlprofile.Result
|
|
prepared tlprofile.PreparedCall
|
|
}
|
|
|
|
func (r *preparedOnlyLayerRPCResult) Prepared() tlprofile.PreparedCall { return r.prepared }
|
|
|
|
func TestBindAdmittedLayerRPCResultRequiresExactRequestIdentity(t *testing.T) {
|
|
dispatcher := tlprofile.NewDispatcher()
|
|
admit := func(request bin.Encoder) tlprofile.Admission {
|
|
t.Helper()
|
|
body := &bin.Buffer{Buf: exactLayerRPCBody(t, request)}
|
|
admitted, err := dispatcher.Admit(tlprofile.Profile227, body, tlprofile.Limits{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return admitted
|
|
}
|
|
|
|
request := admit(&tg.HelpGetConfigRequest{})
|
|
other := admit(&tg.HelpGetNearestDCRequest{})
|
|
|
|
if _, err := bindAdmittedLayerRPCResult(request, &preparedOnlyLayerRPCResult{prepared: other.Prepared()}); !errors.Is(err, errLayerRPCResultIdentityMismatch) {
|
|
t.Fatalf("mismatched result error = %v, want %v", err, errLayerRPCResultIdentityMismatch)
|
|
}
|
|
|
|
bound, err := bindAdmittedLayerRPCResult(request, &preparedOnlyLayerRPCResult{prepared: request.Prepared()})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if bound == nil || bound.call.Identity() != request.Call().Identity() {
|
|
t.Fatal("matching result did not retain the admitted call identity")
|
|
}
|
|
}
|
|
|
|
type mismatchedProjectionLayerRPC struct {
|
|
*admissionOnlyLayerRPC
|
|
result tlprofile.Result
|
|
calls atomic.Int32
|
|
}
|
|
|
|
func (h *mismatchedProjectionLayerRPC) DispatchAdmitted(
|
|
context.Context,
|
|
[8]byte,
|
|
int64,
|
|
int64,
|
|
uint64,
|
|
tlprofile.Admission,
|
|
) (tlprofile.Result, string, error) {
|
|
h.calls.Add(1)
|
|
return h.result, "help.getConfig", nil
|
|
}
|
|
|
|
func TestProjectionFailureCachesInternalWithoutRepeatingBusiness(t *testing.T) {
|
|
dispatcher := tlprofile.NewDispatcher()
|
|
admit := func(request bin.Encoder) tlprofile.Admission {
|
|
t.Helper()
|
|
body := &bin.Buffer{Buf: exactLayerRPCBody(t, request)}
|
|
admitted, err := dispatcher.Admit(tlprofile.Profile227, body, tlprofile.Limits{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return admitted
|
|
}
|
|
request := admit(&tg.HelpGetConfigRequest{})
|
|
other := admit(&tg.HelpGetNearestDCRequest{})
|
|
handler := &mismatchedProjectionLayerRPC{
|
|
admissionOnlyLayerRPC: newAdmissionOnlyLayerRPC(),
|
|
result: &preparedOnlyLayerRPCResult{prepared: other.Prepared()},
|
|
}
|
|
s := New(Options{DC: 2, LayerRPC: handler})
|
|
c := newOutboundTestConn(t, &collectingSessionTransport{}, newOutboundTrackedBudget(1<<20))
|
|
c.authKeyID = [8]byte{0x41, 0x01}
|
|
c.sessionID = 4101
|
|
const reqMsgID = int64(410100)
|
|
claim, err := s.rpcResults.AcquireLayerIdentified(
|
|
c.authKeyID, c.sessionID, reqMsgID,
|
|
tlprofile.Profile227, request.Prepared().Identity(),
|
|
)
|
|
if err != nil || claim.owner == nil {
|
|
t.Fatalf("owner acquisition err=%v", err)
|
|
}
|
|
if err := s.handleAdmittedLayerRPC(
|
|
context.Background(), c, reqMsgID, claim.admissionSeq,
|
|
"help.getConfig", request, claim.owner,
|
|
); err != nil {
|
|
t.Fatalf("publish projection failure: %v", err)
|
|
}
|
|
|
|
deadline := time.Now().Add(2 * time.Second)
|
|
var completed rpcResultAcquire
|
|
for {
|
|
completed, err = s.rpcResults.AcquireLayerIdentified(
|
|
c.authKeyID, c.sessionID, reqMsgID,
|
|
tlprofile.Profile227, request.Prepared().Identity(),
|
|
)
|
|
if err == nil && completed.state == rpcResultAcquireCompleted {
|
|
break
|
|
}
|
|
if time.Now().After(deadline) {
|
|
t.Fatalf("projection failure did not become completed: state=%d err=%v", completed.state, err)
|
|
}
|
|
time.Sleep(time.Millisecond)
|
|
}
|
|
if !completed.executionKnown || !completed.executionOK {
|
|
t.Fatalf("projection failure lost successful business outcome: known=%v ok=%v", completed.executionKnown, completed.executionOK)
|
|
}
|
|
if got := handler.calls.Load(); got != 1 {
|
|
t.Fatalf("business calls=%d, want 1", got)
|
|
}
|
|
var envelope proto.Result
|
|
if err := envelope.Decode(&bin.Buffer{Buf: completed.encoded.body}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var rpcErr mt.RPCError
|
|
if err := rpcErr.Decode(&bin.Buffer{Buf: envelope.Result}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if rpcErr.ErrorCode != 500 || rpcErr.ErrorMessage != "INTERNAL" {
|
|
t.Fatalf("projection terminal = %+v", rpcErr)
|
|
}
|
|
if _, ok := s.rpcResults.Get(c.authKeyID, c.sessionID, reqMsgID); !ok {
|
|
t.Fatal("projection receipt disappeared before duplicate acquire")
|
|
}
|
|
// A same-msg replay is served from the completed exact identity; there is no
|
|
// second DispatchAdmitted call even though projection failed after business
|
|
// success.
|
|
replay, err := s.rpcResults.AcquireLayerIdentified(
|
|
c.authKeyID, c.sessionID, reqMsgID,
|
|
tlprofile.Profile227, request.Prepared().Identity(),
|
|
)
|
|
if err != nil || replay.state != rpcResultAcquireCompleted {
|
|
t.Fatalf("projection replay = state:%d err:%v", replay.state, err)
|
|
}
|
|
if replay.encoded == nil || replay.encoded.replayMsgID != completed.encoded.replayMsgID ||
|
|
replay.encoded.replaySeqNo != completed.encoded.replaySeqNo ||
|
|
!sameBacking(replay.encoded.body, completed.encoded.body) {
|
|
t.Fatal("projection replay did not reference the original logical-outbox frame")
|
|
}
|
|
if got := handler.calls.Load(); got != 1 {
|
|
t.Fatalf("replay repeated business calls=%d", got)
|
|
}
|
|
}
|