Files
quic-go/qlog/frame_test.go
Marten Seemann c2131eb595 qlog: split serializiation and event definitions, remove logging abstraction (#5356)
* qlog: implement a Trace and a Writer struct

* qlog: rename Trace to FileSeq

* split qlog trace writer and QUIC qlog events into separate packages

* use the new qlog.Recorder instead of the logging.ConnectionTracer
2025-10-08 05:53:02 +02:00

422 lines
8.5 KiB
Go

package qlog
import (
"bytes"
"encoding/json"
"testing"
"time"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/qlogwriter/jsontext"
"github.com/stretchr/testify/require"
)
func check(t *testing.T, f any, expected map[string]any) {
var buf bytes.Buffer
enc := jsontext.NewEncoder(&buf)
require.NoError(t, (Frame{Frame: f}).Encode(enc))
data := buf.Bytes()
require.True(t, json.Valid(data))
checkEncoding(t, data, expected)
}
func TestPingFrame(t *testing.T) {
check(t, &PingFrame{}, map[string]any{"frame_type": "ping"})
}
func TestAckFrame(t *testing.T) {
tests := []struct {
name string
frame *AckFrame
expected map[string]any
}{
{
name: "with delay and single packet range",
frame: &AckFrame{
DelayTime: 86 * time.Millisecond,
AckRanges: []AckRange{{Smallest: 120, Largest: 120}},
},
expected: map[string]any{
"frame_type": "ack",
"ack_delay": 86,
"acked_ranges": [][]float64{{120}},
},
},
{
name: "without delay",
frame: &AckFrame{
AckRanges: []AckRange{{Smallest: 120, Largest: 120}},
},
expected: map[string]any{
"frame_type": "ack",
"acked_ranges": [][]float64{{120}},
},
},
{
name: "with ECN counts",
frame: &AckFrame{
AckRanges: []AckRange{{Smallest: 120, Largest: 120}},
ECT0: 10,
ECT1: 100,
ECNCE: 1000,
},
expected: map[string]any{
"frame_type": "ack",
"acked_ranges": [][]float64{{120}},
"ect0": 10,
"ect1": 100,
"ce": 1000,
},
},
{
name: "with multiple ranges",
frame: &AckFrame{
DelayTime: 86 * time.Millisecond,
AckRanges: []AckRange{
{Smallest: 5, Largest: 50},
{Smallest: 100, Largest: 120},
},
},
expected: map[string]any{
"frame_type": "ack",
"ack_delay": 86,
"acked_ranges": [][]float64{
{5, 50},
{100, 120},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
check(t, tt.frame, tt.expected)
})
}
}
func TestResetStreamFrame(t *testing.T) {
check(t,
&ResetStreamFrame{
StreamID: 987,
FinalSize: 1234,
ErrorCode: 42,
},
map[string]any{
"frame_type": "reset_stream",
"stream_id": 987,
"error_code": 42,
"final_size": 1234,
},
)
}
func TestResetStreamAtFrame(t *testing.T) {
check(t,
&ResetStreamFrame{
StreamID: 987,
FinalSize: 1234,
ErrorCode: 42,
ReliableSize: 999,
},
map[string]any{
"frame_type": "reset_stream_at",
"stream_id": 987,
"error_code": 42,
"final_size": 1234,
"reliable_size": 999,
},
)
}
func TestAckFrequencyFrame(t *testing.T) {
check(t,
&AckFrequencyFrame{
SequenceNumber: 1337,
AckElicitingThreshold: 123,
RequestMaxAckDelay: 42 * time.Millisecond,
ReorderingThreshold: 1234,
},
map[string]any{
"frame_type": "ack_frequency",
"sequence_number": 1337,
"ack_eliciting_threshold": 123,
"request_max_ack_delay": 42,
"reordering_threshold": 1234,
},
)
}
func TestImmediateAckFrame(t *testing.T) {
check(t,
&ImmediateAckFrame{},
map[string]any{
"frame_type": "immediate_ack",
},
)
}
func TestStopSendingFrame(t *testing.T) {
check(t,
&StopSendingFrame{StreamID: 987, ErrorCode: 42},
map[string]any{
"frame_type": "stop_sending",
"stream_id": 987,
"error_code": 42,
},
)
}
func TestCryptoFrame(t *testing.T) {
check(t,
&CryptoFrame{Offset: 1337, Length: 6},
map[string]any{
"frame_type": "crypto",
"offset": 1337,
"length": 6,
},
)
}
func TestNewTokenFrame(t *testing.T) {
check(t,
&NewTokenFrame{Token: []byte{0xde, 0xad, 0xbe, 0xef}},
map[string]any{
"frame_type": "new_token",
"token": map[string]any{"data": "deadbeef"},
},
)
}
func TestStreamFrame(t *testing.T) {
tests := []struct {
name string
frame *StreamFrame
expected map[string]any
}{
{
name: "with FIN",
frame: &StreamFrame{
StreamID: 42,
Offset: 1337,
Fin: true,
Length: 9876,
},
expected: map[string]any{
"frame_type": "stream",
"stream_id": 42,
"offset": 1337,
"fin": true,
"length": 9876,
},
},
{
name: "without FIN",
frame: &StreamFrame{
StreamID: 42,
Offset: 1337,
Length: 3,
},
expected: map[string]any{
"frame_type": "stream",
"stream_id": 42,
"offset": 1337,
"length": 3,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
check(t, tt.frame, tt.expected)
})
}
}
func TestMaxDataFrame(t *testing.T) {
check(t,
&MaxDataFrame{MaximumData: 1337},
map[string]any{
"frame_type": "max_data",
"maximum": 1337,
},
)
}
func TestMaxStreamDataFrame(t *testing.T) {
check(t,
&MaxStreamDataFrame{StreamID: 1234, MaximumStreamData: 1337},
map[string]any{
"frame_type": "max_stream_data",
"stream_id": 1234,
"maximum": 1337,
},
)
}
func TestMaxStreamsFrame(t *testing.T) {
check(t,
&MaxStreamsFrame{
Type: protocol.StreamTypeBidi,
MaxStreamNum: 42,
},
map[string]any{
"frame_type": "max_streams",
"stream_type": "bidirectional",
"maximum": 42,
},
)
}
func TestDataBlockedFrame(t *testing.T) {
check(t,
&DataBlockedFrame{MaximumData: 1337},
map[string]any{
"frame_type": "data_blocked",
"limit": 1337,
},
)
}
func TestStreamDataBlockedFrame(t *testing.T) {
check(t,
&StreamDataBlockedFrame{
StreamID: 42,
MaximumStreamData: 1337,
},
map[string]any{
"frame_type": "stream_data_blocked",
"stream_id": 42,
"limit": 1337,
},
)
}
func TestStreamsBlockedFrame(t *testing.T) {
check(t,
&StreamsBlockedFrame{
Type: protocol.StreamTypeUni,
StreamLimit: 123,
},
map[string]any{
"frame_type": "streams_blocked",
"stream_type": "unidirectional",
"limit": 123,
},
)
}
func TestNewConnectionIDFrame(t *testing.T) {
check(t,
&NewConnectionIDFrame{
SequenceNumber: 42,
RetirePriorTo: 24,
ConnectionID: protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef}),
StatelessResetToken: protocol.StatelessResetToken{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf},
},
map[string]any{
"frame_type": "new_connection_id",
"sequence_number": 42,
"retire_prior_to": 24,
"length": 4,
"connection_id": "deadbeef",
"stateless_reset_token": "000102030405060708090a0b0c0d0e0f",
},
)
}
func TestRetireConnectionIDFrame(t *testing.T) {
check(t,
&RetireConnectionIDFrame{SequenceNumber: 1337},
map[string]any{
"frame_type": "retire_connection_id",
"sequence_number": 1337,
},
)
}
func TestPathChallengeFrame(t *testing.T) {
check(t,
&PathChallengeFrame{Data: [8]byte{0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xc0, 0x01}},
map[string]any{
"frame_type": "path_challenge",
"data": "deadbeefcafec001",
},
)
}
func TestPathResponseFrame(t *testing.T) {
check(t,
&PathResponseFrame{Data: [8]byte{0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xc0, 0x01}},
map[string]any{
"frame_type": "path_response",
"data": "deadbeefcafec001",
},
)
}
func TestConnectionCloseFrame(t *testing.T) {
tests := []struct {
name string
frame *ConnectionCloseFrame
expected map[string]any
}{
{
name: "application error code",
frame: &ConnectionCloseFrame{
IsApplicationError: true,
ErrorCode: 1337,
ReasonPhrase: "lorem ipsum",
},
expected: map[string]any{
"frame_type": "connection_close",
"error_space": "application",
"error_code": 1337,
"raw_error_code": 1337,
"reason": "lorem ipsum",
},
},
{
name: "transport error code",
frame: &ConnectionCloseFrame{
ErrorCode: uint64(qerr.FlowControlError),
ReasonPhrase: "lorem ipsum",
},
expected: map[string]any{
"frame_type": "connection_close",
"error_space": "transport",
"error_code": "flow_control_error",
"raw_error_code": int(qerr.FlowControlError),
"reason": "lorem ipsum",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
check(t, tt.frame, tt.expected)
})
}
}
func TestHandshakeDoneFrame(t *testing.T) {
check(t,
&HandshakeDoneFrame{},
map[string]any{
"frame_type": "handshake_done",
},
)
}
func TestDatagramFrame(t *testing.T) {
check(t,
&DatagramFrame{Length: 1337},
map[string]any{
"frame_type": "datagram",
"length": 1337,
},
)
}