package notify import ( "context" "errors" "net" "testing" "time" ) func TestBulkDedicatedBatchPlainRoundTrip(t *testing.T) { releasePayload, err := encodeBulkDedicatedReleasePayload(4096, 2) if err != nil { t.Fatalf("encodeBulkDedicatedReleasePayload failed: %v", err) } items := []bulkDedicatedSendRequest{ { Type: bulkFastPayloadTypeData, Seq: 7, Payload: []byte("hello"), }, { Type: bulkFastPayloadTypeClose, Flags: bulkFastPayloadFlagFullClose, }, { Type: bulkFastPayloadTypeReset, Payload: []byte("boom"), }, { Type: bulkFastPayloadTypeRelease, Payload: releasePayload, }, } plain, err := encodeBulkDedicatedBatchPlain(42, items) if err != nil { t.Fatalf("encodeBulkDedicatedBatchPlain failed: %v", err) } dataID, decoded, matched, err := decodeBulkDedicatedBatchPlain(plain) if err != nil { t.Fatalf("decodeBulkDedicatedBatchPlain failed: %v", err) } if !matched { t.Fatal("decodeBulkDedicatedBatchPlain should match dedicated batch") } if dataID != 42 { t.Fatalf("decoded data id = %d, want 42", dataID) } if len(decoded) != len(items) { t.Fatalf("decoded item count = %d, want %d", len(decoded), len(items)) } for i := range items { if decoded[i].Type != items[i].Type { t.Fatalf("item %d type = %d, want %d", i, decoded[i].Type, items[i].Type) } if decoded[i].Flags != items[i].Flags { t.Fatalf("item %d flags = %d, want %d", i, decoded[i].Flags, items[i].Flags) } if decoded[i].Seq != items[i].Seq { t.Fatalf("item %d seq = %d, want %d", i, decoded[i].Seq, items[i].Seq) } if got, want := string(decoded[i].Payload), string(items[i].Payload); got != want { t.Fatalf("item %d payload = %q, want %q", i, got, want) } } } func TestBulkOpenRoundTripDedicatedMultiWriteTCP(t *testing.T) { server := NewServer().(*ServerCommon) if err := UseModernPSKServer(server, integrationSharedSecret, integrationModernPSKOptions()); err != nil { t.Fatalf("UseModernPSKServer failed: %v", err) } acceptCh := make(chan BulkAcceptInfo, 1) server.SetBulkHandler(func(info BulkAcceptInfo) error { acceptCh <- info return nil }) if err := server.Listen("tcp", "127.0.0.1:0"); err != nil { t.Fatalf("server Listen failed: %v", err) } defer func() { _ = server.Stop() }() client := NewClient().(*ClientCommon) if err := UseModernPSKClient(client, integrationSharedSecret, integrationModernPSKOptions()); err != nil { t.Fatalf("UseModernPSKClient failed: %v", err) } if err := client.Connect("tcp", server.listener.Addr().String()); err != nil { t.Fatalf("client Connect failed: %v", err) } defer func() { _ = client.Stop() }() bulk, err := client.OpenBulk(context.Background(), BulkOpenOptions{ Range: BulkRange{ Offset: 0, Length: 1024, }, Dedicated: true, ChunkSize: 4, }) if err != nil { t.Fatalf("client OpenBulk dedicated failed: %v", err) } accepted := waitAcceptedBulk(t, acceptCh, 2*time.Second) clientParts := []string{"aa", "bb", "cc", "dd", "ee", "ff"} for _, part := range clientParts { if _, err := bulk.Write([]byte(part)); err != nil { t.Fatalf("client dedicated bulk Write(%q) failed: %v", part, err) } } readBulkExactly(t, accepted.Bulk, "aabbccddeeff", 2*time.Second) serverParts := []string{"11", "22", "33", "44", "55", "66"} for _, part := range serverParts { if _, err := accepted.Bulk.Write([]byte(part)); err != nil { t.Fatalf("server dedicated bulk Write(%q) failed: %v", part, err) } } readBulkExactly(t, bulk, "112233445566", 2*time.Second) if err := bulk.CloseWrite(); err != nil { t.Fatalf("client dedicated bulk CloseWrite failed: %v", err) } waitForBulkReadEOF(t, accepted.Bulk, 2*time.Second) if err := accepted.Bulk.Close(); err != nil { t.Fatalf("server dedicated bulk Close failed: %v", err) } waitForBulkReadEOF(t, bulk, 2*time.Second) waitForBulkContextDone(t, bulk.Context(), 2*time.Second) } func TestBulkDedicatedSenderRespectsWriteDeadlineWhenReceiverStalls(t *testing.T) { left, right := net.Pipe() defer left.Close() defer right.Close() sender := newBulkDedicatedSender(left, 1, func(plain []byte) ([]byte, error) { return plain, nil }, nil, nil) defer sender.stop() ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) defer cancel() errCh := make(chan error, 1) go func() { errCh <- sender.submitControl(ctx, bulkFastPayloadTypeClose, 0, 0, nil) }() select { case err := <-errCh: if err == nil { t.Fatal("sender.submitControl should fail when receiver stalls") } if !isTimeoutLikeError(err) { t.Fatalf("sender.submitControl error = %v, want timeout-like error", err) } case <-time.After(time.Second): t.Fatal("sender.submitControl should not hang when receiver stalls") } } func TestBulkDedicatedSenderSubmitWriteDirectPathRespectsWriteDeadlineWhenReceiverStalls(t *testing.T) { left, right := net.Pipe() defer left.Close() defer right.Close() sender := newBulkDedicatedSender(left, 1, func(plain []byte) ([]byte, error) { return plain, nil }, nil, nil) defer sender.stop() ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) defer cancel() payload := make([]byte, 256*1024) errCh := make(chan error, 1) go func() { _, err := sender.submitWrite(ctx, 1, payload, len(payload)) errCh <- err }() select { case err := <-errCh: if err == nil { t.Fatal("sender.submitWrite should fail when receiver stalls") } if !isTimeoutLikeError(err) { t.Fatalf("sender.submitWrite error = %v, want timeout-like error", err) } case <-time.After(time.Second): t.Fatal("sender.submitWrite should not hang when receiver stalls") } } func TestBulkDedicatedSenderSkipsQueuedCanceledRequest(t *testing.T) { conn := newBlockingPacketWriteConn() sender := newBulkDedicatedSender(conn, 1, func(plain []byte) ([]byte, error) { return plain, nil }, nil, nil) defer sender.stop() firstErrCh := make(chan error, 1) go func() { firstErrCh <- sender.submitControl(context.Background(), bulkFastPayloadTypeClose, 0, 1, nil) }() select { case <-conn.startCh: case <-time.After(time.Second): t.Fatal("first dedicated bulk write did not start") } ctx, cancel := context.WithCancel(context.Background()) secondErrCh := make(chan error, 1) go func() { secondErrCh <- sender.submitControl(ctx, bulkFastPayloadTypeReset, 0, 2, nil) }() time.Sleep(20 * time.Millisecond) cancel() select { case err := <-secondErrCh: if !errors.Is(err, context.Canceled) { t.Fatalf("second dedicated bulk submit error = %v, want %v", err, context.Canceled) } case <-time.After(time.Second): t.Fatal("second dedicated bulk submit did not return after cancel") } close(conn.unblockCh) select { case err := <-firstErrCh: if err != nil { t.Fatalf("first dedicated bulk submit failed: %v", err) } case <-time.After(time.Second): t.Fatal("first dedicated bulk submit did not finish") } time.Sleep(50 * time.Millisecond) if got, want := conn.writeCount.Load(), int32(2); got != want { t.Fatalf("dedicated bulk write count = %d, want %d", got, want) } } func TestBulkDedicatedSenderReturnsFlushResultAfterStartedContextCancel(t *testing.T) { conn := newBlockingPacketWriteConn() sender := newBulkDedicatedSender(conn, 1, func(plain []byte) ([]byte, error) { return plain, nil }, nil, nil) defer sender.stop() ctx, cancel := context.WithCancel(context.Background()) errCh := make(chan error, 1) go func() { errCh <- sender.submitControl(ctx, bulkFastPayloadTypeClose, 0, 1, nil) }() select { case <-conn.startCh: case <-time.After(time.Second): t.Fatal("dedicated bulk write did not start") } cancel() select { case err := <-errCh: t.Fatalf("sender.submitControl returned before flush completed: %v", err) case <-time.After(50 * time.Millisecond): } close(conn.unblockCh) select { case err := <-errCh: if err != nil { t.Fatalf("sender.submitControl failed after started flush: %v", err) } case <-time.After(time.Second): t.Fatal("sender.submitControl did not return after started flush completed") } }