// 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) } }) } }