From 031c1709e6b18c0db1cca7c0a5518f9862913426 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 14 Sep 2019 14:11:28 +0700 Subject: [PATCH] implement a basic store for new connection IDs --- codecov.yml | 1 + conn_id_manager.go | 45 ++++ conn_id_manager_test.go | 92 ++++++++ internal/utils/gen.go | 1 + internal/utils/new_connection_id.go | 12 + internal/utils/newconnectionid_linkedlist.go | 217 +++++++++++++++++++ 6 files changed, 368 insertions(+) create mode 100644 conn_id_manager.go create mode 100644 conn_id_manager_test.go create mode 100644 internal/utils/new_connection_id.go create mode 100644 internal/utils/newconnectionid_linkedlist.go diff --git a/codecov.yml b/codecov.yml index 7cd2d24d..4447373d 100644 --- a/codecov.yml +++ b/codecov.yml @@ -9,6 +9,7 @@ coverage: - interop/ - internal/ackhandler/packet_linkedlist.go - internal/utils/byteinterval_linkedlist.go + - internal/utils/newconnectionid_linkedlist.go - internal/utils/packetinterval_linkedlist.go - internal/utils/linkedlist/linkedlist.go - quictrace/ diff --git a/conn_id_manager.go b/conn_id_manager.go new file mode 100644 index 00000000..9db3733e --- /dev/null +++ b/conn_id_manager.go @@ -0,0 +1,45 @@ +package quic + +import ( + "fmt" + + "github.com/lucas-clemente/quic-go/internal/utils" + "github.com/lucas-clemente/quic-go/internal/wire" +) + +type connIDManager struct { + queue utils.NewConnectionIDList +} + +func (h *connIDManager) Add(f *wire.NewConnectionIDFrame) error { + // insert a new element at the end + if h.queue.Len() == 0 || h.queue.Back().Value.SequenceNumber < f.SequenceNumber { + h.queue.PushBack(utils.NewConnectionID{ + SequenceNumber: f.SequenceNumber, + ConnectionID: f.ConnectionID, + StatelessResetToken: &f.StatelessResetToken, + }) + return nil + } + // insert a new element somewhere in the middle + for el := h.queue.Front(); el != nil; el = el.Next() { + if el.Value.SequenceNumber == f.SequenceNumber { + if !el.Value.ConnectionID.Equal(f.ConnectionID) { + return fmt.Errorf("received conflicting connection IDs for sequence number %d", f.SequenceNumber) + } + if *el.Value.StatelessResetToken != f.StatelessResetToken { + return fmt.Errorf("received conflicting stateless reset tokens for sequence number %d", f.SequenceNumber) + } + return nil + } + if el.Value.SequenceNumber > f.SequenceNumber { + h.queue.InsertBefore(utils.NewConnectionID{ + SequenceNumber: f.SequenceNumber, + ConnectionID: f.ConnectionID, + StatelessResetToken: &f.StatelessResetToken, + }, el) + return nil + } + } + panic("should have processed NEW_CONNECTION_ID frame") +} diff --git a/conn_id_manager_test.go b/conn_id_manager_test.go new file mode 100644 index 00000000..7c531ea5 --- /dev/null +++ b/conn_id_manager_test.go @@ -0,0 +1,92 @@ +package quic + +import ( + "github.com/lucas-clemente/quic-go/internal/protocol" + "github.com/lucas-clemente/quic-go/internal/wire" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Connection ID Manager", func() { + var m *connIDManager + + BeforeEach(func() { + m = &connIDManager{} + }) + + get := func() (protocol.ConnectionID, *[16]byte) { + if m.queue.Len() == 0 { + return nil, nil + } + val := m.queue.Remove(m.queue.Front()) + return val.ConnectionID, val.StatelessResetToken + } + + It("returns nil if empty", func() { + c, rt := get() + Expect(c).To(BeNil()) + Expect(rt).To(BeNil()) + }) + + It("adds and gets connection IDs", func() { + Expect(m.Add(&wire.NewConnectionIDFrame{ + SequenceNumber: 10, + ConnectionID: protocol.ConnectionID{2, 3, 4, 5}, + StatelessResetToken: [16]byte{0xe, 0xd, 0xc, 0xb, 0xa, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}, + })).To(Succeed()) + Expect(m.Add(&wire.NewConnectionIDFrame{ + SequenceNumber: 4, + ConnectionID: protocol.ConnectionID{1, 2, 3, 4}, + StatelessResetToken: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe}, + })).To(Succeed()) + c1, rt1 := get() + Expect(c1).To(Equal(protocol.ConnectionID{1, 2, 3, 4})) + Expect(*rt1).To(Equal([16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe})) + c2, rt2 := get() + Expect(c2).To(Equal(protocol.ConnectionID{2, 3, 4, 5})) + Expect(*rt2).To(Equal([16]byte{0xe, 0xd, 0xc, 0xb, 0xa, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0})) + c3, rt3 := get() + Expect(c3).To(BeNil()) + Expect(rt3).To(BeNil()) + }) + + It("accepts duplicates", func() { + f := &wire.NewConnectionIDFrame{ + SequenceNumber: 1, + ConnectionID: protocol.ConnectionID{1, 2, 3, 4}, + StatelessResetToken: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe}, + } + Expect(m.Add(f)).To(Succeed()) + Expect(m.Add(f)).To(Succeed()) + c1, rt1 := get() + Expect(c1).To(Equal(protocol.ConnectionID{1, 2, 3, 4})) + Expect(*rt1).To(Equal([16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe})) + c2, rt2 := get() + Expect(c2).To(BeNil()) + Expect(rt2).To(BeNil()) + }) + + It("rejects duplicates with different connection IDs", func() { + Expect(m.Add(&wire.NewConnectionIDFrame{ + SequenceNumber: 42, + ConnectionID: protocol.ConnectionID{1, 2, 3, 4}, + })).To(Succeed()) + Expect(m.Add(&wire.NewConnectionIDFrame{ + SequenceNumber: 42, + ConnectionID: protocol.ConnectionID{2, 3, 4, 5}, + })).To(MatchError("received conflicting connection IDs for sequence number 42")) + }) + + It("rejects duplicates with different connection IDs", func() { + Expect(m.Add(&wire.NewConnectionIDFrame{ + SequenceNumber: 42, + ConnectionID: protocol.ConnectionID{1, 2, 3, 4}, + StatelessResetToken: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe}, + })).To(Succeed()) + Expect(m.Add(&wire.NewConnectionIDFrame{ + SequenceNumber: 42, + ConnectionID: protocol.ConnectionID{1, 2, 3, 4}, + StatelessResetToken: [16]byte{0xe, 0xd, 0xc, 0xb, 0xa, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}, + })).To(MatchError("received conflicting stateless reset tokens for sequence number 42")) + }) +}) diff --git a/internal/utils/gen.go b/internal/utils/gen.go index bb839be6..8a63e958 100644 --- a/internal/utils/gen.go +++ b/internal/utils/gen.go @@ -2,3 +2,4 @@ package utils //go:generate genny -pkg utils -in linkedlist/linkedlist.go -out byteinterval_linkedlist.go gen Item=ByteInterval //go:generate genny -pkg utils -in linkedlist/linkedlist.go -out packetinterval_linkedlist.go gen Item=PacketInterval +//go:generate genny -pkg utils -in linkedlist/linkedlist.go -out newconnectionid_linkedlist.go gen Item=NewConnectionID diff --git a/internal/utils/new_connection_id.go b/internal/utils/new_connection_id.go new file mode 100644 index 00000000..dd0d8bda --- /dev/null +++ b/internal/utils/new_connection_id.go @@ -0,0 +1,12 @@ +package utils + +import ( + "github.com/lucas-clemente/quic-go/internal/protocol" +) + +// NewConnectionID is a new connection ID +type NewConnectionID struct { + SequenceNumber uint64 + ConnectionID protocol.ConnectionID + StatelessResetToken *[16]byte +} diff --git a/internal/utils/newconnectionid_linkedlist.go b/internal/utils/newconnectionid_linkedlist.go new file mode 100644 index 00000000..d59562e5 --- /dev/null +++ b/internal/utils/newconnectionid_linkedlist.go @@ -0,0 +1,217 @@ +// This file was automatically generated by genny. +// Any changes will be lost if this file is regenerated. +// see https://github.com/cheekybits/genny + +package utils + +// Linked list implementation from the Go standard library. + +// NewConnectionIDElement is an element of a linked list. +type NewConnectionIDElement struct { + // Next and previous pointers in the doubly-linked list of elements. + // To simplify the implementation, internally a list l is implemented + // as a ring, such that &l.root is both the next element of the last + // list element (l.Back()) and the previous element of the first list + // element (l.Front()). + next, prev *NewConnectionIDElement + + // The list to which this element belongs. + list *NewConnectionIDList + + // The value stored with this element. + Value NewConnectionID +} + +// Next returns the next list element or nil. +func (e *NewConnectionIDElement) Next() *NewConnectionIDElement { + if p := e.next; e.list != nil && p != &e.list.root { + return p + } + return nil +} + +// Prev returns the previous list element or nil. +func (e *NewConnectionIDElement) Prev() *NewConnectionIDElement { + if p := e.prev; e.list != nil && p != &e.list.root { + return p + } + return nil +} + +// NewConnectionIDList is a linked list of NewConnectionIDs. +type NewConnectionIDList struct { + root NewConnectionIDElement // sentinel list element, only &root, root.prev, and root.next are used + len int // current list length excluding (this) sentinel element +} + +// Init initializes or clears list l. +func (l *NewConnectionIDList) Init() *NewConnectionIDList { + l.root.next = &l.root + l.root.prev = &l.root + l.len = 0 + return l +} + +// NewNewConnectionIDList returns an initialized list. +func NewNewConnectionIDList() *NewConnectionIDList { return new(NewConnectionIDList).Init() } + +// Len returns the number of elements of list l. +// The complexity is O(1). +func (l *NewConnectionIDList) Len() int { return l.len } + +// Front returns the first element of list l or nil if the list is empty. +func (l *NewConnectionIDList) Front() *NewConnectionIDElement { + if l.len == 0 { + return nil + } + return l.root.next +} + +// Back returns the last element of list l or nil if the list is empty. +func (l *NewConnectionIDList) Back() *NewConnectionIDElement { + if l.len == 0 { + return nil + } + return l.root.prev +} + +// lazyInit lazily initializes a zero List value. +func (l *NewConnectionIDList) lazyInit() { + if l.root.next == nil { + l.Init() + } +} + +// insert inserts e after at, increments l.len, and returns e. +func (l *NewConnectionIDList) insert(e, at *NewConnectionIDElement) *NewConnectionIDElement { + n := at.next + at.next = e + e.prev = at + e.next = n + n.prev = e + e.list = l + l.len++ + return e +} + +// insertValue is a convenience wrapper for insert(&Element{Value: v}, at). +func (l *NewConnectionIDList) insertValue(v NewConnectionID, at *NewConnectionIDElement) *NewConnectionIDElement { + return l.insert(&NewConnectionIDElement{Value: v}, at) +} + +// remove removes e from its list, decrements l.len, and returns e. +func (l *NewConnectionIDList) remove(e *NewConnectionIDElement) *NewConnectionIDElement { + e.prev.next = e.next + e.next.prev = e.prev + e.next = nil // avoid memory leaks + e.prev = nil // avoid memory leaks + e.list = nil + l.len-- + return e +} + +// Remove removes e from l if e is an element of list l. +// It returns the element value e.Value. +// The element must not be nil. +func (l *NewConnectionIDList) Remove(e *NewConnectionIDElement) NewConnectionID { + if e.list == l { + // if e.list == l, l must have been initialized when e was inserted + // in l or l == nil (e is a zero Element) and l.remove will crash + l.remove(e) + } + return e.Value +} + +// PushFront inserts a new element e with value v at the front of list l and returns e. +func (l *NewConnectionIDList) PushFront(v NewConnectionID) *NewConnectionIDElement { + l.lazyInit() + return l.insertValue(v, &l.root) +} + +// PushBack inserts a new element e with value v at the back of list l and returns e. +func (l *NewConnectionIDList) PushBack(v NewConnectionID) *NewConnectionIDElement { + l.lazyInit() + return l.insertValue(v, l.root.prev) +} + +// InsertBefore inserts a new element e with value v immediately before mark and returns e. +// If mark is not an element of l, the list is not modified. +// The mark must not be nil. +func (l *NewConnectionIDList) InsertBefore(v NewConnectionID, mark *NewConnectionIDElement) *NewConnectionIDElement { + if mark.list != l { + return nil + } + // see comment in List.Remove about initialization of l + return l.insertValue(v, mark.prev) +} + +// InsertAfter inserts a new element e with value v immediately after mark and returns e. +// If mark is not an element of l, the list is not modified. +// The mark must not be nil. +func (l *NewConnectionIDList) InsertAfter(v NewConnectionID, mark *NewConnectionIDElement) *NewConnectionIDElement { + if mark.list != l { + return nil + } + // see comment in List.Remove about initialization of l + return l.insertValue(v, mark) +} + +// MoveToFront moves element e to the front of list l. +// If e is not an element of l, the list is not modified. +// The element must not be nil. +func (l *NewConnectionIDList) MoveToFront(e *NewConnectionIDElement) { + if e.list != l || l.root.next == e { + return + } + // see comment in List.Remove about initialization of l + l.insert(l.remove(e), &l.root) +} + +// MoveToBack moves element e to the back of list l. +// If e is not an element of l, the list is not modified. +// The element must not be nil. +func (l *NewConnectionIDList) MoveToBack(e *NewConnectionIDElement) { + if e.list != l || l.root.prev == e { + return + } + // see comment in List.Remove about initialization of l + l.insert(l.remove(e), l.root.prev) +} + +// MoveBefore moves element e to its new position before mark. +// If e or mark is not an element of l, or e == mark, the list is not modified. +// The element and mark must not be nil. +func (l *NewConnectionIDList) MoveBefore(e, mark *NewConnectionIDElement) { + if e.list != l || e == mark || mark.list != l { + return + } + l.insert(l.remove(e), mark.prev) +} + +// MoveAfter moves element e to its new position after mark. +// If e or mark is not an element of l, or e == mark, the list is not modified. +// The element and mark must not be nil. +func (l *NewConnectionIDList) MoveAfter(e, mark *NewConnectionIDElement) { + if e.list != l || e == mark || mark.list != l { + return + } + l.insert(l.remove(e), mark) +} + +// PushBackList inserts a copy of an other list at the back of list l. +// The lists l and other may be the same. They must not be nil. +func (l *NewConnectionIDList) PushBackList(other *NewConnectionIDList) { + l.lazyInit() + for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() { + l.insertValue(e.Value, l.root.prev) + } +} + +// PushFrontList inserts a copy of an other list at the front of list l. +// The lists l and other may be the same. They must not be nil. +func (l *NewConnectionIDList) PushFrontList(other *NewConnectionIDList) { + l.lazyInit() + for i, e := other.Len(), other.Back(); i > 0; i, e = i-1, e.Prev() { + l.insertValue(e.Value, &l.root) + } +}