diff --git a/README.md b/README.md index e6beb8aa5b0..30265c3ab3f 100644 --- a/README.md +++ b/README.md @@ -159,18 +159,18 @@ On the receiver side, this is surfaced as a `quic.ApplicationError`. Unreliable datagrams are a QUIC extension ([RFC 9221](https://datatracker.ietf.org/doc/html/rfc9221)) that is negotiated during the handshake. Support can be enabled by setting the `quic.Config.EnableDatagram` flag. Note that this doesn't guarantee that the peer also supports datagrams. Whether or not the feature negotiation succeeded can be learned from the `quic.ConnectionState.SupportsDatagrams` obtained from `quic.Connection.ConnectionState()`. -QUIC DATAGRAMs are a new QUIC frame type sent in QUIC 1-RTT packets (i.e. after completion of the handshake). Therefore, they're end-to-end encrypted and congestion-controlled. However, if a DATAGRAM frame is deemed lost by QUIC's loss detection mechanism, they are not automatically retransmitted. +QUIC DATAGRAMs are a new QUIC frame type sent in QUIC 1-RTT packets (i.e. after completion of the handshake). Therefore, they're end-to-end encrypted and congestion-controlled. However, if a DATAGRAM frame is deemed lost by QUIC's loss detection mechanism, they are not retransmitted. -Datagrams are sent using the `SendMessage` method on the `quic.Connection`: +Datagrams are sent using the `SendDatagram` method on the `quic.Connection`: ```go -conn.SendMessage([]byte("foobar")) +conn.SendDatagram([]byte("foobar")) ``` -And received using `ReceiveMessage`: +And received using `ReceiveDatagram`: ```go -msg, err := conn.ReceiveMessage() +msg, err := conn.ReceiveDatagram() ``` Note that this code path is currently not optimized. It works for datagrams that are sent occasionally, but it doesn't achieve the same throughput as writing data on a stream. Please get in touch on issue #3766 if your use case relies on high datagram throughput, or if you'd like to help fix this issue. There are also some restrictions regarding the maximum message size (see #3599). diff --git a/connection.go b/connection.go index 09b522a9eb7..93c159137bf 100644 --- a/connection.go +++ b/connection.go @@ -2343,7 +2343,7 @@ func (s *connection) onStreamCompleted(id protocol.StreamID) { } } -func (s *connection) SendMessage(p []byte) error { +func (s *connection) SendDatagram(p []byte) error { if !s.supportsDatagrams() { return errors.New("datagram support disabled") } @@ -2357,7 +2357,7 @@ func (s *connection) SendMessage(p []byte) error { return s.datagramQueue.AddAndWait(f) } -func (s *connection) ReceiveMessage(ctx context.Context) ([]byte, error) { +func (s *connection) ReceiveDatagram(ctx context.Context) ([]byte, error) { if !s.config.EnableDatagrams { return nil, errors.New("datagram support disabled") } diff --git a/integrationtests/self/datagram_test.go b/integrationtests/self/datagram_test.go index 35d0718a978..5f7f09f3415 100644 --- a/integrationtests/self/datagram_test.go +++ b/integrationtests/self/datagram_test.go @@ -57,7 +57,7 @@ var _ = Describe("Datagram test", func() { defer wg.Done() b := make([]byte, 8) binary.BigEndian.PutUint64(b, uint64(i)) - Expect(conn.SendMessage(b)).To(Succeed()) + Expect(conn.SendDatagram(b)).To(Succeed()) }(i) } wg.Wait() @@ -120,7 +120,7 @@ var _ = Describe("Datagram test", func() { for { // Close the connection if no message is received for 100 ms. timer := time.AfterFunc(scaleDuration(100*time.Millisecond), func() { conn.CloseWithError(0, "") }) - if _, err := conn.ReceiveMessage(context.Background()); err != nil { + if _, err := conn.ReceiveDatagram(context.Background()); err != nil { break } timer.Stop() @@ -170,7 +170,7 @@ var _ = Describe("Datagram test", func() { Expect(err).ToNot(HaveOccurred()) Expect(conn.ConnectionState().SupportsDatagrams).To(BeFalse()) - Expect(conn.SendMessage([]byte{0})).To(HaveOccurred()) + Expect(conn.SendDatagram([]byte{0})).To(HaveOccurred()) close() conn.CloseWithError(0, "") diff --git a/integrationtests/self/zero_rtt_oldgo_test.go b/integrationtests/self/zero_rtt_oldgo_test.go index eb2302d3d85..f42194bcc39 100644 --- a/integrationtests/self/zero_rtt_oldgo_test.go +++ b/integrationtests/self/zero_rtt_oldgo_test.go @@ -830,7 +830,7 @@ var _ = Describe("0-RTT", func() { defer close(received) conn, err := ln.Accept(context.Background()) Expect(err).ToNot(HaveOccurred()) - receivedMessage, err = conn.ReceiveMessage(context.Background()) + receivedMessage, err = conn.ReceiveDatagram(context.Background()) Expect(err).ToNot(HaveOccurred()) Expect(conn.ConnectionState().Used0RTT).To(BeTrue()) }() @@ -844,7 +844,7 @@ var _ = Describe("0-RTT", func() { ) Expect(err).ToNot(HaveOccurred()) Expect(conn.ConnectionState().SupportsDatagrams).To(BeTrue()) - Expect(conn.SendMessage(sentMessage)).To(Succeed()) + Expect(conn.SendDatagram(sentMessage)).To(Succeed()) <-conn.HandshakeComplete() <-received @@ -884,7 +884,7 @@ var _ = Describe("0-RTT", func() { defer GinkgoRecover() conn, err := ln.Accept(context.Background()) Expect(err).ToNot(HaveOccurred()) - _, err = conn.ReceiveMessage(context.Background()) + _, err = conn.ReceiveDatagram(context.Background()) Expect(err.Error()).To(Equal("datagram support disabled")) <-conn.HandshakeComplete() Expect(conn.ConnectionState().Used0RTT).To(BeFalse()) @@ -900,7 +900,7 @@ var _ = Describe("0-RTT", func() { Expect(err).ToNot(HaveOccurred()) // the client can temporarily send datagrams but the server doesn't process them. Expect(conn.ConnectionState().SupportsDatagrams).To(BeTrue()) - Expect(conn.SendMessage(make([]byte, 100))).To(Succeed()) + Expect(conn.SendDatagram(make([]byte, 100))).To(Succeed()) <-conn.HandshakeComplete() Expect(conn.ConnectionState().SupportsDatagrams).To(BeFalse()) diff --git a/integrationtests/self/zero_rtt_test.go b/integrationtests/self/zero_rtt_test.go index 1f750d3ac25..3e9a0f601b6 100644 --- a/integrationtests/self/zero_rtt_test.go +++ b/integrationtests/self/zero_rtt_test.go @@ -960,7 +960,7 @@ var _ = Describe("0-RTT", func() { defer close(received) conn, err := ln.Accept(context.Background()) Expect(err).ToNot(HaveOccurred()) - receivedMessage, err = conn.ReceiveMessage(context.Background()) + receivedMessage, err = conn.ReceiveDatagram(context.Background()) Expect(err).ToNot(HaveOccurred()) Expect(conn.ConnectionState().Used0RTT).To(BeTrue()) }() @@ -974,7 +974,7 @@ var _ = Describe("0-RTT", func() { ) Expect(err).ToNot(HaveOccurred()) Expect(conn.ConnectionState().SupportsDatagrams).To(BeTrue()) - Expect(conn.SendMessage(sentMessage)).To(Succeed()) + Expect(conn.SendDatagram(sentMessage)).To(Succeed()) <-conn.HandshakeComplete() <-received @@ -1016,7 +1016,7 @@ var _ = Describe("0-RTT", func() { defer GinkgoRecover() conn, err := ln.Accept(context.Background()) Expect(err).ToNot(HaveOccurred()) - _, err = conn.ReceiveMessage(context.Background()) + _, err = conn.ReceiveDatagram(context.Background()) Expect(err.Error()).To(Equal("datagram support disabled")) <-conn.HandshakeComplete() Expect(conn.ConnectionState().Used0RTT).To(BeFalse()) @@ -1032,7 +1032,7 @@ var _ = Describe("0-RTT", func() { Expect(err).ToNot(HaveOccurred()) // the client can temporarily send datagrams but the server doesn't process them. Expect(conn.ConnectionState().SupportsDatagrams).To(BeTrue()) - Expect(conn.SendMessage(make([]byte, 100))).To(Succeed()) + Expect(conn.SendDatagram(make([]byte, 100))).To(Succeed()) <-conn.HandshakeComplete() Expect(conn.ConnectionState().SupportsDatagrams).To(BeFalse()) diff --git a/interface.go b/interface.go index 6eac385dfce..da0e5e2b31b 100644 --- a/interface.go +++ b/interface.go @@ -187,10 +187,10 @@ type Connection interface { // Warning: This API should not be considered stable and might change soon. ConnectionState() ConnectionState - // SendMessage sends a message as a datagram, as specified in RFC 9221. - SendMessage([]byte) error - // ReceiveMessage gets a message received in a datagram, as specified in RFC 9221. - ReceiveMessage(context.Context) ([]byte, error) + // SendDatagram sends a message as a datagram, as specified in RFC 9221. + SendDatagram([]byte) error + // ReceiveDatagram gets a message received in a datagram, as specified in RFC 9221. + ReceiveDatagram(context.Context) ([]byte, error) } // An EarlyConnection is a connection that is handshaking. @@ -338,7 +338,7 @@ type ConnectionState struct { // SupportsDatagrams says if support for QUIC datagrams (RFC 9221) was negotiated. // This requires both nodes to support and enable the datagram extensions (via Config.EnableDatagrams). // If datagram support was negotiated, datagrams can be sent and received using the - // SendMessage and ReceiveMessage methods on the Connection. + // SendDatagram and ReceiveDatagram methods on the Connection. SupportsDatagrams bool // Used0RTT says if 0-RTT resumption was used. Used0RTT bool diff --git a/internal/mocks/quic/early_conn.go b/internal/mocks/quic/early_conn.go index 223def6c5e9..2e726a823d2 100644 --- a/internal/mocks/quic/early_conn.go +++ b/internal/mocks/quic/early_conn.go @@ -503,41 +503,41 @@ func (c *EarlyConnectionOpenUniStreamSyncCall) DoAndReturn(f func(context.Contex return c } -// ReceiveMessage mocks base method. -func (m *MockEarlyConnection) ReceiveMessage(arg0 context.Context) ([]byte, error) { +// ReceiveDatagram mocks base method. +func (m *MockEarlyConnection) ReceiveDatagram(arg0 context.Context) ([]byte, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ReceiveMessage", arg0) + ret := m.ctrl.Call(m, "ReceiveDatagram", arg0) ret0, _ := ret[0].([]byte) ret1, _ := ret[1].(error) return ret0, ret1 } -// ReceiveMessage indicates an expected call of ReceiveMessage. -func (mr *MockEarlyConnectionMockRecorder) ReceiveMessage(arg0 any) *EarlyConnectionReceiveMessageCall { +// ReceiveDatagram indicates an expected call of ReceiveDatagram. +func (mr *MockEarlyConnectionMockRecorder) ReceiveDatagram(arg0 any) *EarlyConnectionReceiveDatagramCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReceiveMessage", reflect.TypeOf((*MockEarlyConnection)(nil).ReceiveMessage), arg0) - return &EarlyConnectionReceiveMessageCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReceiveDatagram", reflect.TypeOf((*MockEarlyConnection)(nil).ReceiveDatagram), arg0) + return &EarlyConnectionReceiveDatagramCall{Call: call} } -// EarlyConnectionReceiveMessageCall wrap *gomock.Call -type EarlyConnectionReceiveMessageCall struct { +// EarlyConnectionReceiveDatagramCall wrap *gomock.Call +type EarlyConnectionReceiveDatagramCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *EarlyConnectionReceiveMessageCall) Return(arg0 []byte, arg1 error) *EarlyConnectionReceiveMessageCall { +func (c *EarlyConnectionReceiveDatagramCall) Return(arg0 []byte, arg1 error) *EarlyConnectionReceiveDatagramCall { c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *EarlyConnectionReceiveMessageCall) Do(f func(context.Context) ([]byte, error)) *EarlyConnectionReceiveMessageCall { +func (c *EarlyConnectionReceiveDatagramCall) Do(f func(context.Context) ([]byte, error)) *EarlyConnectionReceiveDatagramCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *EarlyConnectionReceiveMessageCall) DoAndReturn(f func(context.Context) ([]byte, error)) *EarlyConnectionReceiveMessageCall { +func (c *EarlyConnectionReceiveDatagramCall) DoAndReturn(f func(context.Context) ([]byte, error)) *EarlyConnectionReceiveDatagramCall { c.Call = c.Call.DoAndReturn(f) return c } @@ -580,40 +580,40 @@ func (c *EarlyConnectionRemoteAddrCall) DoAndReturn(f func() net.Addr) *EarlyCon return c } -// SendMessage mocks base method. -func (m *MockEarlyConnection) SendMessage(arg0 []byte) error { +// SendDatagram mocks base method. +func (m *MockEarlyConnection) SendDatagram(arg0 []byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SendMessage", arg0) + ret := m.ctrl.Call(m, "SendDatagram", arg0) ret0, _ := ret[0].(error) return ret0 } -// SendMessage indicates an expected call of SendMessage. -func (mr *MockEarlyConnectionMockRecorder) SendMessage(arg0 any) *EarlyConnectionSendMessageCall { +// SendDatagram indicates an expected call of SendDatagram. +func (mr *MockEarlyConnectionMockRecorder) SendDatagram(arg0 any) *EarlyConnectionSendDatagramCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockEarlyConnection)(nil).SendMessage), arg0) - return &EarlyConnectionSendMessageCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendDatagram", reflect.TypeOf((*MockEarlyConnection)(nil).SendDatagram), arg0) + return &EarlyConnectionSendDatagramCall{Call: call} } -// EarlyConnectionSendMessageCall wrap *gomock.Call -type EarlyConnectionSendMessageCall struct { +// EarlyConnectionSendDatagramCall wrap *gomock.Call +type EarlyConnectionSendDatagramCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *EarlyConnectionSendMessageCall) Return(arg0 error) *EarlyConnectionSendMessageCall { +func (c *EarlyConnectionSendDatagramCall) Return(arg0 error) *EarlyConnectionSendDatagramCall { c.Call = c.Call.Return(arg0) return c } // Do rewrite *gomock.Call.Do -func (c *EarlyConnectionSendMessageCall) Do(f func([]byte) error) *EarlyConnectionSendMessageCall { +func (c *EarlyConnectionSendDatagramCall) Do(f func([]byte) error) *EarlyConnectionSendDatagramCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *EarlyConnectionSendMessageCall) DoAndReturn(f func([]byte) error) *EarlyConnectionSendMessageCall { +func (c *EarlyConnectionSendDatagramCall) DoAndReturn(f func([]byte) error) *EarlyConnectionSendDatagramCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/mock_quic_conn_test.go b/mock_quic_conn_test.go index c7d84850e05..2a677066246 100644 --- a/mock_quic_conn_test.go +++ b/mock_quic_conn_test.go @@ -541,41 +541,41 @@ func (c *QUICConnOpenUniStreamSyncCall) DoAndReturn(f func(context.Context) (Sen return c } -// ReceiveMessage mocks base method. -func (m *MockQUICConn) ReceiveMessage(arg0 context.Context) ([]byte, error) { +// ReceiveDatagram mocks base method. +func (m *MockQUICConn) ReceiveDatagram(arg0 context.Context) ([]byte, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ReceiveMessage", arg0) + ret := m.ctrl.Call(m, "ReceiveDatagram", arg0) ret0, _ := ret[0].([]byte) ret1, _ := ret[1].(error) return ret0, ret1 } -// ReceiveMessage indicates an expected call of ReceiveMessage. -func (mr *MockQUICConnMockRecorder) ReceiveMessage(arg0 any) *QUICConnReceiveMessageCall { +// ReceiveDatagram indicates an expected call of ReceiveDatagram. +func (mr *MockQUICConnMockRecorder) ReceiveDatagram(arg0 any) *QUICConnReceiveDatagramCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReceiveMessage", reflect.TypeOf((*MockQUICConn)(nil).ReceiveMessage), arg0) - return &QUICConnReceiveMessageCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReceiveDatagram", reflect.TypeOf((*MockQUICConn)(nil).ReceiveDatagram), arg0) + return &QUICConnReceiveDatagramCall{Call: call} } -// QUICConnReceiveMessageCall wrap *gomock.Call -type QUICConnReceiveMessageCall struct { +// QUICConnReceiveDatagramCall wrap *gomock.Call +type QUICConnReceiveDatagramCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *QUICConnReceiveMessageCall) Return(arg0 []byte, arg1 error) *QUICConnReceiveMessageCall { +func (c *QUICConnReceiveDatagramCall) Return(arg0 []byte, arg1 error) *QUICConnReceiveDatagramCall { c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *QUICConnReceiveMessageCall) Do(f func(context.Context) ([]byte, error)) *QUICConnReceiveMessageCall { +func (c *QUICConnReceiveDatagramCall) Do(f func(context.Context) ([]byte, error)) *QUICConnReceiveDatagramCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *QUICConnReceiveMessageCall) DoAndReturn(f func(context.Context) ([]byte, error)) *QUICConnReceiveMessageCall { +func (c *QUICConnReceiveDatagramCall) DoAndReturn(f func(context.Context) ([]byte, error)) *QUICConnReceiveDatagramCall { c.Call = c.Call.DoAndReturn(f) return c } @@ -618,40 +618,40 @@ func (c *QUICConnRemoteAddrCall) DoAndReturn(f func() net.Addr) *QUICConnRemoteA return c } -// SendMessage mocks base method. -func (m *MockQUICConn) SendMessage(arg0 []byte) error { +// SendDatagram mocks base method. +func (m *MockQUICConn) SendDatagram(arg0 []byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SendMessage", arg0) + ret := m.ctrl.Call(m, "SendDatagram", arg0) ret0, _ := ret[0].(error) return ret0 } -// SendMessage indicates an expected call of SendMessage. -func (mr *MockQUICConnMockRecorder) SendMessage(arg0 any) *QUICConnSendMessageCall { +// SendDatagram indicates an expected call of SendDatagram. +func (mr *MockQUICConnMockRecorder) SendDatagram(arg0 any) *QUICConnSendDatagramCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockQUICConn)(nil).SendMessage), arg0) - return &QUICConnSendMessageCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendDatagram", reflect.TypeOf((*MockQUICConn)(nil).SendDatagram), arg0) + return &QUICConnSendDatagramCall{Call: call} } -// QUICConnSendMessageCall wrap *gomock.Call -type QUICConnSendMessageCall struct { +// QUICConnSendDatagramCall wrap *gomock.Call +type QUICConnSendDatagramCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *QUICConnSendMessageCall) Return(arg0 error) *QUICConnSendMessageCall { +func (c *QUICConnSendDatagramCall) Return(arg0 error) *QUICConnSendDatagramCall { c.Call = c.Call.Return(arg0) return c } // Do rewrite *gomock.Call.Do -func (c *QUICConnSendMessageCall) Do(f func([]byte) error) *QUICConnSendMessageCall { +func (c *QUICConnSendDatagramCall) Do(f func([]byte) error) *QUICConnSendDatagramCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *QUICConnSendMessageCall) DoAndReturn(f func([]byte) error) *QUICConnSendMessageCall { +func (c *QUICConnSendDatagramCall) DoAndReturn(f func([]byte) error) *QUICConnSendDatagramCall { c.Call = c.Call.DoAndReturn(f) return c }