CVE-2024-27304: Insufficient Information (NVD-CWE-noinfo)
value: pgx is a PostgreSQL driver and toolkit for Go. SQL injection can occur if an attacker can cause a single query or bind message to exceed 4 GB in size. An integer overflow in the calculated message size can cause the one large message to be sent as multiple messages under the attacker's control. The problem is resolved in v4.18.2 and v5.5.4. As a workaround, reject user input large enough to cause a single query or bind message to exceed 4 GB in size.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-03-06T19:15Z
Repository: https://github.com/jackc/pgproto3
Language: Go
Files changed: authentication_cleartext_password.go, authentication_gss.go, authentication_gss_continue.go, authentication_md5_password.go, authentication_ok.go, authentication_sasl.go, authentication_sasl_continue.go, authentication_sasl_final.go, backend.go, backend_key_data.go, backend_test.go, bind.go, bind_complete.go, bind_test.go, cancel_request.go, close.go, close_complete.go, command_complete.go, copy_both_response.go, copy_both_response_test.go, copy_data.go, copy_done.go, copy_fail.go, copy_in_response.go, copy_out_response.go, data_row.go, describe.go, empty_query_response.go, error_response.go, example/pgfortune/server.go, execute.go, flush.go, frontend.go, function_call.go, function_call_response.go, function_call_test.go, gss_enc_request.go, gss_response.go, no_data.go, notice_response.go, notification_response.go, parameter_description.go, parameter_status.go, parse.go, parse_complete.go, password_message.go, pgproto3.go, pgproto3_private_test.go, portal_suspended.go, query.go, query_test.go, ready_for_query.go, row_description.go, sasl_initial_response.go, sasl_response.go, ssl_request.go, startup_message.go, sync.go, terminate.go
Commit date: 2024-03-04 08:27:16 -0600
Commit message: Backport fixes from pgx v5

Check for overflow on uint16 sizes in pgproto3

Do not allow protocol messages larger than ~1GB

The PostgreSQL server will reject messages greater than ~1 GB anyway.
However, worse than that is that a message that is larger than 4 GB
could wrap the 32-bit integer message size and be interpreted by the
server as multiple messages. This could allow a malicious client to
inject arbitrary protocol messages.

https://github.com/jackc/pgx/security/advisories/GHSA-mrww-27vc-gghv

Diff: diff --git a/authentication_cleartext_password.go b/authentication_cleartext_password.go
index 241fa60..1ec219b 100644
--- a/authentication_cleartext_password.go
+++ b/authentication_cleartext_password.go
@@ -33,15 +33,14 @@ func (dst *AuthenticationCleartextPassword) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *AuthenticationCleartextPassword) Encode(dst []byte) []byte {
-	dst = append(dst, 'R')
-	dst = pgio.AppendInt32(dst, 8)
+func (src *AuthenticationCleartextPassword) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'R')
 	dst = pgio.AppendUint32(dst, AuthTypeCleartextPassword)
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src AuthenticationCleartextPassword) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/authentication_gss.go b/authentication_gss.go
index 5a3f3b1..425be6e 100644
--- a/authentication_gss.go
+++ b/authentication_gss.go
@@ -2,10 +2,11 @@ package pgproto3
 
 import (
 	"encoding/binary"
 	"encoding/json"
 	"errors"
+
 	"github.com/jackc/pgio"
 )
 
 type AuthenticationGSS struct{}
 
@@ -24,15 +25,14 @@ func (a *AuthenticationGSS) Decode(src []byte) error {
 		return errors.New("bad auth type")
 	}
 	return nil
 }
 
-func (a *AuthenticationGSS) Encode(dst []byte) []byte {
-	dst = append(dst, 'R')
-	dst = pgio.AppendInt32(dst, 4)
+func (a *AuthenticationGSS) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'R')
 	dst = pgio.AppendUint32(dst, AuthTypeGSS)
-	return dst
+	return finishMessage(dst, sp)
 }
 
 func (a *AuthenticationGSS) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
 		Type string
diff --git a/authentication_gss_continue.go b/authentication_gss_continue.go
index cf8b183..42a70da 100644
--- a/authentication_gss_continue.go
+++ b/authentication_gss_continue.go
@@ -2,10 +2,11 @@ package pgproto3
 
 import (
 	"encoding/binary"
 	"encoding/json"
 	"errors"
+
 	"github.com/jackc/pgio"
 )
 
 type AuthenticationGSSContinue struct {
 	Data []byte
@@ -28,16 +29,15 @@ func (a *AuthenticationGSSContinue) Decode(src []byte) error {
 
 	a.Data = src[4:]
 	return nil
 }
 
-func (a *AuthenticationGSSContinue) Encode(dst []byte) []byte {
-	dst = append(dst, 'R')
-	dst = pgio.AppendInt32(dst, int32(len(a.Data))+8)
+func (a *AuthenticationGSSContinue) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'R')
 	dst = pgio.AppendUint32(dst, AuthTypeGSSCont)
 	dst = append(dst, a.Data...)
-	return dst
+	return finishMessage(dst, sp)
 }
 
 func (a *AuthenticationGSSContinue) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
 		Type string
diff --git a/authentication_md5_password.go b/authentication_md5_password.go
index 32ec039..9c0f5ee 100644
--- a/authentication_md5_password.go
+++ b/authentication_md5_password.go
@@ -36,16 +36,15 @@ func (dst *AuthenticationMD5Password) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *AuthenticationMD5Password) Encode(dst []byte) []byte {
-	dst = append(dst, 'R')
-	dst = pgio.AppendInt32(dst, 12)
+func (src *AuthenticationMD5Password) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'R')
 	dst = pgio.AppendUint32(dst, AuthTypeMD5Password)
 	dst = append(dst, src.Salt[:]...)
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src AuthenticationMD5Password) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/authentication_ok.go b/authentication_ok.go
index 2b476fe..021f820 100644
--- a/authentication_ok.go
+++ b/authentication_ok.go
@@ -33,15 +33,14 @@ func (dst *AuthenticationOk) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *AuthenticationOk) Encode(dst []byte) []byte {
-	dst = append(dst, 'R')
-	dst = pgio.AppendInt32(dst, 8)
+func (src *AuthenticationOk) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'R')
 	dst = pgio.AppendUint32(dst, AuthTypeOk)
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src AuthenticationOk) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/authentication_sasl.go b/authentication_sasl.go
index bdcb2c3..b56461c 100644
--- a/authentication_sasl.go
+++ b/authentication_sasl.go
@@ -44,25 +44,21 @@ func (dst *AuthenticationSASL) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *AuthenticationSASL) Encode(dst []byte) []byte {
-	dst = append(dst, 'R')
-	sp := len(dst)
-	dst = pgio.AppendInt32(dst, -1)
+func (src *AuthenticationSASL) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'R')
 	dst = pgio.AppendUint32(dst, AuthTypeSASL)
 
 	for _, s := range src.AuthMechanisms {
 		dst = append(dst, []byte(s)...)
 		dst = append(dst, 0)
 	}
 	dst = append(dst, 0)
 
-	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src AuthenticationSASL) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/authentication_sasl_continue.go b/authentication_sasl_continue.go
index 7f4a9c2..d405b12 100644
--- a/authentication_sasl_continue.go
+++ b/authentication_sasl_continue.go
@@ -36,21 +36,15 @@ func (dst *AuthenticationSASLContinue) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *AuthenticationSASLContinue) Encode(dst []byte) []byte {
-	dst = append(dst, 'R')
-	sp := len(dst)
-	dst = pgio.AppendInt32(dst, -1)
+func (src *AuthenticationSASLContinue) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'R')
 	dst = pgio.AppendUint32(dst, AuthTypeSASLContinue)
-
 	dst = append(dst, src.Data...)
-
-	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src AuthenticationSASLContinue) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/authentication_sasl_final.go b/authentication_sasl_final.go
index d82b9ee..c34ac4e 100644
--- a/authentication_sasl_final.go
+++ b/authentication_sasl_final.go
@@ -36,21 +36,15 @@ func (dst *AuthenticationSASLFinal) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *AuthenticationSASLFinal) Encode(dst []byte) []byte {
-	dst = append(dst, 'R')
-	sp := len(dst)
-	dst = pgio.AppendInt32(dst, -1)
+func (src *AuthenticationSASLFinal) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'R')
 	dst = pgio.AppendUint32(dst, AuthTypeSASLFinal)
-
 	dst = append(dst, src.Data...)
-
-	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Unmarshaler.
 func (src AuthenticationSASLFinal) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/backend.go b/backend.go
index 1f14365..6eabcd8 100644
--- a/backend.go
+++ b/backend.go
@@ -47,11 +47,16 @@ func NewBackend(cr ChunkReader, w io.Writer) *Backend {
 	return &Backend{cr: cr, w: w}
 }
 
 // Send sends a message to the frontend.
 func (b *Backend) Send(msg BackendMessage) error {
-	_, err := b.w.Write(msg.Encode(nil))
+	buf, err := msg.Encode(nil)
+	if err != nil {
+		return err
+	}
+
+	_, err = b.w.Write(buf)
 	return err
 }
 
 // ReceiveStartupMessage receives the initial connection message. This method is used of the normal Receive method
 // because the initial connection message is "special" and does not include the message type as the first byte. This
@@ -182,15 +187,15 @@ func (b *Backend) Receive() (FrontendMessage, error) {
 // SetAuthType sets the authentication type in the backend.
 // Since multiple message types can start with 'p', SetAuthType allows
 // contextual identification of FrontendMessages. For example, in the
 // PG message flow documentation for PasswordMessage:
 //
-// 		Byte1('p')
+//			Byte1('p')
 //
-//      Identifies the message as a password response. Note that this is also used for
-//		GSSAPI, SSPI and SASL response messages. The exact message type can be deduced from
-//		the context.
+//	     Identifies the message as a password response. Note that this is also used for
+//			GSSAPI, SSPI and SASL response messages. The exact message type can be deduced from
+//			the context.
 //
 // Since the Frontend does not know about the state of a backend, it is important
 // to call SetAuthType() after an authentication request is received by the Frontend.
 func (b *Backend) SetAuthType(authType uint32) error {
 	switch authType {
diff --git a/backend_key_data.go b/backend_key_data.go
index ca20dd2..0a3d5e5 100644
--- a/backend_key_data.go
+++ b/backend_key_data.go
@@ -27,16 +27,15 @@ func (dst *BackendKeyData) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *BackendKeyData) Encode(dst []byte) []byte {
-	dst = append(dst, 'K')
-	dst = pgio.AppendUint32(dst, 12)
+func (src *BackendKeyData) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'K')
 	dst = pgio.AppendUint32(dst, src.ProcessID)
 	dst = pgio.AppendUint32(dst, src.SecretKey)
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src BackendKeyData) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/backend_test.go b/backend_test.go
index 708f128..97a9aa8 100644
--- a/backend_test.go
+++ b/backend_test.go
@@ -69,12 +69,12 @@ func TestStartupMessage(t *testing.T) {
 			ProtocolVersion: pgproto3.ProtocolVersionNumber,
 			Parameters: map[string]string{
 				"username": "tester",
 			},
 		}
-		dst := []byte{}
-		dst = want.Encode(dst)
+		dst, err := want.Encode([]byte{})
+		require.NoError(t, err)
 
 		server := &interruptReader{}
 		server.push(dst)
 
 		backend := pgproto3.NewBackend(pgproto3.NewChunkReader(server), nil)
diff --git a/bind.go b/bind.go
index e9664f5..dd5503b 100644
--- a/bind.go
+++ b/bind.go
@@ -3,11 +3,13 @@ package pgproto3
 import (
 	"bytes"
 	"encoding/binary"
 	"encoding/hex"
 	"encoding/json"
+	"errors"
 	"fmt"
+	"math"
 
 	"github.com/jackc/pgio"
 )
 
 type Bind struct {
@@ -106,25 +108,29 @@ func (dst *Bind) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *Bind) Encode(dst []byte) []byte {
-	dst = append(dst, 'B')
-	sp := len(dst)
-	dst = pgio.AppendInt32(dst, -1)
+func (src *Bind) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'B')
 
 	dst = append(dst, src.DestinationPortal...)
 	dst = append(dst, 0)
 	dst = append(dst, src.PreparedStatement...)
 	dst = append(dst, 0)
 
+	if len(src.ParameterFormatCodes) > math.MaxUint16 {
+		return nil, errors.New("too many parameter format codes")
+	}
 	dst = pgio.AppendUint16(dst, uint16(len(src.ParameterFormatCodes)))
 	for _, fc := range src.ParameterFormatCodes {
 		dst = pgio.AppendInt16(dst, fc)
 	}
 
+	if len(src.Parameters) > math.MaxUint16 {
+		return nil, errors.New("too many parameters")
+	}
 	dst = pgio.AppendUint16(dst, uint16(len(src.Parameters)))
 	for _, p := range src.Parameters {
 		if p == nil {
 			dst = pgio.AppendInt32(dst, -1)
 			continue
@@ -132,18 +138,19 @@ func (src *Bind) Encode(dst []byte) []byte {
 
 		dst = pgio.AppendInt32(dst, int32(len(p)))
 		dst = append(dst, p...)
 	}
 
+	if len(src.ResultFormatCodes) > math.MaxUint16 {
+		return nil, errors.New("too many result format codes")
+	}
 	dst = pgio.AppendUint16(dst, uint16(len(src.ResultFormatCodes)))
 	for _, fc := range src.ResultFormatCodes {
 		dst = pgio.AppendInt16(dst, fc)
 	}
 
-	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src Bind) MarshalJSON() ([]byte, error) {
 	formattedParameters := make([]map[string]string, len(src.Parameters))
diff --git a/bind_complete.go b/bind_complete.go
index 3be256c..bacf30d 100644
--- a/bind_complete.go
+++ b/bind_complete.go
@@ -18,12 +18,12 @@ func (dst *BindComplete) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *BindComplete) Encode(dst []byte) []byte {
-	return append(dst, '2', 0, 0, 0, 4)
+func (src *BindComplete) Encode(dst []byte) ([]byte, error) {
+	return append(dst, '2', 0, 0, 0, 4), nil
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src BindComplete) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/bind_test.go b/bind_test.go
new file mode 100644
index 0000000..c1de175
--- /dev/null
+++ b/bind_test.go
@@ -0,0 +1,20 @@
+package pgproto3_test
+
+import (
+	"testing"
+
+	"github.com/jackc/pgproto3/v2"
+	"github.com/stretchr/testify/require"
+)
+
+func TestBindBiggerThanMaxMessageBodyLen(t *testing.T) {
+	t.Parallel()
+
+	// Maximum allowed size.
+	_, err := (&pgproto3.Bind{Parameters: [][]byte{make([]byte, pgproto3.MaxMessageBodyLen-16)}}).Encode(nil)
+	require.NoError(t, err)
+
+	// 1 byte too big
+	_, err = (&pgproto3.Bind{Parameters: [][]byte{make([]byte, pgproto3.MaxMessageBodyLen-15)}}).Encode(nil)
+	require.Error(t, err)
+}
diff --git a/cancel_request.go b/cancel_request.go
index 942e404..76acb3f 100644
--- a/cancel_request.go
+++ b/cancel_request.go
@@ -34,16 +34,16 @@ func (dst *CancelRequest) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 4 byte message length.
-func (src *CancelRequest) Encode(dst []byte) []byte {
+func (src *CancelRequest) Encode(dst []byte) ([]byte, error) {
 	dst = pgio.AppendInt32(dst, 16)
 	dst = pgio.AppendInt32(dst, cancelRequestCode)
 	dst = pgio.AppendUint32(dst, src.ProcessID)
 	dst = pgio.AppendUint32(dst, src.SecretKey)
-	return dst
+	return dst, nil
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src CancelRequest) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/close.go b/close.go
index a45f2b9..0b50f27 100644
--- a/close.go
+++ b/close.go
@@ -2,12 +2,10 @@ package pgproto3
 
 import (
 	"bytes"
 	"encoding/json"
 	"errors"
-
-	"github.com/jackc/pgio"
 )
 
 type Close struct {
 	ObjectType byte // 'S' = prepared statement, 'P' = portal
 	Name       string
@@ -35,22 +33,16 @@ func (dst *Close) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *Close) Encode(dst []byte) []byte {
-	dst = append(dst, 'C')
-	sp := len(dst)
-	dst = pgio.AppendInt32(dst, -1)
-
+func (src *Close) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'C')
 	dst = append(dst, src.ObjectType)
 	dst = append(dst, src.Name...)
 	dst = append(dst, 0)
-
-	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src Close) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/close_complete.go b/close_complete.go
index 1d7b8f0..833f7a1 100644
--- a/close_complete.go
+++ b/close_complete.go
@@ -18,12 +18,12 @@ func (dst *CloseComplete) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *CloseComplete) Encode(dst []byte) []byte {
-	return append(dst, '3', 0, 0, 0, 4)
+func (src *CloseComplete) Encode(dst []byte) ([]byte, error) {
+	return append(dst, '3', 0, 0, 0, 4), nil
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src CloseComplete) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/command_complete.go b/command_complete.go
index cdc49f3..9d82206 100644
--- a/command_complete.go
+++ b/command_complete.go
@@ -1,12 +1,10 @@
 package pgproto3
 
 import (
 	"bytes"
 	"encoding/json"
-
-	"github.com/jackc/pgio"
 )
 
 type CommandComplete struct {
 	CommandTag []byte
 }
@@ -26,21 +24,15 @@ func (dst *CommandComplete) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *CommandComplete) Encode(dst []byte) []byte {
-	dst = append(dst, 'C')
-	sp := len(dst)
-	dst = pgio.AppendInt32(dst, -1)
-
+func (src *CommandComplete) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'C')
 	dst = append(dst, src.CommandTag...)
 	dst = append(dst, 0)
-
-	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src CommandComplete) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/copy_both_response.go b/copy_both_response.go
index 4a1c3a0..4bf3ef3 100644
--- a/copy_both_response.go
+++ b/copy_both_response.go
@@ -3,10 +3,11 @@ package pgproto3
 import (
 	"bytes"
 	"encoding/binary"
 	"encoding/json"
 	"errors"
+	"math"
 
 	"github.com/jackc/pgio"
 )
 
 type CopyBothResponse struct {
@@ -42,23 +43,22 @@ func (dst *CopyBothResponse) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *CopyBothResponse) Encode(dst []byte) []byte {
-	dst = append(dst, 'W')
-	sp := len(dst)
-	dst = pgio.AppendInt32(dst, -1)
+func (src *CopyBothResponse) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'W')
 	dst = append(dst, src.OverallFormat)
+	if len(src.ColumnFormatCodes) > math.MaxUint16 {
+		return nil, errors.New("too many column format codes")
+	}
 	dst = pgio.AppendUint16(dst, uint16(len(src.ColumnFormatCodes)))
 	for _, fc := range src.ColumnFormatCodes {
 		dst = pgio.AppendUint16(dst, fc)
 	}
 
-	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src CopyBothResponse) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/copy_both_response_test.go b/copy_both_response_test.go
index d9816fc..b9e36bb 100644
--- a/copy_both_response_test.go
+++ b/copy_both_response_test.go
@@ -3,16 +3,18 @@ package pgproto3_test
 import (
 	"testing"
 
 	"github.com/jackc/pgproto3/v2"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 func TestEncodeDecode(t *testing.T) {
 	srcBytes := []byte{'W', 0x00, 0x00, 0x00, 0x0b, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01}
 	dstResp := pgproto3.CopyBothResponse{}
 	err := dstResp.Decode(srcBytes[5:])
 	assert.NoError(t, err, "No errors on decode")
 	dstBytes := []byte{}
-	dstBytes = dstResp.Encode(dstBytes)
+	dstBytes, err = dstResp.Encode(dstBytes)
+	require.NoError(t, err)
 	assert.EqualValues(t, srcBytes, dstBytes, "Expecting src & dest bytes to match")
 }
diff --git a/copy_data.go b/copy_data.go
index 128aa19..89ecdd4 100644
--- a/copy_data.go
+++ b/copy_data.go
@@ -1,12 +1,10 @@
 package pgproto3
 
 import (
 	"encoding/hex"
 	"encoding/json"
-
-	"github.com/jackc/pgio"
 )
 
 type CopyData struct {
 	Data []byte
 }
@@ -23,15 +21,14 @@ func (dst *CopyData) Decode(src []byte) error {
 	dst.Data = src
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *CopyData) Encode(dst []byte) []byte {
-	dst = append(dst, 'd')
-	dst = pgio.AppendInt32(dst, int32(4+len(src.Data)))
+func (src *CopyData) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'd')
 	dst = append(dst, src.Data...)
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src CopyData) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/copy_done.go b/copy_done.go
index 0e13282..040814d 100644
--- a/copy_done.go
+++ b/copy_done.go
@@ -22,12 +22,12 @@ func (dst *CopyDone) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *CopyDone) Encode(dst []byte) []byte {
-	return append(dst, 'c', 0, 0, 0, 4)
+func (src *CopyDone) Encode(dst []byte) ([]byte, error) {
+	return append(dst, 'c', 0, 0, 0, 4), nil
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src CopyDone) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/copy_fail.go b/copy_fail.go
index 78ff0b3..72a85fd 100644
--- a/copy_fail.go
+++ b/copy_fail.go
@@ -1,12 +1,10 @@
 package pgproto3
 
 import (
 	"bytes"
 	"encoding/json"
-
-	"github.com/jackc/pgio"
 )
 
 type CopyFail struct {
 	Message string
 }
@@ -26,21 +24,15 @@ func (dst *CopyFail) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *CopyFail) Encode(dst []byte) []byte {
-	dst = append(dst, 'f')
-	sp := len(dst)
-	dst = pgio.AppendInt32(dst, -1)
-
+func (src *CopyFail) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'f')
 	dst = append(dst, src.Message...)
 	dst = append(dst, 0)
-
-	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src CopyFail) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/copy_in_response.go b/copy_in_response.go
index 80733ad..bfc3ee0 100644
--- a/copy_in_response.go
+++ b/copy_in_response.go
@@ -3,10 +3,11 @@ package pgproto3
 import (
 	"bytes"
 	"encoding/binary"
 	"encoding/json"
 	"errors"
+	"math"
 
 	"github.com/jackc/pgio"
 )
 
 type CopyInResponse struct {
@@ -42,24 +43,23 @@ func (dst *CopyInResponse) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *CopyInResponse) Encode(dst []byte) []byte {
-	dst = append(dst, 'G')
-	sp := len(dst)
-	dst = pgio.AppendInt32(dst, -1)
+func (src *CopyInResponse) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'G')
 
 	dst = append(dst, src.OverallFormat)
+	if len(src.ColumnFormatCodes) > math.MaxUint16 {
+		return nil, errors.New("too many column format codes")
+	}
 	dst = pgio.AppendUint16(dst, uint16(len(src.ColumnFormatCodes)))
 	for _, fc := range src.ColumnFormatCodes {
 		dst = pgio.AppendUint16(dst, fc)
 	}
 
-	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src CopyInResponse) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/copy_out_response.go b/copy_out_response.go
index 5e607e3..265e35f 100644
--- a/copy_out_response.go
+++ b/copy_out_response.go
@@ -3,10 +3,11 @@ package pgproto3
 import (
 	"bytes"
 	"encoding/binary"
 	"encoding/json"
 	"errors"
+	"math"
 
 	"github.com/jackc/pgio"
 )
 
 type CopyOutResponse struct {
@@ -41,25 +42,24 @@ func (dst *CopyOutResponse) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *CopyOutResponse) Encode(dst []byte) []byte {
-	dst = append(dst, 'H')
-	sp := len(dst)
-	dst = pgio.AppendInt32(dst, -1)
+func (src *CopyOutResponse) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'H')
 
 	dst = append(dst, src.OverallFormat)
 
+	if len(src.ColumnFormatCodes) > math.MaxUint16 {
+		return nil, errors.New("too many column format codes")
+	}
 	dst = pgio.AppendUint16(dst, uint16(len(src.ColumnFormatCodes)))
 	for _, fc := range src.ColumnFormatCodes {
 		dst = pgio.AppendUint16(dst, fc)
 	}
 
-	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src CopyOutResponse) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/data_row.go b/data_row.go
index 6376876..d755515 100644
--- a/data_row.go
+++ b/data_row.go
@@ -2,10 +2,12 @@ package pgproto3
 
 import (
 	"encoding/binary"
 	"encoding/hex"
 	"encoding/json"
+	"errors"
+	"math"
 
 	"github.com/jackc/pgio"
 )
 
 type DataRow struct {
@@ -61,15 +63,16 @@ func (dst *DataRow) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *DataRow) Encode(dst []byte) []byte {
-	dst = append(dst, 'D')
-	sp := len(dst)
-	dst = pgio.AppendInt32(dst, -1)
+func (src *DataRow) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'D')
 
+	if len(src.Values) > math.MaxUint16 {
+		return nil, errors.New("too many values")
+	}
 	dst = pgio.AppendUint16(dst, uint16(len(src.Values)))
 	for _, v := range src.Values {
 		if v == nil {
 			dst = pgio.AppendInt32(dst, -1)
 			continue
@@ -77,13 +80,11 @@ func (src *DataRow) Encode(dst []byte) []byte {
 
 		dst = pgio.AppendInt32(dst, int32(len(v)))
 		dst = append(dst, v...)
 	}
 
-	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src DataRow) MarshalJSON() ([]byte, error) {
 	formattedValues := make([]map[string]string, len(src.Values))
diff --git a/describe.go b/describe.go
index 0d825db..89feff2 100644
--- a/describe.go
+++ b/describe.go
@@ -2,12 +2,10 @@ package pgproto3
 
 import (
 	"bytes"
 	"encoding/json"
 	"errors"
-
-	"github.com/jackc/pgio"
 )
 
 type Describe struct {
 	ObjectType byte // 'S' = prepared statement, 'P' = portal
 	Name       string
@@ -35,22 +33,16 @@ func (dst *Describe) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *Describe) Encode(dst []byte) []byte {
-	dst = append(dst, 'D')
-	sp := len(dst)
-	dst = pgio.AppendInt32(dst, -1)
-
+func (src *Describe) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'D')
 	dst = append(dst, src.ObjectType)
 	dst = append(dst, src.Name...)
 	dst = append(dst, 0)
-
-	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src Describe) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/empty_query_response.go b/empty_query_response.go
index 2b85e74..cb6cca0 100644
--- a/empty_query_response.go
+++ b/empty_query_response.go
@@ -18,12 +18,12 @@ func (dst *EmptyQueryResponse) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *EmptyQueryResponse) Encode(dst []byte) []byte {
-	return append(dst, 'I', 0, 0, 0, 4)
+func (src *EmptyQueryResponse) Encode(dst []byte) ([]byte, error) {
+	return append(dst, 'I', 0, 0, 0, 4), nil
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src EmptyQueryResponse) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/error_response.go b/error_response.go
index ec51e01..6ef9bd0 100644
--- a/error_response.go
+++ b/error_response.go
@@ -1,10 +1,9 @@
 package pgproto3
 
 import (
 	"bytes"
-	"encoding/binary"
 	"encoding/json"
 	"strconv"
 )
 
 type ErrorResponse struct {
@@ -109,124 +108,117 @@ func (dst *ErrorResponse) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *ErrorResponse) Encode(dst []byte) []byte {
-	return append(dst, src.marshalBinary('E')...)
+func (src *ErrorResponse) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'E')
+	dst = src.appendFields(dst)
+	return finishMessage(dst, sp)
 }
 
-func (src *ErrorResponse) marshalBinary(typeByte byte) []byte {
-	var bigEndian BigEndianBuf
-	buf := &bytes.Buffer{}
-
-	buf.WriteByte(typeByte)
-	buf.Write(bigEndian.Uint32(0))
-
+func (src *ErrorResponse) appendFields(dst []byte) []byte {
 	if src.Severity != "" {
-		buf.WriteByte('S')
-		buf.WriteString(src.Severity)
-		buf.WriteByte(0)
+		dst = append(dst, 'S')
+		dst = append(dst, src.Severity...)
+		dst = append(dst, 0)
 	}
 	if src.SeverityUnlocalized != "" {
-		buf.WriteByte('V')
-		buf.WriteString(src.SeverityUnlocalized)
-		buf.WriteByte(0)
+		dst = append(dst, 'V')
+		dst = append(dst, src.SeverityUnlocalized...)
+		dst = append(dst, 0)
 	}
 	if src.Code != "" {
-		buf.WriteByte('C')
-		buf.WriteString(src.Code)
-		buf.WriteByte(0)
+		dst = append(dst, 'C')
+		dst = append(dst, src.Code...)
+		dst = append(dst, 0)
 	}
 	if src.Message != "" {
-		buf.WriteByte('M')
-		buf.WriteString(src.Message)
-		buf.WriteByte(0)
+		dst = append(dst, 'M')
+		dst = append(dst, src.Message...)
+		dst = append(dst, 0)
 	}
 	if src.Detail != "" {
-		buf.WriteByte('D')
-		buf.WriteString(src.Detail)
-		buf.WriteByte(0)
+		dst = append(dst, 'D')
+		dst = append(dst, src.Detail...)
+		dst = append(dst, 0)
 	}
 	if src.Hint != "" {
-		buf.WriteByte('H')
-		buf.WriteString(src.Hint)
-		buf.WriteByte(0)
+		dst = append(dst, 'H')
+		dst = append(dst, src.Hint...)
+		dst = append(dst, 0)
 	}
 	if src.Position != 0 {
-		buf.WriteByte('P')
-		buf.WriteString(strconv.Itoa(int(src.Position)))
-		buf.WriteByte(0)
+		dst = append(dst, 'P')
+		dst = append(dst, strconv.Itoa(int(src.Position))...)
+		dst = append(dst, 0)
 	}
 	if src.InternalPosition != 0 {
-		buf.WriteByte('p')
-		buf.WriteString(strconv.Itoa(int(src.InternalPosition)))
-		buf.WriteByte(0)
+		dst = append(dst, 'p')
+		dst = append(dst, strconv.Itoa(int(src.InternalPosition))...)
+		dst = append(dst, 0)
 	}
 	if src.InternalQuery != "" {
-		buf.WriteByte('q')
-		buf.WriteString(src.InternalQuery)
-		buf.WriteByte(0)
+		dst = append(dst, 'q')
+		dst = append(dst, src.InternalQuery...)
+		dst = append(dst, 0)
 	}
 	if src.Where != "" {
-		buf.WriteByte('W')
-		buf.WriteString(src.Where)
-		buf.WriteByte(0)
+		dst = append(dst, 'W')
+		dst = append(dst, src.Where...)
+		dst = append(dst, 0)
 	}
 	if src.SchemaName != "" {
-		buf.WriteByte('s')
-		buf.WriteString(src.SchemaName)
-		buf.WriteByte(0)
+		dst = append(dst, 's')
+		dst = append(dst, src.SchemaName...)
+		dst = append(dst, 0)
 	}
 	if src.TableName != "" {
-		buf.WriteByte('t')
-		buf.WriteString(src.TableName)
-		buf.WriteByte(0)
+		dst = append(dst, 't')
+		dst = append(dst, src.TableName...)
+		dst = append(dst, 0)
 	}
 	if src.ColumnName != "" {
-		buf.WriteByte('c')
-		buf.WriteString(src.ColumnName)
-		buf.WriteByte(0)
+		dst = append(dst, 'c')
+		dst = append(dst, src.ColumnName...)
+		dst = append(dst, 0)
 	}
 	if src.DataTypeName != "" {
-		buf.WriteByte('d')
-		buf.WriteString(src.DataTypeName)
-		buf.WriteByte(0)
+		dst = append(dst, 'd')
+		dst = append(dst, src.DataTypeName...)
+		dst = append(dst, 0)
 	}
 	if src.ConstraintName != "" {
-		buf.WriteByte('n')
-		buf.WriteString(src.ConstraintName)
-		buf.WriteByte(0)
+		dst = append(dst, 'n')
+		dst = append(dst, src.ConstraintName...)
+		dst = append(dst, 0)
 	}
 	if src.File != "" {
-		buf.WriteByte('F')
-		buf.WriteString(src.File)
-		buf.WriteByte(0)
+		dst = append(dst, 'F')
+		dst = append(dst, src.File...)
+		dst = append(dst, 0)
 	}
 	if src.Line != 0 {
-		buf.WriteByte('L')
-		buf.WriteString(strconv.Itoa(int(src.Line)))
-		buf.WriteByte(0)
+		dst = append(dst, 'L')
+		dst = append(dst, strconv.Itoa(int(src.Line))...)
+		dst = append(dst, 0)
 	}
 	if src.Routine != "" {
-		buf.WriteByte('R')
-		buf.WriteString(src.Routine)
-		buf.WriteByte(0)
+		dst = append(dst, 'R')
+		dst = append(dst, src.Routine...)
+		dst = append(dst, 0)
 	}
 
 	for k, v := range src.UnknownFields {
-		buf.WriteByte(k)
-		buf.WriteByte(0)
-		buf.WriteString(v)
-		buf.WriteByte(0)
+		dst = append(dst, k)
+		dst = append(dst, v...)
+		dst = append(dst, 0)
 	}
 
-	buf.WriteByte(0)
-
-	binary.BigEndian.PutUint32(buf.Bytes()[1:5], uint32(buf.Len()-1))
+	dst = append(dst, 0)
 
-	return buf.Bytes()
+	return dst
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src ErrorResponse) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/example/pgfortune/server.go b/example/pgfortune/server.go
index 777192a..4f00dfd 100644
--- a/example/pgfortune/server.go
+++ b/example/pgfortune/server.go
@@ -44,24 +44,24 @@ func (p *PgFortuneBackend) Run() error {
 			response, err := p.responder()
 			if err != nil {
 				return fmt.Errorf("error generating query response: %w", err)
 			}
 
-			buf := (&pgproto3.RowDescription{Fields: []pgproto3.FieldDescription{
+			buf := mustEncode((&pgproto3.RowDescription{Fields: []pgproto3.FieldDescription{
 				{
 					Name:                 []byte("fortune"),
 					TableOID:             0,
 					TableAttributeNumber: 0,
 					DataTypeOID:          25,
 					DataTypeSize:         -1,
 					TypeModifier:         -1,
 					Format:               0,
 				},
-			}}).Encode(nil)
-			buf = (&pgproto3.DataRow{Values: [][]byte{response}}).Encode(buf)
-			buf = (&pgproto3.CommandComplete{CommandTag: []byte("SELECT 1")}).Encode(buf)
-			buf = (&pgproto3.ReadyForQuery{TxStatus: 'I'}).Encode(buf)
+			}}).Encode(nil))
+			buf = mustEncode((&pgproto3.DataRow{Values: [][]byte{response}}).Encode(buf))
+			buf = mustEncode((&pgproto3.CommandComplete{CommandTag: []byte("SELECT 1")}).Encode(buf))
+			buf = mustEncode((&pgproto3.ReadyForQuery{TxStatus: 'I'}).Encode(buf))
 			_, err = p.conn.Write(buf)
 			if err != nil {
 				return fmt.Errorf("error writing query response: %w", err)
 			}
 		case *pgproto3.Terminate:
@@ -78,12 +78,12 @@ func (p *PgFortuneBackend) handleStartup() error {
 		return fmt.Errorf("error receiving startup message: %w", err)
 	}
 
 	switch startupMessage.(type) {
 	case *pgproto3.StartupMessage:
-		buf := (&pgproto3.AuthenticationOk{}).Encode(nil)
-		buf = (&pgproto3.ReadyForQuery{TxStatus: 'I'}).Encode(buf)
+		buf := mustEncode((&pgproto3.AuthenticationOk{}).Encode(nil))
+		buf = mustEncode((&pgproto3.ReadyForQuery{TxStatus: 'I'}).Encode(buf))
 		_, err = p.conn.Write(buf)
 		if err != nil {
 			return fmt.Errorf("error sending ready for query: %w", err)
 		}
 	case *pgproto3.SSLRequest:
@@ -100,5 +100,12 @@ func (p *PgFortuneBackend) handleStartup() error {
 }
 
 func (p *PgFortuneBackend) Close() error {
 	return p.conn.Close()
 }
+
+func mustEncode(buf []byte, err error) []byte {
+	if err != nil {
+		panic(err)
+	}
+	return buf
+}
diff --git a/execute.go b/execute.go
index 8bae613..efb9e1e 100644
--- a/execute.go
+++ b/execute.go
@@ -34,23 +34,16 @@ func (dst *Execute) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *Execute) Encode(dst []byte) []byte {
-	dst = append(dst, 'E')
-	sp := len(dst)
-	dst = pgio.AppendInt32(dst, -1)
-
+func (src *Execute) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'E')
 	dst = append(dst, src.Portal...)
 	dst = append(dst, 0)
-
 	dst = pgio.AppendUint32(dst, src.MaxRows)
-
-	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src Execute) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/flush.go b/flush.go
index 2725f68..e5dc1fb 100644
--- a/flush.go
+++ b/flush.go
@@ -18,12 +18,12 @@ func (dst *Flush) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *Flush) Encode(dst []byte) []byte {
-	return append(dst, 'H', 0, 0, 0, 4)
+func (src *Flush) Encode(dst []byte) ([]byte, error) {
+	return append(dst, 'H', 0, 0, 0, 4), nil
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src Flush) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/frontend.go b/frontend.go
index 5be8de8..623b0a9 100644
--- a/frontend.go
+++ b/frontend.go
@@ -55,11 +55,15 @@ func NewFrontend(cr ChunkReader, w io.Writer) *Frontend {
 	return &Frontend{cr: cr, w: w}
 }
 
 // Send sends a message to the backend.
 func (f *Frontend) Send(msg FrontendMessage) error {
-	_, err := f.w.Write(msg.Encode(nil))
+	buf, err := msg.Encode(nil)
+	if err != nil {
+		return err
+	}
+	_, err = f.w.Write(buf)
 	return err
 }
 
 func translateEOFtoErrUnexpectedEOF(err error) error {
 	if err == io.EOF {
diff --git a/function_call.go b/function_call.go
index b3a22c4..5d799c4 100644
--- a/function_call.go
+++ b/function_call.go
@@ -1,9 +1,12 @@
 package pgproto3
 
 import (
 	"encoding/binary"
+	"errors"
+	"math"
+
 	"github.com/jackc/pgio"
 )
 
 type FunctionCall struct {
 	Function         uint32
@@ -68,27 +71,32 @@ func (dst *FunctionCall) Decode(src []byte) error {
 	dst.ResultFormatCode = resultFormatCode
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *FunctionCall) Encode(dst []byte) []byte {
-	dst = append(dst, 'F')
-	sp := len(dst)
-	dst = pgio.AppendUint32(dst, 0) // Unknown length, set it at the end
+func (src *FunctionCall) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'F')
 	dst = pgio.AppendUint32(dst, src.Function)
+
+	if len(src.ArgFormatCodes) > math.MaxUint16 {
+		return nil, errors.New("too many arg format codes")
+	}
 	dst = pgio.AppendUint16(dst, uint16(len(src.ArgFormatCodes)))
 	for _, argFormatCode := range src.ArgFormatCodes {
 		dst = pgio.AppendUint16(dst, argFormatCode)
 	}
+
+	if len(src.Arguments) > math.MaxUint16 {
+		return nil, errors.New("too many arguments")
+	}
 	dst = pgio.AppendUint16(dst, uint16(len(src.Arguments)))
 	for _, argument := range src.Arguments {
 		if argument == nil {
 			dst = pgio.AppendInt32(dst, -1)
 		} else {
 			dst = pgio.AppendInt32(dst, int32(len(argument)))
 			dst = append(dst, argument...)
 		}
 	}
 	dst = pgio.AppendUint16(dst, src.ResultFormatCode)
-	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
-	return dst
+	return finishMessage(dst, sp)
 }
diff --git a/function_call_response.go b/function_call_response.go
index 53d6422..abc14f0 100644
--- a/function_call_response.go
+++ b/function_call_response.go
@@ -37,25 +37,21 @@ func (dst *FunctionCallResponse) Decode(src []byte) error {
 	dst.Result = src[rp:]
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *FunctionCallResponse) Encode(dst []byte) []byte {
-	dst = append(dst, 'V')
-	sp := len(dst)
-	dst = pgio.AppendInt32(dst, -1)
+func (src *FunctionCallResponse) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'V')
 
 	if src.Result == nil {
 		dst = pgio.AppendInt32(dst, -1)
 	} else {
 		dst = pgio.AppendInt32(dst, int32(len(src.Result)))
 		dst = append(dst, src.Result...)
 	}
 
-	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src FunctionCallResponse) MarshalJSON() ([]byte, error) {
 	var formattedValue map[string]string
diff --git a/function_call_test.go b/function_call_test.go
index 8c08bb2..2a70fd3 100644
--- a/function_call_test.go
+++ b/function_call_test.go
@@ -2,10 +2,12 @@ package pgproto3
 
 import (
 	"encoding/binary"
 	"reflect"
 	"testing"
+
+	"github.com/stretchr/testify/require"
 )
 
 func TestFunctionCall_EncodeDecode(t *testing.T) {
 	type fields struct {
 		Function         uint32
@@ -28,11 +30,12 @@ func TestFunctionCall_EncodeDecode(t *testing.T) {
 				Function:         tt.fields.Function,
 				ArgFormatCodes:   tt.fields.ArgFormatCodes,
 				Arguments:        tt.fields.Arguments,
 				ResultFormatCode: tt.fields.ResultFormatCode,
 			}
-			encoded := src.Encode([]byte{})
+			encoded, err := src.Encode([]byte{})
+			require.NoError(t, err)
 			dst := &FunctionCall{}
 			// Check the header
 			msgTypeCode := encoded[0]
 			if msgTypeCode != 'F' {
 				t.Errorf("msgTypeCode %v should be 'F'", msgTypeCode)
@@ -42,11 +45,11 @@ func TestFunctionCall_EncodeDecode(t *testing.T) {
 			l := binary.BigEndian.Uint32(encoded[1:5])
 			if int(l) != (len(encoded) - 1) {
 				t.Errorf("Incorrect message length, got = %v, wanted = %v", l, len(encoded))
 			}
 			// Check decoding works as expected
-			err := dst.Decode(encoded[5:])
+			err = dst.Decode(encoded[5:])
 			if err != nil {
 				if !tt.wantErr {
 					t.Errorf("FunctionCall.Decode() error = %v, wantErr %v", err, tt.wantErr)
 				}
 				return
diff --git a/gss_enc_request.go b/gss_enc_request.go
index cf405a3..f6e4f66 100644
--- a/gss_enc_request.go
+++ b/gss_enc_request.go
@@ -29,14 +29,14 @@ func (dst *GSSEncRequest) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 4 byte message length.
-func (src *GSSEncRequest) Encode(dst []byte) []byte {
+func (src *GSSEncRequest) Encode(dst []byte) ([]byte, error) {
 	dst = pgio.AppendInt32(dst, 8)
 	dst = pgio.AppendInt32(dst, gssEncReqNumber)
-	return dst
+	return dst, nil
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src GSSEncRequest) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/gss_response.go b/gss_response.go
index 62da99c..10d9377 100644
--- a/gss_response.go
+++ b/gss_response.go
@@ -1,10 +1,9 @@
 package pgproto3
 
 import (
 	"encoding/json"
-	"github.com/jackc/pgio"
 )
 
 type GSSResponse struct {
 	Data []byte
 }
@@ -15,15 +14,14 @@ func (g *GSSResponse) Frontend() {}
 func (g *GSSResponse) Decode(data []byte) error {
 	g.Data = data
 	return nil
 }
 
-func (g *GSSResponse) Encode(dst []byte) []byte {
-	dst = append(dst, 'p')
-	dst = pgio.AppendInt32(dst, int32(4+len(g.Data)))
+func (g *GSSResponse) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'p')
 	dst = append(dst, g.Data...)
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (g *GSSResponse) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/no_data.go b/no_data.go
index d8f85d3..cbcaad4 100644
--- a/no_data.go
+++ b/no_data.go
@@ -18,12 +18,12 @@ func (dst *NoData) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *NoData) Encode(dst []byte) []byte {
-	return append(dst, 'n', 0, 0, 0, 4)
+func (src *NoData) Encode(dst []byte) ([]byte, error) {
+	return append(dst, 'n', 0, 0, 0, 4), nil
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src NoData) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/notice_response.go b/notice_response.go
index 4ac28a7..497aba6 100644
--- a/notice_response.go
+++ b/notice_response.go
@@ -10,8 +10,10 @@ func (*NoticeResponse) Backend() {}
 func (dst *NoticeResponse) Decode(src []byte) error {
 	return (*ErrorResponse)(dst).Decode(src)
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *NoticeResponse) Encode(dst []byte) []byte {
-	return append(dst, (*ErrorResponse)(src).marshalBinary('N')...)
+func (src *NoticeResponse) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'N')
+	dst = (*ErrorResponse)(src).appendFields(dst)
+	return finishMessage(dst, sp)
 }
diff --git a/notification_response.go b/notification_response.go
index e762eb9..5be3edd 100644
--- a/notification_response.go
+++ b/notification_response.go
@@ -39,24 +39,18 @@ func (dst *NotificationResponse) Decode(src []byte) error {
 	*dst = NotificationResponse{PID: pid, Channel: channel, Payload: payload}
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *NotificationResponse) Encode(dst []byte) []byte {
-	dst = append(dst, 'A')
-	sp := len(dst)
-	dst = pgio.AppendInt32(dst, -1)
-
+func (src *NotificationResponse) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'A')
 	dst = pgio.AppendUint32(dst, src.PID)
 	dst = append(dst, src.Channel...)
 	dst = append(dst, 0)
 	dst = append(dst, src.Payload...)
 	dst = append(dst, 0)
-
-	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src NotificationResponse) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/parameter_description.go b/parameter_description.go
index e28965c..fec0fce 100644
--- a/parameter_description.go
+++ b/parameter_description.go
@@ -2,10 +2,12 @@ package pgproto3
 
 import (
 	"bytes"
 	"encoding/binary"
 	"encoding/json"
+	"errors"
+	"math"
 
 	"github.com/jackc/pgio"
 )
 
 type ParameterDescription struct {
@@ -37,23 +39,22 @@ func (dst *ParameterDescription) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *ParameterDescription) Encode(dst []byte) []byte {
-	dst = append(dst, 't')
-	sp := len(dst)
-	dst = pgio.AppendInt32(dst, -1)
+func (src *ParameterDescription) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 't')
 
+	if len(src.ParameterOIDs) > math.MaxUint16 {
+		return nil, errors.New("too many parameter oids")
+	}
 	dst = pgio.AppendUint16(dst, uint16(len(src.ParameterOIDs)))
 	for _, oid := range src.ParameterOIDs {
 		dst = pgio.AppendUint32(dst, oid)
 	}
 
-	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src ParameterDescription) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/parameter_status.go b/parameter_status.go
index c4021d9..9ee0720 100644
--- a/parameter_status.go
+++ b/parameter_status.go
@@ -1,12 +1,10 @@
 package pgproto3
 
 import (
 	"bytes"
 	"encoding/json"
-
-	"github.com/jackc/pgio"
 )
 
 type ParameterStatus struct {
 	Name  string
 	Value string
@@ -35,23 +33,17 @@ func (dst *ParameterStatus) Decode(src []byte) error {
 	*dst = ParameterStatus{Name: name, Value: value}
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *ParameterStatus) Encode(dst []byte) []byte {
-	dst = append(dst, 'S')
-	sp := len(dst)
-	dst = pgio.AppendInt32(dst, -1)
-
+func (src *ParameterStatus) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'S')
 	dst = append(dst, src.Name...)
 	dst = append(dst, 0)
 	dst = append(dst, src.Value...)
 	dst = append(dst, 0)
-
-	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (ps ParameterStatus) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/parse.go b/parse.go
index 723885d..7dd0699 100644
--- a/parse.go
+++ b/parse.go
@@ -2,10 +2,12 @@ package pgproto3
 
 import (
 	"bytes"
 	"encoding/binary"
 	"encoding/json"
+	"errors"
+	"math"
 
 	"github.com/jackc/pgio"
 )
 
 type Parse struct {
@@ -50,28 +52,27 @@ func (dst *Parse) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *Parse) Encode(dst []byte) []byte {
-	dst = append(dst, 'P')
-	sp := len(dst)
-	dst = pgio.AppendInt32(dst, -1)
+func (src *Parse) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'P')
 
 	dst = append(dst, src.Name...)
 	dst = append(dst, 0)
 	dst = append(dst, src.Query...)
 	dst = append(dst, 0)
 
+	if len(src.ParameterOIDs) > math.MaxUint16 {
+		return nil, errors.New("too many parameter oids")
+	}
 	dst = pgio.AppendUint16(dst, uint16(len(src.ParameterOIDs)))
 	for _, oid := range src.ParameterOIDs {
 		dst = pgio.AppendUint32(dst, oid)
 	}
 
-	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src Parse) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/parse_complete.go b/parse_complete.go
index 92c9498..cff9e27 100644
--- a/parse_complete.go
+++ b/parse_complete.go
@@ -18,12 +18,12 @@ func (dst *ParseComplete) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *ParseComplete) Encode(dst []byte) []byte {
-	return append(dst, '1', 0, 0, 0, 4)
+func (src *ParseComplete) Encode(dst []byte) ([]byte, error) {
+	return append(dst, '1', 0, 0, 0, 4), nil
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src ParseComplete) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/password_message.go b/password_message.go
index cae76c5..d820d32 100644
--- a/password_message.go
+++ b/password_message.go
@@ -1,12 +1,10 @@
 package pgproto3
 
 import (
 	"bytes"
 	"encoding/json"
-
-	"github.com/jackc/pgio"
 )
 
 type PasswordMessage struct {
 	Password string
 }
@@ -30,18 +28,15 @@ func (dst *PasswordMessage) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *PasswordMessage) Encode(dst []byte) []byte {
-	dst = append(dst, 'p')
-	dst = pgio.AppendInt32(dst, int32(4+len(src.Password)+1))
-
+func (src *PasswordMessage) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'p')
 	dst = append(dst, src.Password...)
 	dst = append(dst, 0)
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src PasswordMessage) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/pgproto3.go b/pgproto3.go
index 70c825e..aa4167c 100644
--- a/pgproto3.go
+++ b/pgproto3.go
@@ -2,21 +2,27 @@ package pgproto3
 
 import (
 	"encoding/hex"
 	"errors"
 	"fmt"
+
+	"github.com/jackc/pgio"
 )
 
+// maxMessageBodyLen is the maximum length of a message body in bytes. See PG_LARGE_MESSAGE_LIMIT in the PostgreSQL
+// source. It is defined as (MaxAllocSize - 1). MaxAllocSize is defined as 0x3fffffff.
+const maxMessageBodyLen = (0x3fffffff - 1)
+
 // Message is the interface implemented by an object that can decode and encode
 // a particular PostgreSQL message.
 type Message interface {
 	// Decode is allowed and expected to retain a reference to data after
 	// returning (unlike encoding.BinaryUnmarshaler).
 	Decode(data []byte) error
 
 	// Encode appends itself to dst and returns the new buffer.
-	Encode(dst []byte) []byte
+	Encode(dst []byte) ([]byte, error)
 }
 
 type FrontendMessage interface {
 	Message
 	Frontend() // no-op method to distinguish frontend from backend methods
@@ -61,5 +67,25 @@ func getValueFromJSON(v map[string]string) ([]byte, error) {
 	if binary, ok := v["binary"]; ok {
 		return hex.DecodeString(binary)
 	}
 	return nil, errors.New("unknown protocol representation")
 }
+
+// beginMessage begines a new message of type t. It appends the message type and a placeholder for the message length to
+// dst. It returns the new buffer and the position of the message length placeholder.
+func beginMessage(dst []byte, t byte) ([]byte, int) {
+	dst = append(dst, t)
+	sp := len(dst)
+	dst = pgio.AppendInt32(dst, -1)
+	return dst, sp
+}
+
+// finishMessage finishes a message that was started with beginMessage. It computes the message length and writes it to
+// dst[sp]. If the message length is too large it returns an error. Otherwise it returns the final message buffer.
+func finishMessage(dst []byte, sp int) ([]byte, error) {
+	messageBodyLen := len(dst[sp:])
+	if messageBodyLen > maxMessageBodyLen {
+		return nil, errors.New("message body too large")
+	}
+	pgio.SetInt32(dst[sp:], int32(messageBodyLen))
+	return dst, nil
+}
diff --git a/pgproto3_private_test.go b/pgproto3_private_test.go
new file mode 100644
index 0000000..15da1ea
--- /dev/null
+++ b/pgproto3_private_test.go
@@ -0,0 +1,3 @@
+package pgproto3
+
+const MaxMessageBodyLen = maxMessageBodyLen
diff --git a/portal_suspended.go b/portal_suspended.go
index 1a9e7bf..9e2f8cb 100644
--- a/portal_suspended.go
+++ b/portal_suspended.go
@@ -18,12 +18,12 @@ func (dst *PortalSuspended) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *PortalSuspended) Encode(dst []byte) []byte {
-	return append(dst, 's', 0, 0, 0, 4)
+func (src *PortalSuspended) Encode(dst []byte) ([]byte, error) {
+	return append(dst, 's', 0, 0, 0, 4), nil
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src PortalSuspended) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/query.go b/query.go
index 41c93b4..aebdfde 100644
--- a/query.go
+++ b/query.go
@@ -1,12 +1,10 @@
 package pgproto3
 
 import (
 	"bytes"
 	"encoding/json"
-
-	"github.com/jackc/pgio"
 )
 
 type Query struct {
 	String string
 }
@@ -26,18 +24,15 @@ func (dst *Query) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *Query) Encode(dst []byte) []byte {
-	dst = append(dst, 'Q')
-	dst = pgio.AppendInt32(dst, int32(4+len(src.String)+1))
-
+func (src *Query) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'Q')
 	dst = append(dst, src.String...)
 	dst = append(dst, 0)
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src Query) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/query_test.go b/query_test.go
new file mode 100644
index 0000000..c33a1ed
--- /dev/null
+++ b/query_test.go
@@ -0,0 +1,20 @@
+package pgproto3_test
+
+import (
+	"testing"
+
+	"github.com/jackc/pgproto3/v2"
+	"github.com/stretchr/testify/require"
+)
+
+func TestQueryBiggerThanMaxMessageBodyLen(t *testing.T) {
+	t.Parallel()
+
+	// Maximum allowed size. 4 bytes for size and 1 byte for 0 terminated string.
+	_, err := (&pgproto3.Query{String: string(make([]byte, pgproto3.MaxMessageBodyLen-5))}).Encode(nil)
+	require.NoError(t, err)
+
+	// 1 byte too big
+	_, err = (&pgproto3.Query{String: string(make([]byte, pgproto3.MaxMessageBodyLen-4))}).Encode(nil)
+	require.Error(t, err)
+}
diff --git a/ready_for_query.go b/ready_for_query.go
index 67a39be..a56af9f 100644
--- a/ready_for_query.go
+++ b/ready_for_query.go
@@ -23,12 +23,12 @@ func (dst *ReadyForQuery) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *ReadyForQuery) Encode(dst []byte) []byte {
-	return append(dst, 'Z', 0, 0, 0, 5, src.TxStatus)
+func (src *ReadyForQuery) Encode(dst []byte) ([]byte, error) {
+	return append(dst, 'Z', 0, 0, 0, 5, src.TxStatus), nil
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src ReadyForQuery) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/row_description.go b/row_description.go
index a2e0d28..3f6b2c6 100644
--- a/row_description.go
+++ b/row_description.go
@@ -2,10 +2,12 @@ package pgproto3
 
 import (
 	"bytes"
 	"encoding/binary"
 	"encoding/json"
+	"errors"
+	"math"
 
 	"github.com/jackc/pgio"
 )
 
 const (
@@ -97,15 +99,16 @@ func (dst *RowDescription) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *RowDescription) Encode(dst []byte) []byte {
-	dst = append(dst, 'T')
-	sp := len(dst)
-	dst = pgio.AppendInt32(dst, -1)
+func (src *RowDescription) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'T')
 
+	if len(src.Fields) > math.MaxUint16 {
+		return nil, errors.New("too many fields")
+	}
 	dst = pgio.AppendUint16(dst, uint16(len(src.Fields)))
 	for _, fd := range src.Fields {
 		dst = append(dst, fd.Name...)
 		dst = append(dst, 0)
 
@@ -115,13 +118,11 @@ func (src *RowDescription) Encode(dst []byte) []byte {
 		dst = pgio.AppendInt16(dst, fd.DataTypeSize)
 		dst = pgio.AppendInt32(dst, fd.TypeModifier)
 		dst = pgio.AppendInt16(dst, fd.Format)
 	}
 
-	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src RowDescription) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/sasl_initial_response.go b/sasl_initial_response.go
index a6b553e..1938f65 100644
--- a/sasl_initial_response.go
+++ b/sasl_initial_response.go
@@ -36,24 +36,20 @@ func (dst *SASLInitialResponse) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *SASLInitialResponse) Encode(dst []byte) []byte {
-	dst = append(dst, 'p')
-	sp := len(dst)
-	dst = pgio.AppendInt32(dst, -1)
+func (src *SASLInitialResponse) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'p')
 
 	dst = append(dst, []byte(src.AuthMechanism)...)
 	dst = append(dst, 0)
 
 	dst = pgio.AppendInt32(dst, int32(len(src.Data)))
 	dst = append(dst, src.Data...)
 
-	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src SASLInitialResponse) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/sasl_response.go b/sasl_response.go
index d3e5d6a..f4a1318 100644
--- a/sasl_response.go
+++ b/sasl_response.go
@@ -1,11 +1,9 @@
 package pgproto3
 
 import (
 	"encoding/json"
-
-	"github.com/jackc/pgio"
 )
 
 type SASLResponse struct {
 	Data []byte
 }
@@ -19,17 +17,14 @@ func (dst *SASLResponse) Decode(src []byte) error {
 	*dst = SASLResponse{Data: src}
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *SASLResponse) Encode(dst []byte) []byte {
-	dst = append(dst, 'p')
-	dst = pgio.AppendInt32(dst, int32(4+len(src.Data)))
-
+func (src *SASLResponse) Encode(dst []byte) ([]byte, error) {
+	dst, sp := beginMessage(dst, 'p')
 	dst = append(dst, src.Data...)
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src SASLResponse) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/ssl_request.go b/ssl_request.go
index 96ce489..8feff1a 100644
--- a/ssl_request.go
+++ b/ssl_request.go
@@ -29,14 +29,14 @@ func (dst *SSLRequest) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 4 byte message length.
-func (src *SSLRequest) Encode(dst []byte) []byte {
+func (src *SSLRequest) Encode(dst []byte) ([]byte, error) {
 	dst = pgio.AppendInt32(dst, 8)
 	dst = pgio.AppendInt32(dst, sslRequestNumber)
-	return dst
+	return dst, nil
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src SSLRequest) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/startup_message.go b/startup_message.go
index 5f1cd24..255ea22 100644
--- a/startup_message.go
+++ b/startup_message.go
@@ -62,11 +62,11 @@ func (dst *StartupMessage) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *StartupMessage) Encode(dst []byte) []byte {
+func (src *StartupMessage) Encode(dst []byte) ([]byte, error) {
 	sp := len(dst)
 	dst = pgio.AppendInt32(dst, -1)
 
 	dst = pgio.AppendUint32(dst, src.ProtocolVersion)
 	for k, v := range src.Parameters {
@@ -75,13 +75,11 @@ func (src *StartupMessage) Encode(dst []byte) []byte {
 		dst = append(dst, v...)
 		dst = append(dst, 0)
 	}
 	dst = append(dst, 0)
 
-	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
-
-	return dst
+	return finishMessage(dst, sp)
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src StartupMessage) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/sync.go b/sync.go
index 5db8e07..ea4fc95 100644
--- a/sync.go
+++ b/sync.go
@@ -18,12 +18,12 @@ func (dst *Sync) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *Sync) Encode(dst []byte) []byte {
-	return append(dst, 'S', 0, 0, 0, 4)
+func (src *Sync) Encode(dst []byte) ([]byte, error) {
+	return append(dst, 'S', 0, 0, 0, 4), nil
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src Sync) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
diff --git a/terminate.go b/terminate.go
index 135191e..35a9dc8 100644
--- a/terminate.go
+++ b/terminate.go
@@ -18,12 +18,12 @@ func (dst *Terminate) Decode(src []byte) error {
 
 	return nil
 }
 
 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
-func (src *Terminate) Encode(dst []byte) []byte {
-	return append(dst, 'X', 0, 0, 0, 4)
+func (src *Terminate) Encode(dst []byte) ([]byte, error) {
+	return append(dst, 'X', 0, 0, 0, 4), nil
 }
 
 // MarshalJSON implements encoding/json.Marshaler.
 func (src Terminate) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {

Vulnerable code: func (src *AuthenticationCleartextPassword) Encode(dst []byte) []byte {
	dst = append(dst, 'R')
	dst = pgio.AppendInt32(dst, 8)
	return dst
func (a *AuthenticationGSS) Encode(dst []byte) []byte {
	dst = append(dst, 'R')
	dst = pgio.AppendInt32(dst, 4)
	return dst
func (a *AuthenticationGSSContinue) Encode(dst []byte) []byte {
	dst = append(dst, 'R')
	dst = pgio.AppendInt32(dst, int32(len(a.Data))+8)
	return dst
func (src *AuthenticationMD5Password) Encode(dst []byte) []byte {
	dst = append(dst, 'R')
	dst = pgio.AppendInt32(dst, 12)
	return dst
func (src *AuthenticationOk) Encode(dst []byte) []byte {
	dst = append(dst, 'R')
	dst = pgio.AppendInt32(dst, 8)
	return dst
func (src *AuthenticationSASL) Encode(dst []byte) []byte {
	dst = append(dst, 'R')
	sp := len(dst)
	dst = pgio.AppendInt32(dst, -1)
	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))

	return dst
func (src *AuthenticationSASLContinue) Encode(dst []byte) []byte {
	dst = append(dst, 'R')
	sp := len(dst)
	dst = pgio.AppendInt32(dst, -1)


	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))

	return dst
func (src *AuthenticationSASLFinal) Encode(dst []byte) []byte {
	dst = append(dst, 'R')
	sp := len(dst)
	dst = pgio.AppendInt32(dst, -1)


	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))

	return dst
	_, err := b.w.Write(msg.Encode(nil))
// 		Byte1('p')
//      Identifies the message as a password response. Note that this is also used for
//		GSSAPI, SSPI and SASL response messages. The exact message type can be deduced from
//		the context.
func (src *BackendKeyData) Encode(dst []byte) []byte {
	dst = append(dst, 'K')
	dst = pgio.AppendUint32(dst, 12)
	return dst
		dst := []byte{}
		dst = want.Encode(dst)
func (src *Bind) Encode(dst []byte) []byte {
	dst = append(dst, 'B')
	sp := len(dst)
	dst = pgio.AppendInt32(dst, -1)
	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))

	return dst
func (src *BindComplete) Encode(dst []byte) []byte {
	return append(dst, '2', 0, 0, 0, 4)
func (src *CancelRequest) Encode(dst []byte) []byte {
	return dst

	"github.com/jackc/pgio"
func (src *Close) Encode(dst []byte) []byte {
	dst = append(dst, 'C')
	sp := len(dst)
	dst = pgio.AppendInt32(dst, -1)


	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))

	return dst
func (src *CloseComplete) Encode(dst []byte) []byte {
	return append(dst, '3', 0, 0, 0, 4)

	"github.com/jackc/pgio"
func (src *CommandComplete) Encode(dst []byte) []byte {
	dst = append(dst, 'C')
	sp := len(dst)
	dst = pgio.AppendInt32(dst, -1)


	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))

	return dst
func (src *CopyBothResponse) Encode(dst []byte) []byte {
	dst = append(dst, 'W')
	sp := len(dst)
	dst = pgio.AppendInt32(dst, -1)
	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))

	return dst
	dstBytes = dstResp.Encode(dstBytes)

	"github.com/jackc/pgio"
func (src *CopyData) Encode(dst []byte) []byte {
	dst = append(dst, 'd')
	dst = pgio.AppendInt32(dst, int32(4+len(src.Data)))
	return dst
func (src *CopyDone) Encode(dst []byte) []byte {
	return append(dst, 'c', 0, 0, 0, 4)

	"github.com/jackc/pgio"
func (src *CopyFail) Encode(dst []byte) []byte {
	dst = append(dst, 'f')
	sp := len(dst)
	dst = pgio.AppendInt32(dst, -1)


	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))

	return dst
func (src *CopyInResponse) Encode(dst []byte) []byte {
	dst = append(dst, 'G')
	sp := len(dst)
	dst = pgio.AppendInt32(dst, -1)
	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))

	return dst
func (src *CopyOutResponse) Encode(dst []byte) []byte {
	dst = append(dst, 'H')
	sp := len(dst)
	dst = pgio.AppendInt32(dst, -1)
	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))

	return dst
func (src *DataRow) Encode(dst []byte) []byte {
	dst = append(dst, 'D')
	sp := len(dst)
	dst = pgio.AppendInt32(dst, -1)
	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))

	return dst

	"github.com/jackc/pgio"
func (src *Describe) Encode(dst []byte) []byte {
	dst = append(dst, 'D')
	sp := len(dst)
	dst = pgio.AppendInt32(dst, -1)


	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))

	return dst
func (src *EmptyQueryResponse) Encode(dst []byte) []byte {
	return append(dst, 'I', 0, 0, 0, 4)
	"encoding/binary"
func (src *ErrorResponse) Encode(dst []byte) []byte {
	return append(dst, src.marshalBinary('E')...)
func (src *ErrorResponse) marshalBinary(typeByte byte) []byte {
	var bigEndian BigEndianBuf
	buf := &bytes.Buffer{}

	buf.WriteByte(typeByte)
	buf.Write(bigEndian.Uint32(0))

		buf.WriteByte('S')
		buf.WriteString(src.Severity)
		buf.WriteByte(0)
		buf.WriteByte('V')
		buf.WriteString(src.SeverityUnlocalized)
		buf.WriteByte(0)
		buf.WriteByte('C')
		buf.WriteString(src.Code)
		buf.WriteByte(0)
		buf.WriteByte('M')
		buf.WriteString(src.Message)
		buf.WriteByte(0)
		buf.WriteByte('D')
		buf.WriteString(src.Detail)
		buf.WriteByte(0)
		buf.WriteByte('H')
		buf.WriteString(src.Hint)
		buf.WriteByte(0)
		buf.WriteByte('P')
		buf.WriteString(strconv.Itoa(int(src.Position)))
		buf.WriteByte(0)
		buf.WriteByte('p')
		buf.WriteString(strconv.Itoa(int(src.InternalPosition)))
		buf.WriteByte(0)
		buf.WriteByte('q')
		buf.WriteString(src.InternalQuery)
		buf.WriteByte(0)
		buf.WriteByte('W')
		buf.WriteString(src.Where)
		buf.WriteByte(0)
		buf.WriteByte('s')
		buf.WriteString(src.SchemaName)
		buf.WriteByte(0)
		buf.WriteByte('t')
		buf.WriteString(src.TableName)
		buf.WriteByte(0)
		buf.WriteByte('c')
		buf.WriteString(src.ColumnName)
		buf.WriteByte(0)
		buf.WriteByte('d')
		buf.WriteString(src.DataTypeName)
		buf.WriteByte(0)
		buf.WriteByte('n')
		buf.WriteString(src.ConstraintName)
		buf.WriteByte(0)
		buf.WriteByte('F')
		buf.WriteString(src.File)
		buf.WriteByte(0)
		buf.WriteByte('L')
		buf.WriteString(strconv.Itoa(int(src.Line)))
		buf.WriteByte(0)
		buf.WriteByte('R')
		buf.WriteString(src.Routine)
		buf.WriteByte(0)
		buf.WriteByte(k)
		buf.WriteByte(0)
		buf.WriteString(v)
		buf.WriteByte(0)
	buf.WriteByte(0)

	binary.BigEndian.PutUint32(buf.Bytes()[1:5], uint32(buf.Len()-1))
	return buf.Bytes()
			buf := (&pgproto3.RowDescription{Fields: []pgproto3.FieldDescription{
			}}).Encode(nil)
			buf = (&pgproto3.DataRow{Values: [][]byte{response}}).Encode(buf)
			buf = (&pgproto3.CommandComplete{CommandTag: []byte("SELECT 1")}).Encode(buf)
			buf = (&pgproto3.ReadyForQuery{TxStatus: 'I'}).Encode(buf)
		buf := (&pgproto3.AuthenticationOk{}).Encode(nil)
		buf = (&pgproto3.ReadyForQuery{TxStatus: 'I'}).Encode(buf)
func (src *Execute) Encode(dst []byte) []byte {
	dst = append(dst, 'E')
	sp := len(dst)
	dst = pgio.AppendInt32(dst, -1)



	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))

	return dst
func (src *Flush) Encode(dst []byte) []byte {
	return append(dst, 'H', 0, 0, 0, 4)
	_, err := f.w.Write(msg.Encode(nil))
func (src *FunctionCall) Encode(dst []byte) []byte {
	dst = append(dst, 'F')
	sp := len(dst)
	dst = pgio.AppendUint32(dst, 0) // Unknown length, set it at the end
	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
	return dst
func (src *FunctionCallResponse) Encode(dst []byte) []byte {
	dst = append(dst, 'V')
	sp := len(dst)
	dst = pgio.AppendInt32(dst, -1)
	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))

	return dst
			encoded := src.Encode([]byte{})
			err := dst.Decode(encoded[5:])
func (src *GSSEncRequest) Encode(dst []byte) []byte {
	return dst
	"github.com/jackc/pgio"
func (g *GSSResponse) Encode(dst []byte) []byte {
	dst = append(dst, 'p')
	dst = pgio.AppendInt32(dst, int32(4+len(g.Data)))
	return dst
func (src *NoData) Encode(dst []byte) []byte {
	return append(dst, 'n', 0, 0, 0, 4)
func (src *NoticeResponse) Encode(dst []byte) []byte {
	return append(dst, (*ErrorResponse)(src).marshalBinary('N')...)
func (src *NotificationResponse) Encode(dst []byte) []byte {
	dst = append(dst, 'A')
	sp := len(dst)
	dst = pgio.AppendInt32(dst, -1)


	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))

	return dst
func (src *ParameterDescription) Encode(dst []byte) []byte {
	dst = append(dst, 't')
	sp := len(dst)
	dst = pgio.AppendInt32(dst, -1)
	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))

	return dst

	"github.com/jackc/pgio"
func (src *ParameterStatus) Encode(dst []byte) []byte {
	dst = append(dst, 'S')
	sp := len(dst)
	dst = pgio.AppendInt32(dst, -1)


	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))

	return dst
func (src *Parse) Encode(dst []byte) []byte {
	dst = append(dst, 'P')
	sp := len(dst)
	dst = pgio.AppendInt32(dst, -1)
	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))

	return dst
func (src *ParseComplete) Encode(dst []byte) []byte {
	return append(dst, '1', 0, 0, 0, 4)

	"github.com/jackc/pgio"
func (src *PasswordMessage) Encode(dst []byte) []byte {
	dst = append(dst, 'p')
	dst = pgio.AppendInt32(dst, int32(4+len(src.Password)+1))


	return dst
	Encode(dst []byte) []byte
func (src *PortalSuspended) Encode(dst []byte) []byte {
	return append(dst, 's', 0, 0, 0, 4)

	"github.com/jackc/pgio"
func (src *Query) Encode(dst []byte) []byte {
	dst = append(dst, 'Q')
	dst = pgio.AppendInt32(dst, int32(4+len(src.String)+1))


	return dst
func (src *ReadyForQuery) Encode(dst []byte) []byte {
	return append(dst, 'Z', 0, 0, 0, 5, src.TxStatus)
func (src *RowDescription) Encode(dst []byte) []byte {
	dst = append(dst, 'T')
	sp := len(dst)
	dst = pgio.AppendInt32(dst, -1)
	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))

	return dst
func (src *SASLInitialResponse) Encode(dst []byte) []byte {
	dst = append(dst, 'p')
	sp := len(dst)
	dst = pgio.AppendInt32(dst, -1)
	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))

	return dst

	"github.com/jackc/pgio"
func (src *SASLResponse) Encode(dst []byte) []byte {
	dst = append(dst, 'p')
	dst = pgio.AppendInt32(dst, int32(4+len(src.Data)))


	return dst
func (src *SSLRequest) Encode(dst []byte) []byte {
	return dst
func (src *StartupMessage) Encode(dst []byte) []byte {
	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))

	return dst
func (src *Sync) Encode(dst []byte) []byte {
	return append(dst, 'S', 0, 0, 0, 4)
func (src *Terminate) Encode(dst []byte) []byte {
	return append(dst, 'X', 0, 0, 0, 4)
Fixed code: func (src *AuthenticationCleartextPassword) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'R')
	return finishMessage(dst, sp)

func (a *AuthenticationGSS) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'R')
	return finishMessage(dst, sp)

func (a *AuthenticationGSSContinue) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'R')
	return finishMessage(dst, sp)
func (src *AuthenticationMD5Password) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'R')
	return finishMessage(dst, sp)
func (src *AuthenticationOk) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'R')
	return finishMessage(dst, sp)
func (src *AuthenticationSASL) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'R')
	return finishMessage(dst, sp)
func (src *AuthenticationSASLContinue) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'R')
	return finishMessage(dst, sp)
func (src *AuthenticationSASLFinal) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'R')
	return finishMessage(dst, sp)
	buf, err := msg.Encode(nil)
	if err != nil {
		return err
	}

	_, err = b.w.Write(buf)
//			Byte1('p')
//	     Identifies the message as a password response. Note that this is also used for
//			GSSAPI, SSPI and SASL response messages. The exact message type can be deduced from
//			the context.
func (src *BackendKeyData) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'K')
	return finishMessage(dst, sp)
		dst, err := want.Encode([]byte{})
		require.NoError(t, err)
	"errors"
	"math"
func (src *Bind) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'B')
	if len(src.ParameterFormatCodes) > math.MaxUint16 {
		return nil, errors.New("too many parameter format codes")
	}
	if len(src.Parameters) > math.MaxUint16 {
		return nil, errors.New("too many parameters")
	}
	if len(src.ResultFormatCodes) > math.MaxUint16 {
		return nil, errors.New("too many result format codes")
	}
	return finishMessage(dst, sp)
func (src *BindComplete) Encode(dst []byte) ([]byte, error) {
	return append(dst, '2', 0, 0, 0, 4), nil
package pgproto3_test

import (
	"testing"

	"github.com/jackc/pgproto3/v2"
	"github.com/stretchr/testify/require"
)

func TestBindBiggerThanMaxMessageBodyLen(t *testing.T) {
	t.Parallel()

	// Maximum allowed size.
	_, err := (&pgproto3.Bind{Parameters: [][]byte{make([]byte, pgproto3.MaxMessageBodyLen-16)}}).Encode(nil)
	require.NoError(t, err)

	// 1 byte too big
	_, err = (&pgproto3.Bind{Parameters: [][]byte{make([]byte, pgproto3.MaxMessageBodyLen-15)}}).Encode(nil)
	require.Error(t, err)
}
func (src *CancelRequest) Encode(dst []byte) ([]byte, error) {
	return dst, nil
func (src *Close) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'C')
	return finishMessage(dst, sp)
func (src *CloseComplete) Encode(dst []byte) ([]byte, error) {
	return append(dst, '3', 0, 0, 0, 4), nil
func (src *CommandComplete) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'C')
	return finishMessage(dst, sp)
	"math"
func (src *CopyBothResponse) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'W')
	if len(src.ColumnFormatCodes) > math.MaxUint16 {
		return nil, errors.New("too many column format codes")
	}
	return finishMessage(dst, sp)
	"github.com/stretchr/testify/require"
	dstBytes, err = dstResp.Encode(dstBytes)
	require.NoError(t, err)
func (src *CopyData) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'd')
	return finishMessage(dst, sp)
func (src *CopyDone) Encode(dst []byte) ([]byte, error) {
	return append(dst, 'c', 0, 0, 0, 4), nil
func (src *CopyFail) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'f')
	return finishMessage(dst, sp)
	"math"
func (src *CopyInResponse) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'G')
	if len(src.ColumnFormatCodes) > math.MaxUint16 {
		return nil, errors.New("too many column format codes")
	}
	return finishMessage(dst, sp)
	"math"
func (src *CopyOutResponse) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'H')
	if len(src.ColumnFormatCodes) > math.MaxUint16 {
		return nil, errors.New("too many column format codes")
	}
	return finishMessage(dst, sp)
	"errors"
	"math"
func (src *DataRow) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'D')
	if len(src.Values) > math.MaxUint16 {
		return nil, errors.New("too many values")
	}
	return finishMessage(dst, sp)
func (src *Describe) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'D')
	return finishMessage(dst, sp)
func (src *EmptyQueryResponse) Encode(dst []byte) ([]byte, error) {
	return append(dst, 'I', 0, 0, 0, 4), nil
func (src *ErrorResponse) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'E')
	dst = src.appendFields(dst)
	return finishMessage(dst, sp)
func (src *ErrorResponse) appendFields(dst []byte) []byte {
		dst = append(dst, 'S')
		dst = append(dst, src.Severity...)
		dst = append(dst, 0)
		dst = append(dst, 'V')
		dst = append(dst, src.SeverityUnlocalized...)
		dst = append(dst, 0)
		dst = append(dst, 'C')
		dst = append(dst, src.Code...)
		dst = append(dst, 0)
		dst = append(dst, 'M')
		dst = append(dst, src.Message...)
		dst = append(dst, 0)
		dst = append(dst, 'D')
		dst = append(dst, src.Detail...)
		dst = append(dst, 0)
		dst = append(dst, 'H')
		dst = append(dst, src.Hint...)
		dst = append(dst, 0)
		dst = append(dst, 'P')
		dst = append(dst, strconv.Itoa(int(src.Position))...)
		dst = append(dst, 0)
		dst = append(dst, 'p')
		dst = append(dst, strconv.Itoa(int(src.InternalPosition))...)
		dst = append(dst, 0)
		dst = append(dst, 'q')
		dst = append(dst, src.InternalQuery...)
		dst = append(dst, 0)
		dst = append(dst, 'W')
		dst = append(dst, src.Where...)
		dst = append(dst, 0)
		dst = append(dst, 's')
		dst = append(dst, src.SchemaName...)
		dst = append(dst, 0)
		dst = append(dst, 't')
		dst = append(dst, src.TableName...)
		dst = append(dst, 0)
		dst = append(dst, 'c')
		dst = append(dst, src.ColumnName...)
		dst = append(dst, 0)
		dst = append(dst, 'd')
		dst = append(dst, src.DataTypeName...)
		dst = append(dst, 0)
		dst = append(dst, 'n')
		dst = append(dst, src.ConstraintName...)
		dst = append(dst, 0)
		dst = append(dst, 'F')
		dst = append(dst, src.File...)
		dst = append(dst, 0)
		dst = append(dst, 'L')
		dst = append(dst, strconv.Itoa(int(src.Line))...)
		dst = append(dst, 0)
		dst = append(dst, 'R')
		dst = append(dst, src.Routine...)
		dst = append(dst, 0)
		dst = append(dst, k)
		dst = append(dst, v...)
		dst = append(dst, 0)
	dst = append(dst, 0)
	return dst
			buf := mustEncode((&pgproto3.RowDescription{Fields: []pgproto3.FieldDescription{
			}}).Encode(nil))
			buf = mustEncode((&pgproto3.DataRow{Values: [][]byte{response}}).Encode(buf))
			buf = mustEncode((&pgproto3.CommandComplete{CommandTag: []byte("SELECT 1")}).Encode(buf))
			buf = mustEncode((&pgproto3.ReadyForQuery{TxStatus: 'I'}).Encode(buf))
		buf := mustEncode((&pgproto3.AuthenticationOk{}).Encode(nil))
		buf = mustEncode((&pgproto3.ReadyForQuery{TxStatus: 'I'}).Encode(buf))

func mustEncode(buf []byte, err error) []byte {
	if err != nil {
		panic(err)
	}
	return buf
}
func (src *Execute) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'E')
	return finishMessage(dst, sp)
func (src *Flush) Encode(dst []byte) ([]byte, error) {
	return append(dst, 'H', 0, 0, 0, 4), nil
	buf, err := msg.Encode(nil)
	if err != nil {
		return err
	}
	_, err = f.w.Write(buf)
	"errors"
	"math"

func (src *FunctionCall) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'F')

	if len(src.ArgFormatCodes) > math.MaxUint16 {
		return nil, errors.New("too many arg format codes")
	}

	if len(src.Arguments) > math.MaxUint16 {
		return nil, errors.New("too many arguments")
	}
	return finishMessage(dst, sp)
func (src *FunctionCallResponse) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'V')
	return finishMessage(dst, sp)

	"github.com/stretchr/testify/require"
			encoded, err := src.Encode([]byte{})
			require.NoError(t, err)
			err = dst.Decode(encoded[5:])
func (src *GSSEncRequest) Encode(dst []byte) ([]byte, error) {
	return dst, nil
func (g *GSSResponse) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'p')
	return finishMessage(dst, sp)
func (src *NoData) Encode(dst []byte) ([]byte, error) {
	return append(dst, 'n', 0, 0, 0, 4), nil
func (src *NoticeResponse) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'N')
	dst = (*ErrorResponse)(src).appendFields(dst)
	return finishMessage(dst, sp)
func (src *NotificationResponse) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'A')
	return finishMessage(dst, sp)
	"errors"
	"math"
func (src *ParameterDescription) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 't')
	if len(src.ParameterOIDs) > math.MaxUint16 {
		return nil, errors.New("too many parameter oids")
	}
	return finishMessage(dst, sp)
func (src *ParameterStatus) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'S')
	return finishMessage(dst, sp)
	"errors"
	"math"
func (src *Parse) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'P')
	if len(src.ParameterOIDs) > math.MaxUint16 {
		return nil, errors.New("too many parameter oids")
	}
	return finishMessage(dst, sp)
func (src *ParseComplete) Encode(dst []byte) ([]byte, error) {
	return append(dst, '1', 0, 0, 0, 4), nil
func (src *PasswordMessage) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'p')
	return finishMessage(dst, sp)

	"github.com/jackc/pgio"
// maxMessageBodyLen is the maximum length of a message body in bytes. See PG_LARGE_MESSAGE_LIMIT in the PostgreSQL
// source. It is defined as (MaxAllocSize - 1). MaxAllocSize is defined as 0x3fffffff.
const maxMessageBodyLen = (0x3fffffff - 1)

	Encode(dst []byte) ([]byte, error)

// beginMessage begines a new message of type t. It appends the message type and a placeholder for the message length to
// dst. It returns the new buffer and the position of the message length placeholder.
func beginMessage(dst []byte, t byte) ([]byte, int) {
	dst = append(dst, t)
	sp := len(dst)
	dst = pgio.AppendInt32(dst, -1)
	return dst, sp
}

// finishMessage finishes a message that was started with beginMessage. It computes the message length and writes it to
// dst[sp]. If the message length is too large it returns an error. Otherwise it returns the final message buffer.
func finishMessage(dst []byte, sp int) ([]byte, error) {
	messageBodyLen := len(dst[sp:])
	if messageBodyLen > maxMessageBodyLen {
		return nil, errors.New("message body too large")
	}
	pgio.SetInt32(dst[sp:], int32(messageBodyLen))
	return dst, nil
}
package pgproto3

const MaxMessageBodyLen = maxMessageBodyLen
func (src *PortalSuspended) Encode(dst []byte) ([]byte, error) {
	return append(dst, 's', 0, 0, 0, 4), nil
func (src *Query) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'Q')
	return finishMessage(dst, sp)
package pgproto3_test

import (
	"testing"

	"github.com/jackc/pgproto3/v2"
	"github.com/stretchr/testify/require"
)

func TestQueryBiggerThanMaxMessageBodyLen(t *testing.T) {
	t.Parallel()

	// Maximum allowed size. 4 bytes for size and 1 byte for 0 terminated string.
	_, err := (&pgproto3.Query{String: string(make([]byte, pgproto3.MaxMessageBodyLen-5))}).Encode(nil)
	require.NoError(t, err)

	// 1 byte too big
	_, err = (&pgproto3.Query{String: string(make([]byte, pgproto3.MaxMessageBodyLen-4))}).Encode(nil)
	require.Error(t, err)
}
func (src *ReadyForQuery) Encode(dst []byte) ([]byte, error) {
	return append(dst, 'Z', 0, 0, 0, 5, src.TxStatus), nil
	"errors"
	"math"
func (src *RowDescription) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'T')
	if len(src.Fields) > math.MaxUint16 {
		return nil, errors.New("too many fields")
	}
	return finishMessage(dst, sp)
func (src *SASLInitialResponse) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'p')
	return finishMessage(dst, sp)
func (src *SASLResponse) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'p')
	return finishMessage(dst, sp)
func (src *SSLRequest) Encode(dst []byte) ([]byte, error) {
	return dst, nil
func (src *StartupMessage) Encode(dst []byte) ([]byte, error) {
	return finishMessage(dst, sp)
func (src *Sync) Encode(dst []byte) ([]byte, error) {
	return append(dst, 'S', 0, 0, 0, 4), nil
func (src *Terminate) Encode(dst []byte) ([]byte, error) {
	return append(dst, 'X', 0, 0, 0, 4), nil