blob: 6bee8bb9f6eb74ed216d8b4cffd19cbf2e75622d [file] [log] [blame]
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.21
package quic
import (
"crypto/rand"
"reflect"
"testing"
)
func TestCryptoStreamReceive(t *testing.T) {
data := make([]byte, 1<<20)
rand.Read(data) // doesn't need to be crypto/rand, but non-deprecated and harmless
type frame struct {
start int64
end int64
want int
}
for _, test := range []struct {
name string
frames []frame
}{{
name: "linear",
frames: []frame{{
start: 0,
end: 1000,
want: 1000,
}, {
start: 1000,
end: 2000,
want: 2000,
}, {
// larger than any realistic packet can hold
start: 2000,
end: 1 << 20,
want: 1 << 20,
}},
}, {
name: "out of order",
frames: []frame{{
start: 1000,
end: 2000,
}, {
start: 2000,
end: 3000,
}, {
start: 0,
end: 1000,
want: 3000,
}},
}, {
name: "resent",
frames: []frame{{
start: 0,
end: 1000,
want: 1000,
}, {
start: 0,
end: 1000,
want: 1000,
}, {
start: 1000,
end: 2000,
want: 2000,
}, {
start: 0,
end: 1000,
want: 2000,
}, {
start: 1000,
end: 2000,
want: 2000,
}},
}, {
name: "overlapping",
frames: []frame{{
start: 0,
end: 1000,
want: 1000,
}, {
start: 3000,
end: 4000,
want: 1000,
}, {
start: 2000,
end: 3000,
want: 1000,
}, {
start: 1000,
end: 3000,
want: 4000,
}},
}, {
name: "resent consumed data",
frames: []frame{{
start: 0,
end: 1000,
want: 1000,
}, {
start: 1000,
end: 2000,
want: 2000,
}, {
start: 0,
end: 1000,
want: 2000,
}},
}} {
t.Run(test.name, func(t *testing.T) {
var s cryptoStream
var got []byte
for _, f := range test.frames {
t.Logf("receive [%v,%v)", f.start, f.end)
s.handleCrypto(
f.start,
data[f.start:f.end],
func(b []byte) error {
t.Logf("got new bytes [%v,%v)", len(got), len(got)+len(b))
got = append(got, b...)
return nil
},
)
if len(got) != f.want {
t.Fatalf("have bytes [0,%v), want [0,%v)", len(got), f.want)
}
for i := range got {
if got[i] != data[i] {
t.Fatalf("byte %v of received data = %v, want %v", i, got[i], data[i])
}
}
}
})
}
}
func TestCryptoStreamSends(t *testing.T) {
data := make([]byte, 1<<20)
rand.Read(data) // doesn't need to be crypto/rand, but non-deprecated and harmless
type (
sendOp i64range[int64]
ackOp i64range[int64]
lossOp i64range[int64]
)
for _, test := range []struct {
name string
size int64
ops []any
wantSend []i64range[int64]
wantPTOSend []i64range[int64]
}{{
name: "writes with data remaining",
size: 4000,
ops: []any{
sendOp{0, 1000},
sendOp{1000, 2000},
sendOp{2000, 3000},
},
wantSend: []i64range[int64]{
{3000, 4000},
},
wantPTOSend: []i64range[int64]{
{0, 4000},
},
}, {
name: "lost data is resent",
size: 4000,
ops: []any{
sendOp{0, 1000},
sendOp{1000, 2000},
sendOp{2000, 3000},
sendOp{3000, 4000},
lossOp{1000, 2000},
lossOp{3000, 4000},
},
wantSend: []i64range[int64]{
{1000, 2000},
{3000, 4000},
},
wantPTOSend: []i64range[int64]{
{0, 4000},
},
}, {
name: "acked data at start of range",
size: 4000,
ops: []any{
sendOp{0, 4000},
ackOp{0, 1000},
ackOp{1000, 2000},
ackOp{2000, 3000},
},
wantSend: nil,
wantPTOSend: []i64range[int64]{
{3000, 4000},
},
}, {
name: "acked data is not resent on pto",
size: 4000,
ops: []any{
sendOp{0, 4000},
ackOp{1000, 2000},
},
wantSend: nil,
wantPTOSend: []i64range[int64]{
{0, 1000},
},
}, {
// This is an unusual, but possible scenario:
// Data is sent, resent, one of the two sends is acked, and the other is lost.
name: "acked and then lost data is not resent",
size: 4000,
ops: []any{
sendOp{0, 4000},
sendOp{1000, 2000}, // resent, no-op
ackOp{1000, 2000},
lossOp{1000, 2000},
},
wantSend: nil,
wantPTOSend: []i64range[int64]{
{0, 1000},
},
}, {
// The opposite of the above scenario: data is marked lost, and then acked
// before being resent.
name: "lost and then acked data is not resent",
size: 4000,
ops: []any{
sendOp{0, 4000},
sendOp{1000, 2000}, // resent, no-op
lossOp{1000, 2000},
ackOp{1000, 2000},
},
wantSend: nil,
wantPTOSend: []i64range[int64]{
{0, 1000},
},
}} {
t.Run(test.name, func(t *testing.T) {
var s cryptoStream
s.write(data[:test.size])
for _, op := range test.ops {
switch op := op.(type) {
case sendOp:
t.Logf("send [%v,%v)", op.start, op.end)
b := make([]byte, op.end-op.start)
s.sendData(op.start, b)
case ackOp:
t.Logf("ack [%v,%v)", op.start, op.end)
s.ackOrLoss(op.start, op.end, packetAcked)
case lossOp:
t.Logf("loss [%v,%v)", op.start, op.end)
s.ackOrLoss(op.start, op.end, packetLost)
default:
t.Fatalf("unhandled type %T", op)
}
}
var gotSend []i64range[int64]
s.dataToSend(true, func(off, size int64) (wrote int64) {
gotSend = append(gotSend, i64range[int64]{off, off + size})
return 0
})
if !reflect.DeepEqual(gotSend, test.wantPTOSend) {
t.Fatalf("got data to send on PTO: %v, want %v", gotSend, test.wantPTOSend)
}
gotSend = nil
s.dataToSend(false, func(off, size int64) (wrote int64) {
gotSend = append(gotSend, i64range[int64]{off, off + size})
b := make([]byte, size)
s.sendData(off, b)
return int64(len(b))
})
if !reflect.DeepEqual(gotSend, test.wantSend) {
t.Fatalf("got data to send: %v, want %v", gotSend, test.wantSend)
}
})
}
}