From 16bd559d9a4d13ab1819f0f40430dc7f019b5b49 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 14 May 2016 16:48:19 +0700 Subject: [PATCH] negotiate idle connection state lifetime work towards #20 --- handshake/connection_parameters_manager.go | 35 ++++++++----- .../connection_parameters_manager_test.go | 50 ++++++++++++++----- protocol/protocol.go | 5 ++ protocol/server_parameters.go | 4 ++ utils/minmax.go | 7 +++ utils/minmax_test.go | 5 ++ 6 files changed, 82 insertions(+), 24 deletions(-) diff --git a/handshake/connection_parameters_manager.go b/handshake/connection_parameters_manager.go index 2d153f25..c36890f5 100644 --- a/handshake/connection_parameters_manager.go +++ b/handshake/connection_parameters_manager.go @@ -18,6 +18,7 @@ type ConnectionParametersManager struct { params map[Tag][]byte mutex sync.RWMutex + idleConnectionStateLifetime time.Duration sendStreamFlowControlWindow protocol.ByteCount sendConnectionFlowControlWindow protocol.ByteCount receiveStreamFlowControlWindow protocol.ByteCount @@ -31,9 +32,9 @@ var ErrTagNotInConnectionParameterMap = errors.New("Tag not found in Connections func NewConnectionParamatersManager() *ConnectionParametersManager { return &ConnectionParametersManager{ params: map[Tag][]byte{ - TagICSL: {0x1e, 0x00, 0x00, 0x00}, // idle connection state lifetime = 30s TagMSPC: {0x64, 0x00, 0x00, 0x00}, // Max streams per connection = 100 }, + idleConnectionStateLifetime: protocol.InitialIdleConnectionStateLifetime, sendStreamFlowControlWindow: protocol.InitialStreamFlowControlWindow, // can only be changed by the client sendConnectionFlowControlWindow: protocol.InitialConnectionFlowControlWindow, // can only be changed by the client receiveStreamFlowControlWindow: protocol.ReceiveStreamFlowControlWindow, @@ -48,8 +49,14 @@ func (h *ConnectionParametersManager) SetFromMap(params map[Tag][]byte) error { for key, value := range params { switch key { - case TagICSL, TagMSPC, TagTCID: + case TagMSPC, TagTCID: h.params[key] = value + case TagICSL: + clientValue, err := utils.ReadUint32(bytes.NewBuffer(value)) + if err != nil { + return err + } + h.idleConnectionStateLifetime = h.negotiateIdleConnectionStateLifetime(time.Duration(clientValue) * time.Second) case TagSFCW: sendStreamFlowControlWindow, err := utils.ReadUint32(bytes.NewBuffer(value)) if err != nil { @@ -68,6 +75,11 @@ func (h *ConnectionParametersManager) SetFromMap(params map[Tag][]byte) error { return nil } +func (h *ConnectionParametersManager) negotiateIdleConnectionStateLifetime(clientValue time.Duration) time.Duration { + // TODO: what happens if the clients sets 0 seconds? + return utils.MinDuration(clientValue, protocol.MaxIdleConnectionStateLifetime) +} + // getRawValue gets the byte-slice for a tag func (h *ConnectionParametersManager) getRawValue(tag Tag) ([]byte, error) { h.mutex.RLock() @@ -83,12 +95,15 @@ func (h *ConnectionParametersManager) getRawValue(tag Tag) ([]byte, error) { // GetSHLOMap gets all values (except crypto values) needed for the SHLO func (h *ConnectionParametersManager) GetSHLOMap() map[Tag][]byte { sfcw := bytes.NewBuffer([]byte{}) - cfcw := bytes.NewBuffer([]byte{}) utils.WriteUint32(sfcw, uint32(h.GetReceiveStreamFlowControlWindow())) + cfcw := bytes.NewBuffer([]byte{}) utils.WriteUint32(cfcw, uint32(h.GetReceiveConnectionFlowControlWindow())) + icsl := bytes.NewBuffer([]byte{}) + utils.Debugf("ICSL: %#v\n", h.GetIdleConnectionStateLifetime()) + utils.WriteUint32(icsl, uint32(h.GetIdleConnectionStateLifetime()/time.Second)) return map[Tag][]byte{ - TagICSL: []byte{0x1e, 0x00, 0x00, 0x00}, //30 + TagICSL: icsl.Bytes(), TagMSPC: []byte{0x64, 0x00, 0x00, 0x00}, //100 TagCFCW: cfcw.Bytes(), TagSFCW: sfcw.Bytes(), @@ -129,14 +144,10 @@ func (h *ConnectionParametersManager) GetReceiveConnectionFlowControlWindow() pr // GetIdleConnectionStateLifetime gets the idle timeout func (h *ConnectionParametersManager) GetIdleConnectionStateLifetime() time.Duration { - rawValue, err := h.getRawValue(TagICSL) - if err != nil { - panic("ConnectionParameters: Could not find ICSL") - } - if len(rawValue) != 4 { - panic("ConnectionParameters: ICSL has invalid value") - } - return time.Duration(binary.LittleEndian.Uint32(rawValue)) * time.Second + h.mutex.RLock() + defer h.mutex.RUnlock() + + return h.idleConnectionStateLifetime } // TruncateConnectionID determines if the client requests truncated ConnectionIDs diff --git a/handshake/connection_parameters_manager_test.go b/handshake/connection_parameters_manager_test.go index 02d0a5b6..81891b78 100644 --- a/handshake/connection_parameters_manager_test.go +++ b/handshake/connection_parameters_manager_test.go @@ -15,16 +15,16 @@ var _ = Describe("ConnectionsParameterManager", func() { }) It("stores and retrieves a value", func() { - icsl := []byte{0x13, 0x37} + mspc := []byte{0x13, 0x37} values := map[Tag][]byte{ - TagICSL: icsl, + TagMSPC: mspc, } cpm.SetFromMap(values) - val, err := cpm.getRawValue(TagICSL) + val, err := cpm.getRawValue(TagMSPC) Expect(err).ToNot(HaveOccurred()) - Expect(val).To(Equal(icsl)) + Expect(val).To(Equal(mspc)) }) It("returns an error for a tag that is not set", func() { @@ -53,6 +53,13 @@ var _ = Describe("ConnectionsParameterManager", func() { Expect(entryMap).To(HaveKey(TagCFCW)) Expect(entryMap[TagCFCW]).To(Equal([]byte{0xAD, 0xFB, 0xCA, 0xDE})) }) + + It("returns connection-level flow control windows in SHLO", func() { + cpm.idleConnectionStateLifetime = 0xDECAFBAD * time.Second + entryMap := cpm.GetSHLOMap() + Expect(entryMap).To(HaveKey(TagICSL)) + Expect(entryMap[TagICSL]).To(Equal([]byte{0xAD, 0xFB, 0xCA, 0xDE})) + }) }) Context("Truncated connection IDs", func() { @@ -123,14 +130,33 @@ var _ = Describe("ConnectionsParameterManager", func() { }) }) - It("gets idle connection state lifetime", func() { - cpm.params[TagICSL] = []byte{0xad, 0xfb, 0xca, 0xde} - val := cpm.GetIdleConnectionStateLifetime() - Expect(val).To(Equal(0xdecafbad * time.Second)) - }) + Context("idle connection state lifetime", func() { + It("has initial idle conneciton state lifetime", func() { + Expect(cpm.GetIdleConnectionStateLifetime()).To(Equal(protocol.InitialIdleConnectionStateLifetime)) + }) - It("has initial idle conneciton state lifetime", func() { - val := cpm.GetIdleConnectionStateLifetime() - Expect(val).To(Equal(30 * time.Second)) + It("negotiates correctly when the client wants a longer lifetime", func() { + Expect(cpm.negotiateIdleConnectionStateLifetime(protocol.MaxIdleConnectionStateLifetime + 10*time.Second)).To(Equal(protocol.MaxIdleConnectionStateLifetime)) + }) + + It("negotiates correctly when the client wants a shorter lifetime", func() { + Expect(cpm.negotiateIdleConnectionStateLifetime(protocol.MaxIdleConnectionStateLifetime - 1*time.Second)).To(Equal(protocol.MaxIdleConnectionStateLifetime - 1*time.Second)) + }) + + It("sets the negotiated lifetime", func() { + // this test only works if the value given here is smaller than protocol.MaxIdleConnectionStateLifetime + values := map[Tag][]byte{ + TagICSL: []byte{10, 0, 0, 0}, + } + err := cpm.SetFromMap(values) + Expect(err).ToNot(HaveOccurred()) + Expect(cpm.GetIdleConnectionStateLifetime()).To(Equal(10 * time.Second)) + }) + + It("gets idle connection state lifetime", func() { + value := 0xDECAFBAD * time.Second + cpm.idleConnectionStateLifetime = value + Expect(cpm.GetIdleConnectionStateLifetime()).To(Equal(value)) + }) }) }) diff --git a/protocol/protocol.go b/protocol/protocol.go index 012307ad..ee7fdf09 100644 --- a/protocol/protocol.go +++ b/protocol/protocol.go @@ -1,5 +1,7 @@ package protocol +import "time" + // A PacketNumber in QUIC type PacketNumber uint64 @@ -46,3 +48,6 @@ const InitialStreamFlowControlWindow ByteCount = (1 << 14) // 16 kB // InitialConnectionFlowControlWindow is the initial connection-level flow control window for sending const InitialConnectionFlowControlWindow ByteCount = (1 << 14) // 16 kB + +// InitialIdleConnectionStateLifetime is the initial idle connection state lifetime +const InitialIdleConnectionStateLifetime = 30 * time.Second diff --git a/protocol/server_parameters.go b/protocol/server_parameters.go index c0dccf5b..752c334f 100644 --- a/protocol/server_parameters.go +++ b/protocol/server_parameters.go @@ -31,3 +31,7 @@ const ReceiveStreamFlowControlWindow ByteCount = (1 << 20) // 1 MB // ReceiveConnectionFlowControlWindow is the stream-level flow control window for receiving data // TODO: set a reasonable value here const ReceiveConnectionFlowControlWindow ByteCount = (1 << 20) // 1 MB + +// MaxIdleConnectionStateLifetime is the maximum value we accept for the idle connection state lifetime +// TODO: set a reasonable value here +const MaxIdleConnectionStateLifetime = 60 * time.Second diff --git a/utils/minmax.go b/utils/minmax.go index e3650d82..1fd514cb 100644 --- a/utils/minmax.go +++ b/utils/minmax.go @@ -58,6 +58,13 @@ func MaxDuration(a, b time.Duration) time.Duration { return b } +func MinDuration(a, b time.Duration) time.Duration { + if a > b { + return b + } + return a +} + // AbsDuration returns the absolute value of a time duration func AbsDuration(d time.Duration) time.Duration { if d >= 0 { diff --git a/utils/minmax_test.go b/utils/minmax_test.go index d3144b0c..e09a8493 100644 --- a/utils/minmax_test.go +++ b/utils/minmax_test.go @@ -33,6 +33,11 @@ var _ = Describe("Min / Max", func() { Expect(MaxDuration(time.Microsecond, time.Nanosecond)).To(Equal(time.Microsecond)) Expect(MaxDuration(time.Nanosecond, time.Microsecond)).To(Equal(time.Microsecond)) }) + + It("returns the minimum duration", func() { + Expect(MinDuration(time.Microsecond, time.Nanosecond)).To(Equal(time.Nanosecond)) + Expect(MinDuration(time.Nanosecond, time.Microsecond)).To(Equal(time.Nanosecond)) + }) }) Context("Min", func() {