From a587af079d2b32d3cfff8f7261ead85e339befc0 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 22 Dec 2016 01:08:34 +0700 Subject: [PATCH] improve the connection parameters manager for the client --- flowcontrol/flow_controller.go | 4 +- flowcontrol/flow_controller_test.go | 35 +++-- handshake/connection_parameters_manager.go | 109 ++++++++------- .../connection_parameters_manager_test.go | 131 +++++++++--------- handshake/crypto_setup_client.go | 2 +- handshake/crypto_setup_client_test.go | 2 +- handshake/crypto_setup_server.go | 2 +- protocol/server_parameters.go | 25 +++- streams_map_test.go | 11 +- 9 files changed, 176 insertions(+), 145 deletions(-) diff --git a/flowcontrol/flow_controller.go b/flowcontrol/flow_controller.go index 6acd8d35..4dadf7c8 100644 --- a/flowcontrol/flow_controller.go +++ b/flowcontrol/flow_controller.go @@ -42,11 +42,11 @@ func newFlowController(streamID protocol.StreamID, connectionParameters handshak if streamID == 0 { fc.receiveFlowControlWindow = connectionParameters.GetReceiveConnectionFlowControlWindow() fc.receiveFlowControlWindowIncrement = fc.receiveFlowControlWindow - fc.maxReceiveFlowControlWindowIncrement = protocol.MaxReceiveConnectionFlowControlWindow + fc.maxReceiveFlowControlWindowIncrement = connectionParameters.GetMaxReceiveConnectionFlowControlWindow() } else { fc.receiveFlowControlWindow = connectionParameters.GetReceiveStreamFlowControlWindow() fc.receiveFlowControlWindowIncrement = fc.receiveFlowControlWindow - fc.maxReceiveFlowControlWindowIncrement = protocol.MaxReceiveStreamFlowControlWindow + fc.maxReceiveFlowControlWindowIncrement = connectionParameters.GetMaxReceiveStreamFlowControlWindow() } return &fc diff --git a/flowcontrol/flow_controller_test.go b/flowcontrol/flow_controller_test.go index 9afc92d8..db71920e 100644 --- a/flowcontrol/flow_controller_test.go +++ b/flowcontrol/flow_controller_test.go @@ -11,19 +11,18 @@ import ( ) type mockConnectionParametersManager struct { - sendStreamFlowControlWindow protocol.ByteCount - sendConnectionFlowControlWindow protocol.ByteCount - receiveStreamFlowControlWindow protocol.ByteCount - receiveConnectionFlowControlWindow protocol.ByteCount + sendStreamFlowControlWindow protocol.ByteCount + sendConnectionFlowControlWindow protocol.ByteCount + receiveStreamFlowControlWindow protocol.ByteCount + maxReceiveStreamFlowControlWindow protocol.ByteCount + receiveConnectionFlowControlWindow protocol.ByteCount + maxReceiveConnectionFlowControlWindow protocol.ByteCount } func (m *mockConnectionParametersManager) SetFromMap(map[handshake.Tag][]byte) error { panic("not implemented") } -func (m *mockConnectionParametersManager) GetSHLOMap() (map[handshake.Tag][]byte, error) { - panic("not implemented") -} -func (m *mockConnectionParametersManager) GetCHLOMap() (map[handshake.Tag][]byte, error) { +func (m *mockConnectionParametersManager) GetHelloMap() (map[handshake.Tag][]byte, error) { panic("not implemented") } func (m *mockConnectionParametersManager) GetSendStreamFlowControlWindow() protocol.ByteCount { @@ -35,9 +34,15 @@ func (m *mockConnectionParametersManager) GetSendConnectionFlowControlWindow() p func (m *mockConnectionParametersManager) GetReceiveStreamFlowControlWindow() protocol.ByteCount { return m.receiveStreamFlowControlWindow } +func (m *mockConnectionParametersManager) GetMaxReceiveStreamFlowControlWindow() protocol.ByteCount { + return m.maxReceiveStreamFlowControlWindow +} func (m *mockConnectionParametersManager) GetReceiveConnectionFlowControlWindow() protocol.ByteCount { return m.receiveConnectionFlowControlWindow } +func (m *mockConnectionParametersManager) GetMaxReceiveConnectionFlowControlWindow() protocol.ByteCount { + return m.maxReceiveConnectionFlowControlWindow +} func (m *mockConnectionParametersManager) GetMaxOutgoingStreams() uint32 { panic("not implemented") } func (m *mockConnectionParametersManager) GetMaxIncomingStreams() uint32 { panic("not implemented") } func (m *mockConnectionParametersManager) GetIdleConnectionStateLifetime() time.Duration { @@ -61,10 +66,12 @@ var _ = Describe("Flow controller", func() { BeforeEach(func() { cpm = &mockConnectionParametersManager{ - sendStreamFlowControlWindow: 1000, - receiveStreamFlowControlWindow: 2000, - sendConnectionFlowControlWindow: 3000, - receiveConnectionFlowControlWindow: 4000, + sendStreamFlowControlWindow: 1000, + receiveStreamFlowControlWindow: 2000, + sendConnectionFlowControlWindow: 3000, + receiveConnectionFlowControlWindow: 4000, + maxReceiveStreamFlowControlWindow: 8000, + maxReceiveConnectionFlowControlWindow: 9000, } rttStats = &congestion.RTTStats{} }) @@ -73,14 +80,14 @@ var _ = Describe("Flow controller", func() { fc := newFlowController(5, cpm, rttStats) Expect(fc.streamID).To(Equal(protocol.StreamID(5))) Expect(fc.receiveFlowControlWindow).To(Equal(protocol.ByteCount(2000))) - Expect(fc.maxReceiveFlowControlWindowIncrement).To(Equal(protocol.MaxReceiveStreamFlowControlWindow)) + Expect(fc.maxReceiveFlowControlWindowIncrement).To(Equal(cpm.GetMaxReceiveStreamFlowControlWindow())) }) It("reads the stream send and receive windows when acting as connection-level flow controller", func() { fc := newFlowController(0, cpm, rttStats) Expect(fc.streamID).To(Equal(protocol.StreamID(0))) Expect(fc.receiveFlowControlWindow).To(Equal(protocol.ByteCount(4000))) - Expect(fc.maxReceiveFlowControlWindowIncrement).To(Equal(protocol.MaxReceiveConnectionFlowControlWindow)) + Expect(fc.maxReceiveFlowControlWindowIncrement).To(Equal(cpm.GetMaxReceiveConnectionFlowControlWindow())) }) It("does not set the stream flow control windows for sending", func() { diff --git a/handshake/connection_parameters_manager.go b/handshake/connection_parameters_manager.go index 0b271dab..5b8c816a 100644 --- a/handshake/connection_parameters_manager.go +++ b/handshake/connection_parameters_manager.go @@ -12,15 +12,23 @@ import ( ) // ConnectionParametersManager negotiates and stores the connection parameters +// A ConnectionParametersManager can be used for a server as well as a client +// For the server: +// 1. call SetFromMap with the values received in the CHLO. This sets the corresponding values here, subject to negotiation +// 2. call GetHelloMap to get the values to send in the SHLO +// For the client: +// 1. call GetHelloMap to get the values to send in a CHLO +// 2. call SetFromMap with the values received in the SHLO type ConnectionParametersManager interface { SetFromMap(map[Tag][]byte) error - GetSHLOMap() (map[Tag][]byte, error) - GetCHLOMap() (map[Tag][]byte, error) + GetHelloMap() (map[Tag][]byte, error) GetSendStreamFlowControlWindow() protocol.ByteCount GetSendConnectionFlowControlWindow() protocol.ByteCount GetReceiveStreamFlowControlWindow() protocol.ByteCount + GetMaxReceiveStreamFlowControlWindow() protocol.ByteCount GetReceiveConnectionFlowControlWindow() protocol.ByteCount + GetMaxReceiveConnectionFlowControlWindow() protocol.ByteCount GetMaxOutgoingStreams() uint32 GetMaxIncomingStreams() uint32 GetIdleConnectionStateLifetime() time.Duration @@ -58,17 +66,26 @@ var ( // NewConnectionParamatersManager creates a new connection parameters manager func NewConnectionParamatersManager(pers protocol.Perspective, v protocol.VersionNumber) ConnectionParametersManager { - return &connectionParametersManager{ - perspective: pers, - version: v, - idleConnectionStateLifetime: protocol.DefaultIdleTimeout, - sendStreamFlowControlWindow: protocol.InitialStreamFlowControlWindow, // can only be changed by the client - sendConnectionFlowControlWindow: protocol.InitialConnectionFlowControlWindow, // can only be changed by the client - receiveStreamFlowControlWindow: protocol.ReceiveStreamFlowControlWindow, - receiveConnectionFlowControlWindow: protocol.ReceiveConnectionFlowControlWindow, - maxStreamsPerConnection: protocol.MaxStreamsPerConnection, // this is the value negotiated based on what the client sent - maxIncomingDynamicStreamsPerConnection: protocol.MaxStreamsPerConnection, // "incoming" seen from the client's perspective + h := &connectionParametersManager{ + perspective: pers, + version: v, + sendStreamFlowControlWindow: protocol.InitialStreamFlowControlWindow, // can only be changed by the client + sendConnectionFlowControlWindow: protocol.InitialConnectionFlowControlWindow, // can only be changed by the client + receiveStreamFlowControlWindow: protocol.ReceiveStreamFlowControlWindow, + receiveConnectionFlowControlWindow: protocol.ReceiveConnectionFlowControlWindow, } + + if h.perspective == protocol.PerspectiveServer { + h.idleConnectionStateLifetime = protocol.DefaultIdleTimeout + h.maxStreamsPerConnection = protocol.MaxStreamsPerConnection // this is the value negotiated based on what the client sent + h.maxIncomingDynamicStreamsPerConnection = protocol.MaxStreamsPerConnection // "incoming" seen from the client's perspective + } else { + h.idleConnectionStateLifetime = protocol.MaxIdleTimeoutClient + h.maxStreamsPerConnection = protocol.MaxStreamsPerConnection // this is the value negotiated based on what the client sent + h.maxIncomingDynamicStreamsPerConnection = protocol.MaxStreamsPerConnection // "incoming" seen from the server's perspective + } + + return h } // SetFromMap reads all params @@ -76,7 +93,7 @@ func (h *connectionParametersManager) SetFromMap(params map[Tag][]byte) error { h.mutex.Lock() defer h.mutex.Unlock() - if value, ok := params[TagTCID]; ok { + if value, ok := params[TagTCID]; ok && h.perspective == protocol.PerspectiveServer { clientValue, err := utils.ReadUint32(bytes.NewBuffer(value)) if err != nil { return ErrMalformedTag @@ -144,16 +161,14 @@ func (h *connectionParametersManager) negotiateMaxIncomingDynamicStreamsPerConne } func (h *connectionParametersManager) negotiateIdleConnectionStateLifetime(clientValue time.Duration) time.Duration { - return utils.MinDuration(clientValue, protocol.MaxIdleTimeout) + if h.perspective == protocol.PerspectiveServer { + return utils.MinDuration(clientValue, protocol.MaxIdleTimeoutServer) + } + return utils.MinDuration(clientValue, protocol.MaxIdleTimeoutClient) } -// GetSHLOMap gets all parameters needed for the SHLO -// if the client sent us parameters earlier, these are the negotiated values -func (h *connectionParametersManager) GetSHLOMap() (map[Tag][]byte, error) { - if h.perspective != protocol.PerspectiveServer { - return nil, errors.New("ConnectionParametersManager BUG: GetSHLOMap should only be called for a server") - } - +// GetHelloMap gets all parameters needed for the Hello message +func (h *connectionParametersManager) GetHelloMap() (map[Tag][]byte, error) { sfcw := bytes.NewBuffer([]byte{}) utils.WriteUint32(sfcw, uint32(h.GetReceiveStreamFlowControlWindow())) cfcw := bytes.NewBuffer([]byte{}) @@ -179,38 +194,6 @@ func (h *connectionParametersManager) GetSHLOMap() (map[Tag][]byte, error) { return tags, nil } -// GetCHLOMap gets all parameters needed for the CHLO -// these are the values the client is suggesting to the server. The negotiation is done by the server -func (h *connectionParametersManager) GetCHLOMap() (map[Tag][]byte, error) { - if h.perspective != protocol.PerspectiveClient { - return nil, errors.New("ConnectionParametersManager BUG: GetCHLOMap should only be called for a client") - } - - sfcw := bytes.NewBuffer([]byte{}) - utils.WriteUint32(sfcw, uint32(protocol.InitialStreamFlowControlWindow)) - cfcw := bytes.NewBuffer([]byte{}) - utils.WriteUint32(cfcw, uint32(protocol.InitialConnectionFlowControlWindow)) - mspc := bytes.NewBuffer([]byte{}) - utils.WriteUint32(mspc, protocol.MaxStreamsPerConnection) - icsl := bytes.NewBuffer([]byte{}) - utils.WriteUint32(icsl, uint32(protocol.DefaultIdleTimeout/time.Second)) - - tags := map[Tag][]byte{ - TagICSL: icsl.Bytes(), - TagMSPC: mspc.Bytes(), - TagCFCW: cfcw.Bytes(), - TagSFCW: sfcw.Bytes(), - } - - if h.version > protocol.Version34 { - mids := bytes.NewBuffer([]byte{}) - utils.WriteUint32(mids, protocol.MaxIncomingDynamicStreamsPerConnection) - tags[TagMIDS] = mids.Bytes() - } - - return tags, nil -} - // GetSendStreamFlowControlWindow gets the size of the stream-level flow control window for sending data func (h *connectionParametersManager) GetSendStreamFlowControlWindow() protocol.ByteCount { h.mutex.RLock() @@ -232,6 +215,14 @@ func (h *connectionParametersManager) GetReceiveStreamFlowControlWindow() protoc return h.receiveStreamFlowControlWindow } +// GetMaxReceiveStreamFlowControlWindow gets the maximum size of the stream-level flow control window for sending data +func (h *connectionParametersManager) GetMaxReceiveStreamFlowControlWindow() protocol.ByteCount { + if h.perspective == protocol.PerspectiveServer { + return protocol.MaxReceiveStreamFlowControlWindowServer + } + return protocol.MaxReceiveStreamFlowControlWindowClient +} + // GetReceiveConnectionFlowControlWindow gets the size of the stream-level flow control window for receiving data func (h *connectionParametersManager) GetReceiveConnectionFlowControlWindow() protocol.ByteCount { h.mutex.RLock() @@ -239,6 +230,14 @@ func (h *connectionParametersManager) GetReceiveConnectionFlowControlWindow() pr return h.receiveConnectionFlowControlWindow } +// GetMaxReceiveConnectionFlowControlWindow gets the maximum size of the stream-level flow control window for sending data +func (h *connectionParametersManager) GetMaxReceiveConnectionFlowControlWindow() protocol.ByteCount { + if h.perspective == protocol.PerspectiveServer { + return protocol.MaxReceiveConnectionFlowControlWindowServer + } + return protocol.MaxReceiveConnectionFlowControlWindowClient +} + // GetMaxOutgoingStreams gets the maximum number of outgoing streams per connection func (h *connectionParametersManager) GetMaxOutgoingStreams() uint32 { h.mutex.RLock() @@ -274,6 +273,10 @@ func (h *connectionParametersManager) GetIdleConnectionStateLifetime() time.Dura // TruncateConnectionID determines if the client requests truncated ConnectionIDs func (h *connectionParametersManager) TruncateConnectionID() bool { + if h.perspective == protocol.PerspectiveClient { + return false + } + h.mutex.RLock() defer h.mutex.RUnlock() return h.truncateConnectionID diff --git a/handshake/connection_parameters_manager_test.go b/handshake/connection_parameters_manager_test.go index a4478ba4..cbe24daa 100644 --- a/handshake/connection_parameters_manager_test.go +++ b/handshake/connection_parameters_manager_test.go @@ -10,14 +10,22 @@ import ( ) var _ = Describe("ConnectionsParameterManager", func() { - var cpm *connectionParametersManager + var cpm *connectionParametersManager // a connectionParametersManager for a server + var cpmClient *connectionParametersManager + BeforeEach(func() { cpm = NewConnectionParamatersManager(protocol.PerspectiveServer, protocol.Version36).(*connectionParametersManager) + cpmClient = NewConnectionParamatersManager(protocol.PerspectiveClient, protocol.Version36).(*connectionParametersManager) }) Context("SHLO", func() { + BeforeEach(func() { + // these tests should only use the server connectionParametersManager. Make them panic if they don't + cpmClient = nil + }) + It("returns all parameters necessary for the SHLO", func() { - entryMap, err := cpm.GetSHLOMap() + entryMap, err := cpm.GetHelloMap() Expect(err).ToNot(HaveOccurred()) Expect(entryMap).To(HaveKey(TagICSL)) Expect(entryMap).To(HaveKey(TagMSPC)) @@ -26,14 +34,14 @@ var _ = Describe("ConnectionsParameterManager", func() { It("doesn't add the MaximumIncomingDynamicStreams tag for QUIC 34", func() { cpm.version = protocol.Version34 - entryMap, err := cpm.GetSHLOMap() + entryMap, err := cpm.GetHelloMap() Expect(err).ToNot(HaveOccurred()) Expect(entryMap).ToNot(HaveKey(TagMIDS)) }) It("sets the stream-level flow control windows in SHLO", func() { cpm.receiveStreamFlowControlWindow = 0xDEADBEEF - entryMap, err := cpm.GetSHLOMap() + entryMap, err := cpm.GetHelloMap() Expect(err).ToNot(HaveOccurred()) Expect(entryMap).To(HaveKey(TagSFCW)) Expect(entryMap[TagSFCW]).To(Equal([]byte{0xEF, 0xBE, 0xAD, 0xDE})) @@ -41,7 +49,7 @@ var _ = Describe("ConnectionsParameterManager", func() { It("sets the connection-level flow control windows in SHLO", func() { cpm.receiveConnectionFlowControlWindow = 0xDECAFBAD - entryMap, err := cpm.GetSHLOMap() + entryMap, err := cpm.GetHelloMap() Expect(err).ToNot(HaveOccurred()) Expect(entryMap).To(HaveKey(TagCFCW)) Expect(entryMap[TagCFCW]).To(Equal([]byte{0xAD, 0xFB, 0xCA, 0xDE})) @@ -49,7 +57,7 @@ var _ = Describe("ConnectionsParameterManager", func() { It("sets the connection-level flow control windows in SHLO", func() { cpm.idleConnectionStateLifetime = 0xDECAFBAD * time.Second - entryMap, err := cpm.GetSHLOMap() + entryMap, err := cpm.GetHelloMap() Expect(err).ToNot(HaveOccurred()) Expect(entryMap).To(HaveKey(TagICSL)) Expect(entryMap[TagICSL]).To(Equal([]byte{0xAD, 0xFB, 0xCA, 0xDE})) @@ -60,7 +68,7 @@ var _ = Describe("ConnectionsParameterManager", func() { Expect(val).To(BeNumerically("<", protocol.MaxStreamsPerConnection)) err := cpm.SetFromMap(map[Tag][]byte{TagMSPC: []byte{byte(val), 0, 0, 0}}) Expect(err).ToNot(HaveOccurred()) - entryMap, err := cpm.GetSHLOMap() + entryMap, err := cpm.GetHelloMap() Expect(err).ToNot(HaveOccurred()) Expect(entryMap[TagMSPC]).To(Equal([]byte{byte(val), 0, 0, 0})) }) @@ -68,50 +76,39 @@ var _ = Describe("ConnectionsParameterManager", func() { It("always sends its own value for the maximum incoming dynamic streams in the SHLO", func() { err := cpm.SetFromMap(map[Tag][]byte{TagMIDS: []byte{5, 0, 0, 0}}) Expect(err).ToNot(HaveOccurred()) - entryMap, err := cpm.GetSHLOMap() + entryMap, err := cpm.GetHelloMap() Expect(err).ToNot(HaveOccurred()) Expect(entryMap[TagMIDS]).To(Equal([]byte{byte(protocol.MaxIncomingDynamicStreamsPerConnection), 0, 0, 0})) }) - - It("errors if called from a client", func() { - cpm.perspective = protocol.PerspectiveClient - _, err := cpm.GetSHLOMap() - Expect(err).To(HaveOccurred()) - }) }) Context("CHLO", func() { BeforeEach(func() { - cpm.perspective = protocol.PerspectiveClient + // these tests should only use the client connectionParametersManager. Make them panic if they don't + cpm = nil }) It("has the right values", func() { - entryMap, err := cpm.GetCHLOMap() + entryMap, err := cpmClient.GetHelloMap() Expect(err).ToNot(HaveOccurred()) Expect(entryMap).To(HaveKey(TagICSL)) - Expect(binary.LittleEndian.Uint32(entryMap[TagICSL])).To(Equal(uint32(protocol.DefaultIdleTimeout / time.Second))) + Expect(binary.LittleEndian.Uint32(entryMap[TagICSL])).To(BeEquivalentTo(protocol.MaxIdleTimeoutClient / time.Second)) Expect(entryMap).To(HaveKey(TagMSPC)) - Expect(binary.LittleEndian.Uint32(entryMap[TagMSPC])).To(Equal(uint32(protocol.MaxStreamsPerConnection))) + Expect(binary.LittleEndian.Uint32(entryMap[TagMSPC])).To(BeEquivalentTo(protocol.MaxStreamsPerConnection)) Expect(entryMap).To(HaveKey(TagMIDS)) - Expect(binary.LittleEndian.Uint32(entryMap[TagMIDS])).To(Equal(uint32(protocol.MaxIncomingDynamicStreamsPerConnection))) + Expect(binary.LittleEndian.Uint32(entryMap[TagMIDS])).To(BeEquivalentTo(protocol.MaxIncomingDynamicStreamsPerConnection)) Expect(entryMap).To(HaveKey(TagSFCW)) - Expect(binary.LittleEndian.Uint32(entryMap[TagSFCW])).To(Equal(uint32(protocol.InitialStreamFlowControlWindow))) + Expect(binary.LittleEndian.Uint32(entryMap[TagSFCW])).To(BeEquivalentTo(protocol.ReceiveStreamFlowControlWindow)) Expect(entryMap).To(HaveKey(TagCFCW)) - Expect(binary.LittleEndian.Uint32(entryMap[TagCFCW])).To(Equal(uint32(protocol.InitialConnectionFlowControlWindow))) + Expect(binary.LittleEndian.Uint32(entryMap[TagCFCW])).To(BeEquivalentTo(protocol.ReceiveConnectionFlowControlWindow)) }) It("doesn't add the MIDS tag for QUIC 34", func() { - cpm.version = protocol.Version34 - entryMap, err := cpm.GetCHLOMap() + cpmClient.version = protocol.Version34 + entryMap, err := cpmClient.GetHelloMap() Expect(err).ToNot(HaveOccurred()) Expect(entryMap).ToNot(HaveKey(TagMIDS)) }) - - It("errors if called from a server", func() { - cpm.perspective = protocol.PerspectiveServer - _, err := cpm.GetCHLOMap() - Expect(err).To(HaveOccurred()) - }) }) Context("Truncated connection IDs", func() { @@ -120,62 +117,69 @@ var _ = Describe("ConnectionsParameterManager", func() { }) It("reads the tag for truncated connection IDs", func() { - values := map[Tag][]byte{ - TagTCID: {0, 0, 0, 0}, - } + values := map[Tag][]byte{TagTCID: {0, 0, 0, 0}} cpm.SetFromMap(values) Expect(cpm.TruncateConnectionID()).To(BeTrue()) }) + + It("ignores the TCID tag, as a client", func() { + values := map[Tag][]byte{TagTCID: {0, 0, 0, 0}} + cpmClient.SetFromMap(values) + Expect(cpmClient.TruncateConnectionID()).To(BeFalse()) + }) + + It("errors when given an invalid value", func() { + values := map[Tag][]byte{TagTCID: {2, 0, 0}} // 1 byte too short + err := cpm.SetFromMap(values) + Expect(err).To(MatchError(ErrMalformedTag)) + }) }) Context("flow control", func() { - It("has the correct default stream-level flow control window for sending", func() { + It("has the correct default flow control windows for sending", func() { Expect(cpm.GetSendStreamFlowControlWindow()).To(Equal(protocol.InitialStreamFlowControlWindow)) - }) - - It("has the correct default connection-level flow control window for sending", func() { Expect(cpm.GetSendConnectionFlowControlWindow()).To(Equal(protocol.InitialConnectionFlowControlWindow)) + Expect(cpmClient.GetSendStreamFlowControlWindow()).To(Equal(protocol.InitialStreamFlowControlWindow)) + Expect(cpmClient.GetSendConnectionFlowControlWindow()).To(Equal(protocol.InitialConnectionFlowControlWindow)) }) - It("has the correct default stream-level flow control window for receiving", func() { + It("has the correct default flow control windows for receiving", func() { Expect(cpm.GetReceiveStreamFlowControlWindow()).To(Equal(protocol.ReceiveStreamFlowControlWindow)) + Expect(cpm.GetReceiveConnectionFlowControlWindow()).To(Equal(protocol.ReceiveConnectionFlowControlWindow)) + Expect(cpmClient.GetReceiveStreamFlowControlWindow()).To(Equal(protocol.ReceiveStreamFlowControlWindow)) + Expect(cpmClient.GetReceiveConnectionFlowControlWindow()).To(Equal(protocol.ReceiveConnectionFlowControlWindow)) }) - It("has the correct default connection-level flow control window for receiving", func() { - Expect(cpm.GetReceiveConnectionFlowControlWindow()).To(Equal(protocol.ReceiveConnectionFlowControlWindow)) + It("has the correct maximum flow control windows", func() { + Expect(cpm.GetMaxReceiveStreamFlowControlWindow()).To(Equal(protocol.MaxReceiveStreamFlowControlWindowServer)) + Expect(cpm.GetMaxReceiveConnectionFlowControlWindow()).To(Equal(protocol.MaxReceiveConnectionFlowControlWindowServer)) + Expect(cpmClient.GetMaxReceiveStreamFlowControlWindow()).To(Equal(protocol.MaxReceiveStreamFlowControlWindowClient)) + Expect(cpmClient.GetMaxReceiveConnectionFlowControlWindow()).To(Equal(protocol.MaxReceiveConnectionFlowControlWindowClient)) }) It("sets a new stream-level flow control window for sending", func() { - values := map[Tag][]byte{ - TagSFCW: {0xDE, 0xAD, 0xBE, 0xEF}, - } + values := map[Tag][]byte{TagSFCW: {0xDE, 0xAD, 0xBE, 0xEF}} err := cpm.SetFromMap(values) Expect(err).ToNot(HaveOccurred()) Expect(cpm.GetSendStreamFlowControlWindow()).To(Equal(protocol.ByteCount(0xEFBEADDE))) }) It("does not change the stream-level flow control window when given an invalid value", func() { - values := map[Tag][]byte{ - TagSFCW: {0xDE, 0xAD, 0xBE}, // 1 byte too short - } + values := map[Tag][]byte{TagSFCW: {0xDE, 0xAD, 0xBE}} // 1 byte too short err := cpm.SetFromMap(values) Expect(err).To(MatchError(ErrMalformedTag)) Expect(cpm.GetSendStreamFlowControlWindow()).To(Equal(protocol.InitialStreamFlowControlWindow)) }) It("sets a new connection-level flow control window for sending", func() { - values := map[Tag][]byte{ - TagCFCW: {0xDE, 0xAD, 0xBE, 0xEF}, - } + values := map[Tag][]byte{TagCFCW: {0xDE, 0xAD, 0xBE, 0xEF}} err := cpm.SetFromMap(values) Expect(err).ToNot(HaveOccurred()) Expect(cpm.GetSendConnectionFlowControlWindow()).To(Equal(protocol.ByteCount(0xEFBEADDE))) }) It("does not change the connection-level flow control window when given an invalid value", func() { - values := map[Tag][]byte{ - TagSFCW: {0xDE, 0xAD, 0xBE}, // 1 byte too short - } + values := map[Tag][]byte{TagCFCW: {0xDE, 0xAD, 0xBE}} // 1 byte too short err := cpm.SetFromMap(values) Expect(err).To(MatchError(ErrMalformedTag)) Expect(cpm.GetSendStreamFlowControlWindow()).To(Equal(protocol.InitialConnectionFlowControlWindow)) @@ -204,12 +208,14 @@ var _ = Describe("ConnectionsParameterManager", func() { Expect(cpm.GetIdleConnectionStateLifetime()).To(Equal(protocol.DefaultIdleTimeout)) }) - It("negotiates correctly when the client wants a longer lifetime", func() { - Expect(cpm.negotiateIdleConnectionStateLifetime(protocol.MaxIdleTimeout + 10*time.Second)).To(Equal(protocol.MaxIdleTimeout)) + It("negotiates correctly when the peer wants a longer lifetime", func() { + Expect(cpm.negotiateIdleConnectionStateLifetime(protocol.MaxIdleTimeoutServer + 10*time.Second)).To(Equal(protocol.MaxIdleTimeoutServer)) + Expect(cpmClient.negotiateIdleConnectionStateLifetime(protocol.MaxIdleTimeoutClient + 10*time.Second)).To(Equal(protocol.MaxIdleTimeoutClient)) }) - It("negotiates correctly when the client wants a shorter lifetime", func() { - Expect(cpm.negotiateIdleConnectionStateLifetime(protocol.MaxIdleTimeout - 1*time.Second)).To(Equal(protocol.MaxIdleTimeout - 1*time.Second)) + It("negotiates correctly when the peer wants a shorter lifetime", func() { + Expect(cpm.negotiateIdleConnectionStateLifetime(protocol.MaxIdleTimeoutServer - 1*time.Second)).To(Equal(protocol.MaxIdleTimeoutServer - 1*time.Second)) + Expect(cpmClient.negotiateIdleConnectionStateLifetime(protocol.MaxIdleTimeoutClient - 1*time.Second)).To(Equal(protocol.MaxIdleTimeoutClient - 1*time.Second)) }) It("sets the negotiated lifetime", func() { @@ -236,21 +242,23 @@ var _ = Describe("ConnectionsParameterManager", func() { cpm.idleConnectionStateLifetime = value Expect(cpm.GetIdleConnectionStateLifetime()).To(Equal(value)) }) + + It("errors when given an invalid value", func() { + values := map[Tag][]byte{TagICSL: {2, 0, 0}} // 1 byte too short + err := cpm.SetFromMap(values) + Expect(err).To(MatchError(ErrMalformedTag)) + }) }) Context("max streams per connection", func() { It("errors when given an invalid max streams per connection value", func() { - values := map[Tag][]byte{ - TagMSPC: {2, 0, 0}, // 1 byte too short - } + values := map[Tag][]byte{TagMSPC: {2, 0, 0}} // 1 byte too short err := cpm.SetFromMap(values) Expect(err).To(MatchError(ErrMalformedTag)) }) It("errors when given an invalid max dynamic incoming streams per connection value", func() { - values := map[Tag][]byte{ - TagMIDS: {2, 0, 0}, // 1 byte too short - } + values := map[Tag][]byte{TagMIDS: {2, 0, 0}} // 1 byte too short err := cpm.SetFromMap(values) Expect(err).To(MatchError(ErrMalformedTag)) }) @@ -300,6 +308,5 @@ var _ = Describe("ConnectionsParameterManager", func() { Expect(cpm.GetMaxIncomingStreams()).To(BeNumerically("~", 60*protocol.MaxStreamsMultiplier, 10)) }) }) - }) }) diff --git a/handshake/crypto_setup_client.go b/handshake/crypto_setup_client.go index f6915112..a297e06a 100644 --- a/handshake/crypto_setup_client.go +++ b/handshake/crypto_setup_client.go @@ -318,7 +318,7 @@ func (h *cryptoSetupClient) sendCHLO() error { } func (h *cryptoSetupClient) getTags() (map[Tag][]byte, error) { - tags, err := h.connectionParameters.GetCHLOMap() + tags, err := h.connectionParameters.GetHelloMap() if err != nil { return nil, err } diff --git a/handshake/crypto_setup_client_test.go b/handshake/crypto_setup_client_test.go index 99045eff..786cdfb3 100644 --- a/handshake/crypto_setup_client_test.go +++ b/handshake/crypto_setup_client_test.go @@ -428,7 +428,7 @@ var _ = Describe("Crypto setup", func() { }) It("adds the tags returned from the connectionParametersManager to the CHLO", func() { - cpmTags, err := cs.connectionParameters.GetCHLOMap() + cpmTags, err := cs.connectionParameters.GetHelloMap() Expect(err).ToNot(HaveOccurred()) Expect(cpmTags).ToNot(BeEmpty()) tags, err := cs.getTags() diff --git a/handshake/crypto_setup_server.go b/handshake/crypto_setup_server.go index a1d3ee26..a30b8ad4 100644 --- a/handshake/crypto_setup_server.go +++ b/handshake/crypto_setup_server.go @@ -340,7 +340,7 @@ func (h *cryptoSetupServer) handleCHLO(sni string, data []byte, cryptoData map[T return nil, err } - replyMap, err := h.connectionParameters.GetSHLOMap() + replyMap, err := h.connectionParameters.GetHelloMap() if err != nil { return nil, err } diff --git a/protocol/server_parameters.go b/protocol/server_parameters.go index 2fc87619..11981823 100644 --- a/protocol/server_parameters.go +++ b/protocol/server_parameters.go @@ -24,13 +24,21 @@ const ReceiveStreamFlowControlWindow ByteCount = (1 << 10) * 32 // 32 kB // This is the value that Google servers are using const ReceiveConnectionFlowControlWindow ByteCount = (1 << 10) * 48 // 48 kB -// MaxReceiveStreamFlowControlWindow is the maximum stream-level flow control window for receiving data +// MaxReceiveStreamFlowControlWindowServer is the maximum stream-level flow control window for receiving data // This is the value that Google servers are using -const MaxReceiveStreamFlowControlWindow ByteCount = 1 * (1 << 20) // 1 MB +const MaxReceiveStreamFlowControlWindowServer ByteCount = 1 * (1 << 20) // 1 MB -// MaxReceiveConnectionFlowControlWindow is the connection-level flow control window for receiving data +// MaxReceiveConnectionFlowControlWindowServer is the connection-level flow control window for receiving data // This is the value that Google servers are using -const MaxReceiveConnectionFlowControlWindow ByteCount = 1.5 * (1 << 20) // 1.5 MB +const MaxReceiveConnectionFlowControlWindowServer ByteCount = 1.5 * (1 << 20) // 1.5 MB + +// MaxReceiveStreamFlowControlWindowClient is the maximum stream-level flow control window for receiving data, for the client +// This is the value that Chromium is using +const MaxReceiveStreamFlowControlWindowClient ByteCount = 6 * (1 << 20) // 6 MB + +// MaxReceiveConnectionFlowControlWindowClient is the connection-level flow control window for receiving data, for the server +// This is the value that Google servers are using +const MaxReceiveConnectionFlowControlWindowClient ByteCount = 15 * (1 << 20) // 15 MB // MaxStreamsPerConnection is the maximum value accepted for the number of streams per connection const MaxStreamsPerConnection = 100 @@ -95,11 +103,14 @@ const EphermalKeyLifetime = time.Minute // InitialIdleTimeout is the timeout before the handshake succeeds. const InitialIdleTimeout = 5 * time.Second -// DefaultIdleTimeout is the default idle timeout. +// DefaultIdleTimeout is the default idle timeout, for the server const DefaultIdleTimeout = 30 * time.Second -// MaxIdleTimeout is the maximum idle timeout that can be negotiated. -const MaxIdleTimeout = 1 * time.Minute +// MaxIdleTimeoutServer is the maximum idle timeout that can be negotiated, for the server +const MaxIdleTimeoutServer = 1 * time.Minute + +// MaxIdleTimeoutClient is the idle timeout that the client suggests to the server +const MaxIdleTimeoutClient = 2 * time.Minute // MaxTimeForCryptoHandshake is the default timeout for a connection until the crypto handshake succeeds. const MaxTimeForCryptoHandshake = 10 * time.Second diff --git a/streams_map_test.go b/streams_map_test.go index 234f2b54..1a6a6a22 100644 --- a/streams_map_test.go +++ b/streams_map_test.go @@ -21,10 +21,7 @@ type mockConnectionParametersManager struct { func (m *mockConnectionParametersManager) SetFromMap(map[handshake.Tag][]byte) error { panic("not implemented") } -func (m *mockConnectionParametersManager) GetSHLOMap() (map[handshake.Tag][]byte, error) { - panic("not implemented") -} -func (m *mockConnectionParametersManager) GetCHLOMap() (map[handshake.Tag][]byte, error) { +func (m *mockConnectionParametersManager) GetHelloMap() (map[handshake.Tag][]byte, error) { panic("not implemented") } func (m *mockConnectionParametersManager) GetSendStreamFlowControlWindow() protocol.ByteCount { @@ -36,9 +33,15 @@ func (m *mockConnectionParametersManager) GetSendConnectionFlowControlWindow() p func (m *mockConnectionParametersManager) GetReceiveStreamFlowControlWindow() protocol.ByteCount { return math.MaxUint64 } +func (m *mockConnectionParametersManager) GetMaxReceiveStreamFlowControlWindow() protocol.ByteCount { + return math.MaxUint64 +} func (m *mockConnectionParametersManager) GetReceiveConnectionFlowControlWindow() protocol.ByteCount { return math.MaxUint64 } +func (m *mockConnectionParametersManager) GetMaxReceiveConnectionFlowControlWindow() protocol.ByteCount { + return math.MaxUint64 +} func (m *mockConnectionParametersManager) GetMaxOutgoingStreams() uint32 { return m.maxOutgoingStreams } func (m *mockConnectionParametersManager) GetMaxIncomingStreams() uint32 { return m.maxIncomingStreams } func (m *mockConnectionParametersManager) GetIdleConnectionStateLifetime() time.Duration {