quic: parameterize rangeset

Make the rangeset type parameterized, so it can be used for
either packet number or byte ranges without type conversions.

For golang/go#58547

Change-Id: I764913a33ba58222dcfd36f94de01c2249d73551
Reviewed-on: https://go-review.googlesource.com/c/net/+/499284
Run-TryBot: Damien Neil <dneil@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/internal/quic/frame_debug.go b/internal/quic/frame_debug.go
index 34a0039..93ddf55 100644
--- a/internal/quic/frame_debug.go
+++ b/internal/quic/frame_debug.go
@@ -116,15 +116,15 @@
 // debugFrameAck is an ACK frame.
 type debugFrameAck struct {
 	ackDelay time.Duration
-	ranges   []i64range
+	ranges   []i64range[packetNumber]
 }
 
 func parseDebugFrameAck(b []byte) (f debugFrameAck, n int) {
 	f.ranges = nil
 	_, f.ackDelay, n = consumeAckFrame(b, ackDelayExponent, func(start, end packetNumber) {
-		f.ranges = append(f.ranges, i64range{
-			start: int64(start),
-			end:   int64(end),
+		f.ranges = append(f.ranges, i64range[packetNumber]{
+			start: start,
+			end:   end,
 		})
 	})
 	// Ranges are parsed smallest to highest; reverse ranges slice to order them high to low.
@@ -144,7 +144,7 @@
 }
 
 func (f debugFrameAck) write(w *packetWriter) bool {
-	return w.appendAckFrame(rangeset(f.ranges), ackDelayExponent, f.ackDelay)
+	return w.appendAckFrame(rangeset[packetNumber](f.ranges), ackDelayExponent, f.ackDelay)
 }
 
 // debugFrameResetStream is a RESET_STREAM frame.
diff --git a/internal/quic/packet_codec_test.go b/internal/quic/packet_codec_test.go
index 2a2043b..efd519b 100644
--- a/internal/quic/packet_codec_test.go
+++ b/internal/quic/packet_codec_test.go
@@ -222,7 +222,7 @@
 		s: "ACK Delay=80µs [0,16) [17,32) [48,64)",
 		f: debugFrameAck{
 			ackDelay: (10 << ackDelayExponent) * time.Microsecond,
-			ranges: []i64range{
+			ranges: []i64range[packetNumber]{
 				{0x00, 0x10},
 				{0x11, 0x20},
 				{0x30, 0x40},
@@ -595,7 +595,7 @@
 		desc: "ACK frame with ECN counts",
 		want: debugFrameAck{
 			ackDelay: (10 << ackDelayExponent) * time.Microsecond,
-			ranges: []i64range{
+			ranges: []i64range[packetNumber]{
 				{0, 1},
 			},
 		},
diff --git a/internal/quic/packet_writer.go b/internal/quic/packet_writer.go
index 494105e..bfe9af7 100644
--- a/internal/quic/packet_writer.go
+++ b/internal/quic/packet_writer.go
@@ -257,7 +257,7 @@
 // to the peer potentially failing to receive an acknowledgement
 // for an older packet during a period of high packet loss or
 // reordering. This may result in unnecessary retransmissions.
-func (w *packetWriter) appendAckFrame(seen rangeset, ackDelayExponent uint8, delay time.Duration) (added bool) {
+func (w *packetWriter) appendAckFrame(seen rangeset[packetNumber], ackDelayExponent uint8, delay time.Duration) (added bool) {
 	if len(seen) == 0 {
 		return false
 	}
diff --git a/internal/quic/rangeset.go b/internal/quic/rangeset.go
index ea331ab..5339c5a 100644
--- a/internal/quic/rangeset.go
+++ b/internal/quic/rangeset.go
@@ -11,27 +11,24 @@
 //
 // Rangesets are efficient for small numbers of ranges,
 // which is expected to be the common case.
-//
-// Once we're willing to drop support for pre-generics versions of Go, this can
-// be made into a parameterized type to permit use with packetNumber without casts.
-type rangeset []i64range
+type rangeset[T ~int64] []i64range[T]
 
-type i64range struct {
-	start, end int64 // [start, end)
+type i64range[T ~int64] struct {
+	start, end T // [start, end)
 }
 
 // size returns the size of the range.
-func (r i64range) size() int64 {
+func (r i64range[T]) size() T {
 	return r.end - r.start
 }
 
 // contains reports whether v is in the range.
-func (r i64range) contains(v int64) bool {
+func (r i64range[T]) contains(v T) bool {
 	return r.start <= v && v < r.end
 }
 
 // add adds [start, end) to the set, combining it with existing ranges if necessary.
-func (s *rangeset) add(start, end int64) {
+func (s *rangeset[T]) add(start, end T) {
 	if start == end {
 		return
 	}
@@ -65,11 +62,11 @@
 		s.removeranges(i+1, j)
 		return
 	}
-	*s = append(*s, i64range{start, end})
+	*s = append(*s, i64range[T]{start, end})
 }
 
 // sub removes [start, end) from the set.
-func (s *rangeset) sub(start, end int64) {
+func (s *rangeset[T]) sub(start, end T) {
 	removefrom, removeto := -1, -1
 	for i := range *s {
 		r := &(*s)[i]
@@ -106,7 +103,7 @@
 }
 
 // contains reports whether s contains v.
-func (s rangeset) contains(v int64) bool {
+func (s rangeset[T]) contains(v T) bool {
 	for _, r := range s {
 		if v >= r.end {
 			continue
@@ -120,7 +117,7 @@
 }
 
 // rangeContaining returns the range containing v, or the range [0,0) if v is not in s.
-func (s rangeset) rangeContaining(v int64) i64range {
+func (s rangeset[T]) rangeContaining(v T) i64range[T] {
 	for _, r := range s {
 		if v >= r.end {
 			continue
@@ -130,11 +127,11 @@
 		}
 		break
 	}
-	return i64range{0, 0}
+	return i64range[T]{0, 0}
 }
 
 // min returns the minimum value in the set, or 0 if empty.
-func (s rangeset) min() int64 {
+func (s rangeset[T]) min() T {
 	if len(s) == 0 {
 		return 0
 	}
@@ -142,7 +139,7 @@
 }
 
 // max returns the maximum value in the set, or 0 if empty.
-func (s rangeset) max() int64 {
+func (s rangeset[T]) max() T {
 	if len(s) == 0 {
 		return 0
 	}
@@ -150,7 +147,7 @@
 }
 
 // end returns the end of the last range in the set, or 0 if empty.
-func (s rangeset) end() int64 {
+func (s rangeset[T]) end() T {
 	if len(s) == 0 {
 		return 0
 	}
@@ -158,7 +155,7 @@
 }
 
 // isrange reports if the rangeset covers exactly the range [start, end).
-func (s rangeset) isrange(start, end int64) bool {
+func (s rangeset[T]) isrange(start, end T) bool {
 	switch len(s) {
 	case 0:
 		return start == 0 && end == 0
@@ -169,7 +166,7 @@
 }
 
 // removeranges removes ranges [i,j).
-func (s *rangeset) removeranges(i, j int) {
+func (s *rangeset[T]) removeranges(i, j int) {
 	if i == j {
 		return
 	}
@@ -178,8 +175,8 @@
 }
 
 // insert adds a new range at index i.
-func (s *rangeset) insertrange(i int, start, end int64) {
-	*s = append(*s, i64range{})
+func (s *rangeset[T]) insertrange(i int, start, end T) {
+	*s = append(*s, i64range[T]{})
 	copy((*s)[i+1:], (*s)[i:])
-	(*s)[i] = i64range{start, end}
+	(*s)[i] = i64range[T]{start, end}
 }
diff --git a/internal/quic/rangeset_test.go b/internal/quic/rangeset_test.go
index 082f920..3080469 100644
--- a/internal/quic/rangeset_test.go
+++ b/internal/quic/rangeset_test.go
@@ -13,13 +13,13 @@
 
 func TestRangeSize(t *testing.T) {
 	for _, test := range []struct {
-		r    i64range
+		r    i64range[int64]
 		want int64
 	}{{
-		r:    i64range{0, 100},
+		r:    i64range[int64]{0, 100},
 		want: 100,
 	}, {
-		r:    i64range{10, 20},
+		r:    i64range[int64]{10, 20},
 		want: 10,
 	}} {
 		if got := test.r.size(); got != test.want {
@@ -29,7 +29,7 @@
 }
 
 func TestRangeContains(t *testing.T) {
-	r := i64range{5, 10}
+	r := i64range[int64]{5, 10}
 	for _, i := range []int64{0, 4, 10, 15} {
 		if r.contains(i) {
 			t.Errorf("%v.contains(%v) = true, want false", r, i)
@@ -45,69 +45,69 @@
 func TestRangesetAdd(t *testing.T) {
 	for _, test := range []struct {
 		desc string
-		set  rangeset
-		add  i64range
-		want rangeset
+		set  rangeset[int64]
+		add  i64range[int64]
+		want rangeset[int64]
 	}{{
 		desc: "add to empty set",
-		set:  rangeset{},
-		add:  i64range{0, 100},
-		want: rangeset{{0, 100}},
+		set:  rangeset[int64]{},
+		add:  i64range[int64]{0, 100},
+		want: rangeset[int64]{{0, 100}},
 	}, {
 		desc: "add empty range",
-		set:  rangeset{},
-		add:  i64range{100, 100},
-		want: rangeset{},
+		set:  rangeset[int64]{},
+		add:  i64range[int64]{100, 100},
+		want: rangeset[int64]{},
 	}, {
 		desc: "append nonadjacent range",
-		set:  rangeset{{100, 200}},
-		add:  i64range{300, 400},
-		want: rangeset{{100, 200}, {300, 400}},
+		set:  rangeset[int64]{{100, 200}},
+		add:  i64range[int64]{300, 400},
+		want: rangeset[int64]{{100, 200}, {300, 400}},
 	}, {
 		desc: "prepend nonadjacent range",
-		set:  rangeset{{100, 200}},
-		add:  i64range{0, 50},
-		want: rangeset{{0, 50}, {100, 200}},
+		set:  rangeset[int64]{{100, 200}},
+		add:  i64range[int64]{0, 50},
+		want: rangeset[int64]{{0, 50}, {100, 200}},
 	}, {
 		desc: "insert nonadjacent range",
-		set:  rangeset{{100, 200}, {500, 600}},
-		add:  i64range{300, 400},
-		want: rangeset{{100, 200}, {300, 400}, {500, 600}},
+		set:  rangeset[int64]{{100, 200}, {500, 600}},
+		add:  i64range[int64]{300, 400},
+		want: rangeset[int64]{{100, 200}, {300, 400}, {500, 600}},
 	}, {
 		desc: "prepend adjacent range",
-		set:  rangeset{{100, 200}},
-		add:  i64range{50, 100},
-		want: rangeset{{50, 200}},
+		set:  rangeset[int64]{{100, 200}},
+		add:  i64range[int64]{50, 100},
+		want: rangeset[int64]{{50, 200}},
 	}, {
 		desc: "append adjacent range",
-		set:  rangeset{{100, 200}},
-		add:  i64range{200, 250},
-		want: rangeset{{100, 250}},
+		set:  rangeset[int64]{{100, 200}},
+		add:  i64range[int64]{200, 250},
+		want: rangeset[int64]{{100, 250}},
 	}, {
 		desc: "prepend overlapping range",
-		set:  rangeset{{100, 200}},
-		add:  i64range{50, 150},
-		want: rangeset{{50, 200}},
+		set:  rangeset[int64]{{100, 200}},
+		add:  i64range[int64]{50, 150},
+		want: rangeset[int64]{{50, 200}},
 	}, {
 		desc: "append overlapping range",
-		set:  rangeset{{100, 200}},
-		add:  i64range{150, 250},
-		want: rangeset{{100, 250}},
+		set:  rangeset[int64]{{100, 200}},
+		add:  i64range[int64]{150, 250},
+		want: rangeset[int64]{{100, 250}},
 	}, {
 		desc: "replace range",
-		set:  rangeset{{100, 200}},
-		add:  i64range{50, 250},
-		want: rangeset{{50, 250}},
+		set:  rangeset[int64]{{100, 200}},
+		add:  i64range[int64]{50, 250},
+		want: rangeset[int64]{{50, 250}},
 	}, {
 		desc: "prepend and combine",
-		set:  rangeset{{100, 200}, {300, 400}, {500, 600}},
-		add:  i64range{50, 300},
-		want: rangeset{{50, 400}, {500, 600}},
+		set:  rangeset[int64]{{100, 200}, {300, 400}, {500, 600}},
+		add:  i64range[int64]{50, 300},
+		want: rangeset[int64]{{50, 400}, {500, 600}},
 	}, {
 		desc: "combine several ranges",
-		set:  rangeset{{100, 200}, {300, 400}, {500, 600}, {700, 800}, {900, 1000}},
-		add:  i64range{300, 850},
-		want: rangeset{{100, 200}, {300, 850}, {900, 1000}},
+		set:  rangeset[int64]{{100, 200}, {300, 400}, {500, 600}, {700, 800}, {900, 1000}},
+		add:  i64range[int64]{300, 850},
+		want: rangeset[int64]{{100, 200}, {300, 850}, {900, 1000}},
 	}} {
 		test := test
 		t.Run(test.desc, func(t *testing.T) {
@@ -125,59 +125,59 @@
 func TestRangesetSub(t *testing.T) {
 	for _, test := range []struct {
 		desc string
-		set  rangeset
-		sub  i64range
-		want rangeset
+		set  rangeset[int64]
+		sub  i64range[int64]
+		want rangeset[int64]
 	}{{
 		desc: "subtract from empty set",
-		set:  rangeset{},
-		sub:  i64range{0, 100},
-		want: rangeset{},
+		set:  rangeset[int64]{},
+		sub:  i64range[int64]{0, 100},
+		want: rangeset[int64]{},
 	}, {
 		desc: "subtract empty range",
-		set:  rangeset{{0, 100}},
-		sub:  i64range{0, 0},
-		want: rangeset{{0, 100}},
+		set:  rangeset[int64]{{0, 100}},
+		sub:  i64range[int64]{0, 0},
+		want: rangeset[int64]{{0, 100}},
 	}, {
 		desc: "subtract not present in set",
-		set:  rangeset{{0, 100}, {200, 300}},
-		sub:  i64range{100, 200},
-		want: rangeset{{0, 100}, {200, 300}},
+		set:  rangeset[int64]{{0, 100}, {200, 300}},
+		sub:  i64range[int64]{100, 200},
+		want: rangeset[int64]{{0, 100}, {200, 300}},
 	}, {
 		desc: "subtract prefix",
-		set:  rangeset{{100, 200}},
-		sub:  i64range{0, 150},
-		want: rangeset{{150, 200}},
+		set:  rangeset[int64]{{100, 200}},
+		sub:  i64range[int64]{0, 150},
+		want: rangeset[int64]{{150, 200}},
 	}, {
 		desc: "subtract suffix",
-		set:  rangeset{{100, 200}},
-		sub:  i64range{150, 300},
-		want: rangeset{{100, 150}},
+		set:  rangeset[int64]{{100, 200}},
+		sub:  i64range[int64]{150, 300},
+		want: rangeset[int64]{{100, 150}},
 	}, {
 		desc: "subtract middle",
-		set:  rangeset{{0, 100}},
-		sub:  i64range{40, 60},
-		want: rangeset{{0, 40}, {60, 100}},
+		set:  rangeset[int64]{{0, 100}},
+		sub:  i64range[int64]{40, 60},
+		want: rangeset[int64]{{0, 40}, {60, 100}},
 	}, {
 		desc: "subtract from two ranges",
-		set:  rangeset{{0, 100}, {200, 300}},
-		sub:  i64range{50, 250},
-		want: rangeset{{0, 50}, {250, 300}},
+		set:  rangeset[int64]{{0, 100}, {200, 300}},
+		sub:  i64range[int64]{50, 250},
+		want: rangeset[int64]{{0, 50}, {250, 300}},
 	}, {
 		desc: "subtract removes range",
-		set:  rangeset{{0, 100}, {200, 300}, {400, 500}},
-		sub:  i64range{200, 300},
-		want: rangeset{{0, 100}, {400, 500}},
+		set:  rangeset[int64]{{0, 100}, {200, 300}, {400, 500}},
+		sub:  i64range[int64]{200, 300},
+		want: rangeset[int64]{{0, 100}, {400, 500}},
 	}, {
 		desc: "subtract removes multiple ranges",
-		set:  rangeset{{0, 100}, {200, 300}, {400, 500}, {600, 700}},
-		sub:  i64range{50, 650},
-		want: rangeset{{0, 50}, {650, 700}},
+		set:  rangeset[int64]{{0, 100}, {200, 300}, {400, 500}, {600, 700}},
+		sub:  i64range[int64]{50, 650},
+		want: rangeset[int64]{{0, 50}, {650, 700}},
 	}, {
 		desc: "subtract only range",
-		set:  rangeset{{0, 100}},
-		sub:  i64range{0, 100},
-		want: rangeset{},
+		set:  rangeset[int64]{{0, 100}},
+		sub:  i64range[int64]{0, 100},
+		want: rangeset[int64]{},
 	}} {
 		test := test
 		t.Run(test.desc, func(t *testing.T) {
@@ -193,7 +193,7 @@
 }
 
 func TestRangesetContains(t *testing.T) {
-	var s rangeset
+	var s rangeset[int64]
 	s.add(10, 20)
 	s.add(30, 40)
 	for i := int64(0); i < 50; i++ {
@@ -205,23 +205,23 @@
 }
 
 func TestRangesetRangeContaining(t *testing.T) {
-	var s rangeset
+	var s rangeset[int64]
 	s.add(10, 20)
 	s.add(30, 40)
 	for _, test := range []struct {
 		v    int64
-		want i64range
+		want i64range[int64]
 	}{
-		{0, i64range{0, 0}},
-		{9, i64range{0, 0}},
-		{10, i64range{10, 20}},
-		{15, i64range{10, 20}},
-		{19, i64range{10, 20}},
-		{20, i64range{0, 0}},
-		{29, i64range{0, 0}},
-		{30, i64range{30, 40}},
-		{39, i64range{30, 40}},
-		{40, i64range{0, 0}},
+		{0, i64range[int64]{0, 0}},
+		{9, i64range[int64]{0, 0}},
+		{10, i64range[int64]{10, 20}},
+		{15, i64range[int64]{10, 20}},
+		{19, i64range[int64]{10, 20}},
+		{20, i64range[int64]{0, 0}},
+		{29, i64range[int64]{0, 0}},
+		{30, i64range[int64]{30, 40}},
+		{39, i64range[int64]{30, 40}},
+		{40, i64range[int64]{0, 0}},
 	} {
 		got := s.rangeContaining(test.v)
 		if got != test.want {
@@ -232,22 +232,22 @@
 
 func TestRangesetLimits(t *testing.T) {
 	for _, test := range []struct {
-		s       rangeset
+		s       rangeset[int64]
 		wantMin int64
 		wantMax int64
 		wantEnd int64
 	}{{
-		s:       rangeset{},
+		s:       rangeset[int64]{},
 		wantMin: 0,
 		wantMax: 0,
 		wantEnd: 0,
 	}, {
-		s:       rangeset{{10, 20}},
+		s:       rangeset[int64]{{10, 20}},
 		wantMin: 10,
 		wantMax: 19,
 		wantEnd: 20,
 	}, {
-		s:       rangeset{{10, 20}, {30, 40}, {50, 60}},
+		s:       rangeset[int64]{{10, 20}, {30, 40}, {50, 60}},
 		wantMin: 10,
 		wantMax: 59,
 		wantEnd: 60,
@@ -266,28 +266,28 @@
 
 func TestRangesetIsRange(t *testing.T) {
 	for _, test := range []struct {
-		s    rangeset
-		r    i64range
+		s    rangeset[int64]
+		r    i64range[int64]
 		want bool
 	}{{
-		s:    rangeset{{0, 100}},
-		r:    i64range{0, 100},
+		s:    rangeset[int64]{{0, 100}},
+		r:    i64range[int64]{0, 100},
 		want: true,
 	}, {
-		s:    rangeset{{0, 100}},
-		r:    i64range{0, 101},
+		s:    rangeset[int64]{{0, 100}},
+		r:    i64range[int64]{0, 101},
 		want: false,
 	}, {
-		s:    rangeset{{0, 10}, {11, 100}},
-		r:    i64range{0, 100},
+		s:    rangeset[int64]{{0, 10}, {11, 100}},
+		r:    i64range[int64]{0, 100},
 		want: false,
 	}, {
-		s:    rangeset{},
-		r:    i64range{0, 0},
+		s:    rangeset[int64]{},
+		r:    i64range[int64]{0, 0},
 		want: true,
 	}, {
-		s:    rangeset{},
-		r:    i64range{0, 1},
+		s:    rangeset[int64]{},
+		r:    i64range[int64]{0, 1},
 		want: false,
 	}} {
 		if got := test.s.isrange(test.r.start, test.r.end); got != test.want {
diff --git a/internal/quic/sent_packet_test.go b/internal/quic/sent_packet_test.go
index c01a2fb..c0b04e6 100644
--- a/internal/quic/sent_packet_test.go
+++ b/internal/quic/sent_packet_test.go
@@ -13,7 +13,7 @@
 		byte(frameTypePing),
 		byte(frameTypeStreamBase),
 		uint64(1),
-		i64range{1 << 20, 1<<20 + 1024},
+		i64range[int64]{1 << 20, 1<<20 + 1024},
 	}
 	// Record sent frames.
 	sent := newSentPacket()
@@ -23,7 +23,7 @@
 			sent.appendAckElicitingFrame(f)
 		case uint64:
 			sent.appendInt(f)
-		case i64range:
+		case i64range[int64]:
 			sent.appendOffAndSize(f.start, int(f.size()))
 		}
 	}
@@ -41,7 +41,7 @@
 			if got := sent.nextInt(); got != want {
 				t.Fatalf("%v: sent.nextInt() = %v, want %v", i, got, want)
 			}
-		case i64range:
+		case i64range[int64]:
 			if start, end := sent.nextRange(); start != want.start || end != want.end {
 				t.Fatalf("%v: sent.nextRange() = [%v,%v), want %v", i, start, end, want)
 			}