1f2e74acca
- 完善 stream/bulk DataID 分配、预留和双向命名空间,修复并发打开及 dedicated/shared 回退时的 ID 冲突 - 将收发、回复、恢复任务和 sidecar 绑定原始会话与物理连接,防止重连后的旧消息误操作新连接 - 加强 close/reset 身份校验及实例移除检查,修复 dedicated attach 失败、通道引用和资源回收竞态 - 收紧批量发送器停止准入,确保在途入队完成后统一清理请求、缓冲区和等待者 - 修复 record 满队列死锁、取消时序号消耗及关闭竞态,确保关闭有界并返回真实错误 - 增加协商式 record 逻辑半关闭,保留反向 ACK;通过 reset 传递 RecordFailure,避免背压掩盖原始失败原因 - 补齐帧长度、批次数量、序号溢出和未确认窗口校验,提前拒绝超限数据并按字节预算拆批 - 为入站分发增加全局及单连接的条数、字节预算和阻塞背压,关闭时唤醒等待者,消除正常断连日志噪音 - 完善 bulk 窗口释放失败处理与传输诊断,补充并发、重连、背压、协议边界及真实 TCP 回归覆盖
783 lines
27 KiB
Go
783 lines
27 KiB
Go
package notify
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/binary"
|
|
"errors"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"b612.me/stario"
|
|
)
|
|
|
|
type bulkAttachScriptConn struct {
|
|
readBuf *bytes.Reader
|
|
writeBuf bytes.Buffer
|
|
}
|
|
|
|
func newBulkAttachScriptConn(inbound []byte) *bulkAttachScriptConn {
|
|
return &bulkAttachScriptConn{
|
|
readBuf: bytes.NewReader(append([]byte(nil), inbound...)),
|
|
}
|
|
}
|
|
|
|
func (c *bulkAttachScriptConn) Read(p []byte) (int, error) { return c.readBuf.Read(p) }
|
|
func (c *bulkAttachScriptConn) Write(p []byte) (int, error) { return c.writeBuf.Write(p) }
|
|
func (c *bulkAttachScriptConn) Close() error { return nil }
|
|
func (c *bulkAttachScriptConn) LocalAddr() net.Addr { return bulkAttachTestAddr("local") }
|
|
func (c *bulkAttachScriptConn) RemoteAddr() net.Addr { return bulkAttachTestAddr("remote") }
|
|
func (c *bulkAttachScriptConn) SetDeadline(time.Time) error { return nil }
|
|
func (c *bulkAttachScriptConn) SetReadDeadline(time.Time) error {
|
|
return nil
|
|
}
|
|
func (c *bulkAttachScriptConn) SetWriteDeadline(time.Time) error {
|
|
return nil
|
|
}
|
|
|
|
func (c *bulkAttachScriptConn) writtenBytes() []byte {
|
|
return append([]byte(nil), c.writeBuf.Bytes()...)
|
|
}
|
|
|
|
type bulkAttachTestAddr string
|
|
|
|
func (a bulkAttachTestAddr) Network() string { return "tcp" }
|
|
func (a bulkAttachTestAddr) String() string { return string(a) }
|
|
|
|
func encodeDedicatedRecordForAttachTest(payload []byte) []byte {
|
|
out := make([]byte, bulkDedicatedRecordHeaderLen+len(payload))
|
|
copy(out[:4], bulkDedicatedRecordMagic)
|
|
binary.BigEndian.PutUint32(out[4:8], uint32(len(payload)))
|
|
copy(out[bulkDedicatedRecordHeaderLen:], payload)
|
|
return out
|
|
}
|
|
|
|
func TestSendDedicatedBulkAttachRequestKeepsCoalescedDedicatedPayloadUnread(t *testing.T) {
|
|
client := NewClient().(*ClientCommon)
|
|
UseLegacySecurityClient(client)
|
|
client.msgID = 100
|
|
|
|
bulk := newBulkHandle(context.Background(), newBulkRuntime("dedicated-attach-test"), clientFileScope(), BulkOpenRequest{
|
|
BulkID: "bulk-attach-test",
|
|
DataID: 1,
|
|
Dedicated: true,
|
|
AttachToken: "attach-token",
|
|
}, 0, nil, nil, 0, nil, nil, nil, nil, nil)
|
|
|
|
encodedResp, err := client.sequenceEn(bulkAttachResponse{Accepted: true})
|
|
if err != nil {
|
|
t.Fatalf("encode bulkAttachResponse failed: %v", err)
|
|
}
|
|
replyFrame, err := encodeDirectSignalFrame(stario.NewQueue(), client.sequenceEn, client.msgEn, client.SecretKey, TransferMsg{
|
|
ID: 101,
|
|
Key: systemBulkAttachKey,
|
|
Value: encodedResp,
|
|
Type: MSG_SYS_REPLY,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("encode attach reply frame failed: %v", err)
|
|
}
|
|
dedicatedPayload := []byte("dedicated-tail-bytes")
|
|
conn := newBulkAttachScriptConn(append(replyFrame, encodeDedicatedRecordForAttachTest(dedicatedPayload)...))
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
resp, err := client.sendDedicatedBulkAttachRequest(ctx, conn, bulk)
|
|
if err != nil {
|
|
t.Fatalf("sendDedicatedBulkAttachRequest failed: %v", err)
|
|
}
|
|
if !resp.Accepted {
|
|
t.Fatalf("bulk attach response = %+v, want accepted", resp)
|
|
}
|
|
|
|
parsedReq := stario.NewQueue()
|
|
var reqMsg TransferMsg
|
|
if err := parsedReq.ParseMessageOwned(conn.writtenBytes(), "attach-request", func(msgq stario.MsgQueue) error {
|
|
transfer, err := decodeDirectSignalPayload(client.sequenceDe, client.msgDe, client.SecretKey, msgq.Msg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
reqMsg = transfer
|
|
return nil
|
|
}); err != nil {
|
|
t.Fatalf("parse written attach request failed: %v", err)
|
|
}
|
|
if reqMsg.Key != systemBulkAttachKey || reqMsg.Type != MSG_SYS_WAIT {
|
|
t.Fatalf("attach request message mismatch: %+v", reqMsg)
|
|
}
|
|
readPayload, err := readBulkDedicatedRecord(conn)
|
|
if err != nil {
|
|
t.Fatalf("readBulkDedicatedRecord after attach failed: %v", err)
|
|
}
|
|
if !bytes.Equal(readPayload, dedicatedPayload) {
|
|
t.Fatalf("dedicated payload mismatch: got %q want %q", string(readPayload), string(dedicatedPayload))
|
|
}
|
|
}
|
|
|
|
func TestSendDedicatedBulkAttachRequestUsesBootstrapProtectionEvenAfterSteadySwitch(t *testing.T) {
|
|
client := NewClient().(*ClientCommon)
|
|
if err := UseModernPSKClient(client, integrationSharedSecret, integrationModernPSKOptions()); err != nil {
|
|
t.Fatalf("UseModernPSKClient failed: %v", err)
|
|
}
|
|
client.msgID = 100
|
|
|
|
alternate, err := deriveModernPSKProtectionProfile([]byte("notify-dedicated-attach-other-secret"), integrationModernPSKOptions(), ProtectionManaged)
|
|
if err != nil {
|
|
t.Fatalf("deriveModernPSKProtectionProfile(alternate) failed: %v", err)
|
|
}
|
|
client.setClientTransportProtectionProfile(alternate)
|
|
|
|
bulk := newBulkHandle(context.Background(), newBulkRuntime("dedicated-attach-bootstrap-test"), clientFileScope(), BulkOpenRequest{
|
|
BulkID: "bulk-attach-bootstrap-test",
|
|
DataID: 1,
|
|
Dedicated: true,
|
|
AttachToken: "attach-token",
|
|
}, 0, nil, nil, 0, nil, nil, nil, nil, nil)
|
|
|
|
bootstrap := client.clientDedicatedBulkAttachTransportProtectionProfile()
|
|
encodedResp, err := client.sequenceEn(bulkAttachResponse{Accepted: true})
|
|
if err != nil {
|
|
t.Fatalf("encode bulkAttachResponse failed: %v", err)
|
|
}
|
|
replyFrame, err := encodeDirectSignalFrame(stario.NewQueue(), client.sequenceEn, bootstrap.msgEn, bootstrap.secretKey, TransferMsg{
|
|
ID: 101,
|
|
Key: systemBulkAttachKey,
|
|
Value: encodedResp,
|
|
Type: MSG_SYS_REPLY,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("encode attach reply frame failed: %v", err)
|
|
}
|
|
conn := newBulkAttachScriptConn(replyFrame)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
resp, err := client.sendDedicatedBulkAttachRequest(ctx, conn, bulk)
|
|
if err != nil {
|
|
t.Fatalf("sendDedicatedBulkAttachRequest failed: %v", err)
|
|
}
|
|
if !resp.Accepted {
|
|
t.Fatalf("bulk attach response = %+v, want accepted", resp)
|
|
}
|
|
|
|
parsedReq := stario.NewQueue()
|
|
var reqMsg TransferMsg
|
|
if err := parsedReq.ParseMessageOwned(conn.writtenBytes(), "attach-request", func(msgq stario.MsgQueue) error {
|
|
transfer, err := decodeDirectSignalPayload(client.sequenceDe, bootstrap.msgDe, bootstrap.secretKey, msgq.Msg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
reqMsg = transfer
|
|
return nil
|
|
}); err != nil {
|
|
t.Fatalf("parse written attach request with bootstrap profile failed: %v", err)
|
|
}
|
|
if reqMsg.Key != systemBulkAttachKey || reqMsg.Type != MSG_SYS_WAIT {
|
|
t.Fatalf("attach request message mismatch: %+v", reqMsg)
|
|
}
|
|
|
|
if err := parsedReq.ParseMessageOwned(conn.writtenBytes(), "attach-request-current", func(msgq stario.MsgQueue) error {
|
|
_, err := decodeDirectSignalPayload(client.sequenceDe, alternate.msgDe, alternate.secretKey, msgq.Msg)
|
|
return err
|
|
}); !errors.Is(err, errTransportPayloadDecryptFailed) {
|
|
t.Fatalf("decode written attach request with current steady profile error = %v, want %v", err, errTransportPayloadDecryptFailed)
|
|
}
|
|
}
|
|
|
|
func TestAttachDedicatedBulkSidecarStopsWhenOriginalRouteReattaches(t *testing.T) {
|
|
client := NewClient().(*ClientCommon)
|
|
UseLegacySecurityClient(client)
|
|
stopCtx, stopFn := context.WithCancel(context.Background())
|
|
defer stopFn()
|
|
queue := stario.NewQueueCtx(stopCtx, 4, ^uint32(0))
|
|
firstLeft, firstRight := net.Pipe()
|
|
defer firstRight.Close()
|
|
epoch := client.beginClientSessionEpoch()
|
|
client.setClientSessionRuntime(newClientSessionRuntime(firstLeft, stopCtx, stopFn, queue, epoch))
|
|
client.markSessionStarted()
|
|
defer client.markSessionStopped("test done", nil)
|
|
|
|
route := client.clientSessionRouteSnapshot()
|
|
bulk := newBulkHandle(stopCtx, client.getBulkRuntime(), clientFileScope(), BulkOpenRequest{
|
|
BulkID: "dedicated-original-route",
|
|
DataID: 1,
|
|
Dedicated: true,
|
|
DedicatedLaneID: 1,
|
|
AttachToken: "attach-token",
|
|
}, epoch, nil, nil, 0, nil, nil, nil, nil, nil)
|
|
bulk.setClientSnapshotOwner(client)
|
|
bulk.setClientSessionRoute(route)
|
|
defer bulk.finalize()
|
|
|
|
client.bulkDedicatedAttachSem = make(chan struct{}, 1)
|
|
client.bulkDedicatedAttachSem <- struct{}{}
|
|
defer func() {
|
|
select {
|
|
case <-client.bulkDedicatedAttachSem:
|
|
default:
|
|
}
|
|
}()
|
|
result := make(chan error, 1)
|
|
go func() {
|
|
result <- client.attachDedicatedBulkSidecar(context.Background(), bulk)
|
|
}()
|
|
|
|
deadline := time.Now().Add(time.Second)
|
|
for time.Now().Before(deadline) {
|
|
client.bulkDedicatedSidecarMu.Lock()
|
|
lane := client.bulkDedicatedLanes[1]
|
|
blocked := lane != nil && lane.attachFlight != nil
|
|
client.bulkDedicatedSidecarMu.Unlock()
|
|
if blocked {
|
|
break
|
|
}
|
|
time.Sleep(time.Millisecond)
|
|
}
|
|
client.bulkDedicatedSidecarMu.Lock()
|
|
lane := client.bulkDedicatedLanes[1]
|
|
blocked := lane != nil && lane.attachFlight != nil
|
|
client.bulkDedicatedSidecarMu.Unlock()
|
|
if !blocked {
|
|
t.Fatal("dedicated attach did not reach the blocked attach slot")
|
|
}
|
|
|
|
secondLeft, secondRight := net.Pipe()
|
|
defer secondRight.Close()
|
|
if err := client.attachClientSessionTransport(secondLeft); err != nil {
|
|
t.Fatalf("attach replacement client transport: %v", err)
|
|
}
|
|
select {
|
|
case err := <-result:
|
|
if !errors.Is(err, errTransportDetached) {
|
|
t.Fatalf("dedicated attach error = %v, want transport detached", err)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("dedicated attach remained blocked after original route detached")
|
|
}
|
|
if sidecar := client.clientDedicatedSidecarSnapshotForLane(1); sidecar != nil {
|
|
t.Fatalf("stale route installed dedicated sidecar: %+v", sidecar)
|
|
}
|
|
}
|
|
|
|
func TestClientDedicatedSidecarRejectsLateOldRoutePublicationAndRelease(t *testing.T) {
|
|
client := NewClient().(*ClientCommon)
|
|
UseLegacySecurityClient(client)
|
|
stopCtx, stopFn := context.WithCancel(context.Background())
|
|
defer stopFn()
|
|
queue := stario.NewQueueCtx(stopCtx, 4, ^uint32(0))
|
|
firstLeft, firstRight := net.Pipe()
|
|
defer firstRight.Close()
|
|
epoch := client.beginClientSessionEpoch()
|
|
client.setClientSessionRuntime(newClientSessionRuntime(firstLeft, stopCtx, stopFn, queue, epoch))
|
|
client.markSessionStarted()
|
|
defer client.markSessionStopped("test done", nil)
|
|
|
|
oldRoute := client.clientSessionRouteSnapshot()
|
|
oldLane, err := client.reserveBulkDedicatedLaneAtRoute(oldRoute)
|
|
if err != nil {
|
|
t.Fatalf("reserve old-route lane: %v", err)
|
|
}
|
|
oldSidecarLeft, oldSidecarRight := net.Pipe()
|
|
defer oldSidecarRight.Close()
|
|
oldSidecar := newBulkDedicatedSidecar(oldSidecarLeft, oldLane)
|
|
if _, installed, err := client.installClientDedicatedSidecarAtRoute(oldLane, oldSidecar, oldRoute); err != nil || !installed {
|
|
t.Fatalf("install old-route sidecar = installed=%v err=%v, want installed", installed, err)
|
|
}
|
|
|
|
secondLeft, secondRight := net.Pipe()
|
|
defer secondRight.Close()
|
|
if err := client.attachClientSessionTransport(secondLeft); err != nil {
|
|
t.Fatalf("attach replacement client transport: %v", err)
|
|
}
|
|
newRoute := client.clientSessionRouteSnapshot()
|
|
lateLeft, lateRight := net.Pipe()
|
|
defer lateRight.Close()
|
|
lateSidecar := newBulkDedicatedSidecar(lateLeft, oldLane)
|
|
if _, installed, err := client.installClientDedicatedSidecarAtRoute(oldLane, lateSidecar, oldRoute); err == nil || installed {
|
|
lateSidecar.close()
|
|
t.Fatalf("late old-route sidecar install = installed=%v err=%v, want rejection", installed, err)
|
|
}
|
|
lateSidecar.close()
|
|
|
|
newLane, err := client.reserveBulkDedicatedLaneAtRoute(newRoute)
|
|
if err != nil {
|
|
t.Fatalf("reserve new-route lane: %v", err)
|
|
}
|
|
newSidecarLeft, newSidecarRight := net.Pipe()
|
|
defer newSidecarRight.Close()
|
|
newSidecar := newBulkDedicatedSidecar(newSidecarLeft, newLane)
|
|
if _, installed, err := client.installClientDedicatedSidecarAtRoute(newLane, newSidecar, newRoute); err != nil || !installed {
|
|
t.Fatalf("install new-route sidecar = installed=%v err=%v, want installed", installed, err)
|
|
}
|
|
|
|
client.releaseBulkDedicatedLaneAtRoute(newLane, oldRoute)
|
|
client.bulkDedicatedSidecarMu.Lock()
|
|
lane := client.bulkDedicatedLanes[newLane]
|
|
active := 0
|
|
if lane != nil {
|
|
active = lane.activeBulks
|
|
}
|
|
client.bulkDedicatedSidecarMu.Unlock()
|
|
if active != 1 {
|
|
t.Fatalf("old-route release changed new-route lane active count to %d, want 1", active)
|
|
}
|
|
}
|
|
|
|
func TestHandleBulkAttachSystemMessageAcceptedWritesDirectReplyBeforeDedicatedHandoff(t *testing.T) {
|
|
server := NewServer().(*ServerCommon)
|
|
UseLegacySecurityServer(server)
|
|
|
|
sidecarLeft, sidecarRight := net.Pipe()
|
|
defer sidecarRight.Close()
|
|
|
|
current := server.bootstrapAcceptedLogical("dedicated-attach-current", nil, sidecarLeft)
|
|
if current == nil {
|
|
t.Fatal("bootstrapAcceptedLogical(current) should return logical")
|
|
}
|
|
target := server.bootstrapAcceptedLogical("dedicated-attach-target", nil, nil)
|
|
if target == nil {
|
|
t.Fatal("bootstrapAcceptedLogical(target) should return logical")
|
|
}
|
|
|
|
bulk := newBulkHandle(context.Background(), server.getBulkRuntime(), serverFileScope(target), BulkOpenRequest{
|
|
BulkID: "server-dedicated-attach-test",
|
|
DataID: 7,
|
|
Dedicated: true,
|
|
AttachToken: "attach-token",
|
|
}, 0, target, nil, 0, nil, nil, nil, nil, nil)
|
|
bulk.markAcceptHandled()
|
|
if err := server.getBulkRuntime().register(serverFileScope(target), bulk); err != nil {
|
|
t.Fatalf("register bulk runtime failed: %v", err)
|
|
}
|
|
|
|
reqPayload, err := server.sequenceEn(bulkAttachRequest{
|
|
PeerID: target.ID(),
|
|
BulkID: bulk.ID(),
|
|
AttachToken: "attach-token",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("encode bulkAttachRequest failed: %v", err)
|
|
}
|
|
msg := Message{
|
|
NetType: NET_SERVER,
|
|
LogicalConn: current,
|
|
ClientConn: current.compatClientConn(),
|
|
TransferMsg: TransferMsg{
|
|
ID: 42,
|
|
Key: systemBulkAttachKey,
|
|
Value: reqPayload,
|
|
Type: MSG_SYS_WAIT,
|
|
},
|
|
inboundConn: sidecarLeft,
|
|
Time: time.Now(),
|
|
}
|
|
|
|
type attachReplyResult struct {
|
|
transfer TransferMsg
|
|
resp bulkAttachResponse
|
|
err error
|
|
}
|
|
replyCh := make(chan attachReplyResult, 1)
|
|
go func() {
|
|
_ = sidecarRight.SetReadDeadline(time.Now().Add(time.Second))
|
|
replyPayload, err := readDirectSignalFramePayload(sidecarRight)
|
|
if err != nil {
|
|
replyCh <- attachReplyResult{err: err}
|
|
return
|
|
}
|
|
transfer, err := decodeDirectSignalPayload(server.sequenceDe, current.msgDeSnapshot(), current.secretKeySnapshot(), replyPayload)
|
|
if err != nil {
|
|
replyCh <- attachReplyResult{err: err}
|
|
return
|
|
}
|
|
resp, err := decodeBulkAttachResponse(server.sequenceDe, transfer.Value)
|
|
replyCh <- attachReplyResult{transfer: transfer, resp: resp, err: err}
|
|
}()
|
|
|
|
if !server.handleBulkAttachSystemMessage(msg) {
|
|
t.Fatal("handleBulkAttachSystemMessage should accept dedicated attach message")
|
|
}
|
|
|
|
var result attachReplyResult
|
|
select {
|
|
case result = <-replyCh:
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("timed out waiting for direct attach reply")
|
|
}
|
|
if result.err != nil {
|
|
t.Fatalf("read direct attach reply failed: %v", result.err)
|
|
}
|
|
transfer := result.transfer
|
|
if transfer.ID != msg.ID || transfer.Key != systemBulkAttachKey || transfer.Type != MSG_SYS_REPLY {
|
|
t.Fatalf("attach reply mismatch: %+v", transfer)
|
|
}
|
|
resp := result.resp
|
|
if !resp.Accepted || resp.Error != "" {
|
|
t.Fatalf("bulk attach response = %+v, want accepted", resp)
|
|
}
|
|
if got := bulk.dedicatedConnSnapshot(); got != sidecarLeft {
|
|
t.Fatalf("dedicated conn mismatch: got %v want %v", got, sidecarLeft)
|
|
}
|
|
if current.transportAttachedSnapshot() {
|
|
t.Fatal("attach sidecar logical transport should be detached after handoff")
|
|
}
|
|
if got := server.GetLogicalConn(current.ID()); got != nil {
|
|
t.Fatalf("attach sidecar logical should be removed after handoff, got %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestHandleBulkAttachRejectsBulkWhosePrimaryTransportReattached(t *testing.T) {
|
|
server := NewServer().(*ServerCommon)
|
|
UseLegacySecurityServer(server)
|
|
runtimeCtx, runtimeCancel := context.WithCancel(context.Background())
|
|
defer runtimeCancel()
|
|
server.setServerSessionRuntime(&serverSessionRuntime{
|
|
stopCtx: runtimeCtx,
|
|
stopFn: runtimeCancel,
|
|
queue: stario.NewQueueCtx(runtimeCtx, 4, ^uint32(0)),
|
|
})
|
|
server.markSessionStarted()
|
|
defer server.markSessionStopped("test done", nil)
|
|
|
|
attachLeft, attachRight := net.Pipe()
|
|
defer attachRight.Close()
|
|
current := server.bootstrapAcceptedLogical("dedicated-stale-primary-current", nil, attachLeft)
|
|
if current == nil {
|
|
t.Fatal("bootstrapAcceptedLogical(current) should return logical")
|
|
}
|
|
primaryLeft, primaryRight := net.Pipe()
|
|
defer primaryRight.Close()
|
|
target := server.bootstrapAcceptedLogical("dedicated-stale-primary-target", nil, primaryLeft)
|
|
if target == nil {
|
|
t.Fatal("bootstrapAcceptedLogical(target) should return logical")
|
|
}
|
|
primaryTransport := target.CurrentTransportConn()
|
|
if primaryTransport == nil {
|
|
t.Fatal("target primary transport should exist")
|
|
}
|
|
|
|
bulk := newBulkHandle(context.Background(), server.getBulkRuntime(), serverFileScope(target), BulkOpenRequest{
|
|
BulkID: "dedicated-stale-primary",
|
|
DataID: 7,
|
|
Dedicated: true,
|
|
AttachToken: "attach-token",
|
|
}, 0, target, primaryTransport, primaryTransport.TransportGeneration(), nil, nil, nil, nil, nil)
|
|
if err := server.getBulkRuntime().register(serverFileScope(target), bulk); err != nil {
|
|
t.Fatalf("register dedicated bulk: %v", err)
|
|
}
|
|
defer bulk.finalize()
|
|
|
|
replacementLeft, replacementRight := net.Pipe()
|
|
defer replacementRight.Close()
|
|
if err := target.attachClientConnSessionTransport(replacementLeft); err != nil {
|
|
t.Fatalf("reattach target primary transport: %v", err)
|
|
}
|
|
reqPayload, err := server.sequenceEn(bulkAttachRequest{
|
|
PeerID: target.ID(),
|
|
BulkID: bulk.ID(),
|
|
AttachToken: "attach-token",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("encode bulk attach request: %v", err)
|
|
}
|
|
message := Message{
|
|
NetType: NET_SERVER,
|
|
LogicalConn: current,
|
|
TransportConn: current.CurrentTransportConn(),
|
|
TransferMsg: TransferMsg{
|
|
ID: 91,
|
|
Key: systemBulkAttachKey,
|
|
Value: reqPayload,
|
|
Type: MSG_SYS_WAIT,
|
|
},
|
|
inboundConn: attachLeft,
|
|
}
|
|
|
|
replyDone := make(chan bulkAttachResponse, 1)
|
|
go func() {
|
|
_ = attachRight.SetReadDeadline(time.Now().Add(time.Second))
|
|
payload, readErr := readDirectSignalFramePayload(attachRight)
|
|
if readErr != nil {
|
|
replyDone <- bulkAttachResponse{Error: readErr.Error()}
|
|
return
|
|
}
|
|
transfer, decodeErr := decodeDirectSignalPayload(server.sequenceDe, current.msgDeSnapshot(), current.secretKeySnapshot(), payload)
|
|
if decodeErr != nil {
|
|
replyDone <- bulkAttachResponse{Error: decodeErr.Error()}
|
|
return
|
|
}
|
|
resp, decodeErr := decodeBulkAttachResponse(server.sequenceDe, transfer.Value)
|
|
if decodeErr != nil {
|
|
resp.Error = decodeErr.Error()
|
|
}
|
|
replyDone <- resp
|
|
}()
|
|
|
|
if !server.handleBulkAttachSystemMessage(message) {
|
|
t.Fatal("handleBulkAttachSystemMessage should consume attach message")
|
|
}
|
|
select {
|
|
case resp := <-replyDone:
|
|
if resp.Accepted || resp.Error == "" {
|
|
t.Fatalf("stale-primary attach response = %+v, want rejection", resp)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("timed out waiting for stale-primary attach rejection")
|
|
}
|
|
if !current.transportAttachedSnapshot() {
|
|
t.Fatal("rejected attach must not detach the inbound attach connection")
|
|
}
|
|
if got := bulk.dedicatedConnSnapshot(); got != nil {
|
|
t.Fatalf("rejected stale-primary bulk attached sidecar %v", got)
|
|
}
|
|
}
|
|
|
|
func TestHandleBulkAttachSystemMessageDoesNotExposeSharedSidecarBeforeReplyCompletes(t *testing.T) {
|
|
server := NewServer().(*ServerCommon)
|
|
UseLegacySecurityServer(server)
|
|
|
|
sidecarLeft, sidecarRight := net.Pipe()
|
|
defer sidecarRight.Close()
|
|
|
|
current := server.bootstrapAcceptedLogical("dedicated-attach-current-blocked", nil, sidecarLeft)
|
|
if current == nil {
|
|
t.Fatal("bootstrapAcceptedLogical(current) should return logical")
|
|
}
|
|
target := server.bootstrapAcceptedLogical("dedicated-attach-target-blocked", nil, nil)
|
|
if target == nil {
|
|
t.Fatal("bootstrapAcceptedLogical(target) should return logical")
|
|
}
|
|
|
|
currentBulk := newBulkHandle(context.Background(), server.getBulkRuntime(), serverFileScope(target), BulkOpenRequest{
|
|
BulkID: "server-dedicated-current",
|
|
DataID: 17,
|
|
Dedicated: true,
|
|
AttachToken: "attach-token",
|
|
}, 0, target, nil, 0, nil, nil, nil, nil, nil)
|
|
currentBulk.markAcceptHandled()
|
|
if err := server.getBulkRuntime().register(serverFileScope(target), currentBulk); err != nil {
|
|
t.Fatalf("register current bulk runtime failed: %v", err)
|
|
}
|
|
|
|
pendingBulk := newBulkHandle(context.Background(), server.getBulkRuntime(), serverFileScope(target), BulkOpenRequest{
|
|
BulkID: "server-dedicated-pending",
|
|
DataID: 18,
|
|
Dedicated: true,
|
|
AttachToken: "attach-token-2",
|
|
}, 0, target, nil, 0, nil, nil, nil, nil, nil)
|
|
pendingBulk.markAcceptHandled()
|
|
if err := server.getBulkRuntime().register(serverFileScope(target), pendingBulk); err != nil {
|
|
t.Fatalf("register pending bulk runtime failed: %v", err)
|
|
}
|
|
|
|
reqPayload, err := server.sequenceEn(bulkAttachRequest{
|
|
PeerID: target.ID(),
|
|
BulkID: currentBulk.ID(),
|
|
AttachToken: "attach-token",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("encode bulkAttachRequest failed: %v", err)
|
|
}
|
|
msg := Message{
|
|
NetType: NET_SERVER,
|
|
LogicalConn: current,
|
|
ClientConn: current.compatClientConn(),
|
|
TransferMsg: TransferMsg{
|
|
ID: 77,
|
|
Key: systemBulkAttachKey,
|
|
Value: reqPayload,
|
|
Type: MSG_SYS_WAIT,
|
|
},
|
|
inboundConn: sidecarLeft,
|
|
Time: time.Now(),
|
|
}
|
|
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
_ = server.handleBulkAttachSystemMessage(msg)
|
|
}()
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
if got := pendingBulk.dedicatedConnSnapshot(); got != nil {
|
|
t.Fatal("pending dedicated bulk should not observe shared sidecar before attach reply is fully sent")
|
|
}
|
|
if got := server.serverDedicatedSidecarSnapshot(target); got != nil {
|
|
t.Fatal("server dedicated sidecar should not be published before attach reply is fully sent")
|
|
}
|
|
|
|
type attachReplyResult struct {
|
|
transfer TransferMsg
|
|
resp bulkAttachResponse
|
|
err error
|
|
}
|
|
replyCh := make(chan attachReplyResult, 1)
|
|
go func() {
|
|
_ = sidecarRight.SetReadDeadline(time.Now().Add(time.Second))
|
|
replyPayload, err := readDirectSignalFramePayload(sidecarRight)
|
|
if err != nil {
|
|
replyCh <- attachReplyResult{err: err}
|
|
return
|
|
}
|
|
transfer, err := decodeDirectSignalPayload(server.sequenceDe, current.msgDeSnapshot(), current.secretKeySnapshot(), replyPayload)
|
|
if err != nil {
|
|
replyCh <- attachReplyResult{err: err}
|
|
return
|
|
}
|
|
resp, err := decodeBulkAttachResponse(server.sequenceDe, transfer.Value)
|
|
replyCh <- attachReplyResult{transfer: transfer, resp: resp, err: err}
|
|
}()
|
|
|
|
select {
|
|
case result := <-replyCh:
|
|
if result.err != nil {
|
|
t.Fatalf("read direct attach reply failed: %v", result.err)
|
|
}
|
|
if !result.resp.Accepted {
|
|
t.Fatalf("bulk attach response = %+v, want accepted", result.resp)
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("timed out waiting for direct attach reply")
|
|
}
|
|
|
|
select {
|
|
case <-done:
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("timed out waiting for handleBulkAttachSystemMessage to finish")
|
|
}
|
|
|
|
if got := pendingBulk.dedicatedConnSnapshot(); got != sidecarLeft {
|
|
t.Fatalf("pending dedicated bulk conn mismatch after reply: got %v want %v", got, sidecarLeft)
|
|
}
|
|
if got := server.serverDedicatedSidecarSnapshot(target); got == nil || got.conn != sidecarLeft {
|
|
t.Fatal("server dedicated sidecar should be published after attach reply completes")
|
|
}
|
|
}
|
|
|
|
func TestBulkAttachResponseErrorCarriesStructuredFields(t *testing.T) {
|
|
resp := toBulkAttachResponseError(&bulkAttachError{
|
|
Code: bulkAttachErrorCodeTokenMismatch,
|
|
Retryable: false,
|
|
Message: "bulk attach token mismatch",
|
|
FailedSeq: 7,
|
|
FailedBulk: "bulk-1",
|
|
}, "fallback-bulk")
|
|
|
|
if resp.Accepted {
|
|
t.Fatalf("Accepted = %v, want false", resp.Accepted)
|
|
}
|
|
if got, want := resp.Code, string(bulkAttachErrorCodeTokenMismatch); got != want {
|
|
t.Fatalf("Code = %q, want %q", got, want)
|
|
}
|
|
if got, want := resp.Retryable, false; got != want {
|
|
t.Fatalf("Retryable = %v, want %v", got, want)
|
|
}
|
|
if got, want := resp.FailedSeq, uint64(7); got != want {
|
|
t.Fatalf("FailedSeq = %d, want %d", got, want)
|
|
}
|
|
if got, want := resp.FailedBulk, "bulk-1"; got != want {
|
|
t.Fatalf("FailedBulk = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestResolveInboundDedicatedBulkRejectsAlreadyAttachedAfterDataStarted(t *testing.T) {
|
|
server := NewServer().(*ServerCommon)
|
|
UseLegacySecurityServer(server)
|
|
|
|
currentLeft, currentRight := net.Pipe()
|
|
defer currentRight.Close()
|
|
current := server.bootstrapAcceptedLogical("dedicated-attach-current", nil, currentLeft)
|
|
if current == nil {
|
|
t.Fatal("bootstrapAcceptedLogical(current) should return logical")
|
|
}
|
|
target := server.bootstrapAcceptedLogical("dedicated-attach-target", nil, nil)
|
|
if target == nil {
|
|
t.Fatal("bootstrapAcceptedLogical(target) should return logical")
|
|
}
|
|
|
|
bulk := newBulkHandle(context.Background(), server.getBulkRuntime(), serverFileScope(target), BulkOpenRequest{
|
|
BulkID: "server-dedicated-attach-test",
|
|
DataID: 7,
|
|
Dedicated: true,
|
|
AttachToken: "attach-token",
|
|
}, 0, target, nil, 0, nil, nil, nil, nil, nil)
|
|
bulk.markAcceptHandled()
|
|
if err := server.getBulkRuntime().register(serverFileScope(target), bulk); err != nil {
|
|
t.Fatalf("register bulk runtime failed: %v", err)
|
|
}
|
|
attachedLeft, attachedRight := net.Pipe()
|
|
defer attachedRight.Close()
|
|
if err := bulk.attachDedicatedConn(attachedLeft); err != nil {
|
|
t.Fatalf("attachDedicatedConn failed: %v", err)
|
|
}
|
|
bulk.markDedicatedDataStarted()
|
|
|
|
_, _, err := server.resolveInboundDedicatedBulk(current, bulkAttachRequest{
|
|
PeerID: target.ID(),
|
|
BulkID: bulk.ID(),
|
|
AttachToken: "attach-token",
|
|
})
|
|
if err == nil {
|
|
t.Fatal("resolveInboundDedicatedBulk should reject duplicate attach after data started")
|
|
}
|
|
var attachErr *bulkAttachError
|
|
if !errors.As(err, &attachErr) || attachErr == nil {
|
|
t.Fatalf("resolveInboundDedicatedBulk error type = %T, want *bulkAttachError", err)
|
|
}
|
|
if got, want := attachErr.Code, bulkAttachErrorCodeAlreadyAttached; got != want {
|
|
t.Fatalf("attach error code = %q, want %q", got, want)
|
|
}
|
|
if attachErr.Retryable {
|
|
t.Fatalf("attach error retryable = true, want false")
|
|
}
|
|
}
|
|
|
|
func TestResolveInboundDedicatedBulkAllowsReattachBeforeDataStarts(t *testing.T) {
|
|
server := NewServer().(*ServerCommon)
|
|
UseLegacySecurityServer(server)
|
|
|
|
currentLeft, currentRight := net.Pipe()
|
|
defer currentRight.Close()
|
|
current := server.bootstrapAcceptedLogical("dedicated-attach-current", nil, currentLeft)
|
|
if current == nil {
|
|
t.Fatal("bootstrapAcceptedLogical(current) should return logical")
|
|
}
|
|
target := server.bootstrapAcceptedLogical("dedicated-attach-target", nil, nil)
|
|
if target == nil {
|
|
t.Fatal("bootstrapAcceptedLogical(target) should return logical")
|
|
}
|
|
|
|
bulk := newBulkHandle(context.Background(), server.getBulkRuntime(), serverFileScope(target), BulkOpenRequest{
|
|
BulkID: "server-dedicated-attach-test",
|
|
DataID: 7,
|
|
Dedicated: true,
|
|
AttachToken: "attach-token",
|
|
}, 0, target, nil, 0, nil, nil, nil, nil, nil)
|
|
bulk.markAcceptHandled()
|
|
if err := server.getBulkRuntime().register(serverFileScope(target), bulk); err != nil {
|
|
t.Fatalf("register bulk runtime failed: %v", err)
|
|
}
|
|
attachedLeft, attachedRight := net.Pipe()
|
|
defer attachedRight.Close()
|
|
if err := bulk.attachDedicatedConn(attachedLeft); err != nil {
|
|
t.Fatalf("attachDedicatedConn failed: %v", err)
|
|
}
|
|
|
|
resolvedLogical, resolvedBulk, err := server.resolveInboundDedicatedBulk(current, bulkAttachRequest{
|
|
PeerID: target.ID(),
|
|
BulkID: bulk.ID(),
|
|
AttachToken: "attach-token",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("resolveInboundDedicatedBulk failed: %v", err)
|
|
}
|
|
if resolvedLogical != target {
|
|
t.Fatalf("resolved logical mismatch: got %v want %v", resolvedLogical, target)
|
|
}
|
|
if resolvedBulk != bulk {
|
|
t.Fatalf("resolved bulk mismatch: got %v want %v", resolvedBulk, bulk)
|
|
}
|
|
}
|