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/pgx
Language: Go
Files changed: pgconn/pgconn.go, pgconn/pgconn_test.go, pgproto3/authentication_cleartext_password.go, pgproto3/authentication_gss.go, pgproto3/authentication_gss_continue.go, pgproto3/authentication_md5_password.go, pgproto3/authentication_ok.go, pgproto3/authentication_sasl.go, pgproto3/authentication_sasl_continue.go, pgproto3/authentication_sasl_final.go, pgproto3/backend.go, pgproto3/backend_key_data.go, pgproto3/backend_test.go, pgproto3/bind.go, pgproto3/bind_complete.go, pgproto3/bind_test.go, pgproto3/cancel_request.go, pgproto3/close.go, pgproto3/close_complete.go, pgproto3/command_complete.go, pgproto3/copy_both_response.go, pgproto3/copy_both_response_test.go, pgproto3/copy_data.go, pgproto3/copy_done.go, pgproto3/copy_fail.go, pgproto3/copy_in_response.go, pgproto3/copy_out_response.go, pgproto3/data_row.go, pgproto3/describe.go, pgproto3/empty_query_response.go, pgproto3/error_response.go, pgproto3/example/pgfortune/server.go, pgproto3/execute.go, pgproto3/flush.go, pgproto3/frontend.go, pgproto3/function_call.go, pgproto3/function_call_response.go, pgproto3/function_call_test.go, pgproto3/gss_enc_request.go, pgproto3/gss_response.go, pgproto3/no_data.go, pgproto3/notice_response.go, pgproto3/notification_response.go, pgproto3/parameter_description.go, pgproto3/parameter_status.go, pgproto3/parse.go, pgproto3/parse_complete.go, pgproto3/password_message.go, pgproto3/pgproto3.go, pgproto3/pgproto3_private_test.go, pgproto3/portal_suspended.go, pgproto3/query.go, pgproto3/query_test.go, pgproto3/ready_for_query.go, pgproto3/row_description.go, pgproto3/sasl_initial_response.go, pgproto3/sasl_response.go, pgproto3/ssl_request.go, pgproto3/startup_message.go, pgproto3/sync.go, pgproto3/terminate.go
Commit date: 2024-03-04 09:09:29 -0600
Commit message: 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/pgconn/pgconn.go b/pgconn/pgconn.go
index ad81ec60..0bf03f33 100644
--- a/pgconn/pgconn.go
+++ b/pgconn/pgconn.go
@@ -1672,29 +1672,59 @@ func (rr *ResultReader) concludeCommand(commandTag CommandTag, err error) {
 }
 
 // Batch is a collection of queries that can be sent to the PostgreSQL server in a single round-trip.
 type Batch struct {
 	buf []byte
+	err error
 }
 
 // ExecParams appends an ExecParams command to the batch. See PgConn.ExecParams for parameter descriptions.
 func (batch *Batch) ExecParams(sql string, paramValues [][]byte, paramOIDs []uint32, paramFormats []int16, resultFormats []int16) {
-	batch.buf = (&pgproto3.Parse{Query: sql, ParameterOIDs: paramOIDs}).Encode(batch.buf)
+	if batch.err != nil {
+		return
+	}
+
+	batch.buf, batch.err = (&pgproto3.Parse{Query: sql, ParameterOIDs: paramOIDs}).Encode(batch.buf)
+	if batch.err != nil {
+		return
+	}
 	batch.ExecPrepared("", paramValues, paramFormats, resultFormats)
 }
 
 // ExecPrepared appends an ExecPrepared e command to the batch. See PgConn.ExecPrepared for parameter descriptions.
 func (batch *Batch) ExecPrepared(stmtName string, paramValues [][]byte, paramFormats []int16, resultFormats []int16) {
-	batch.buf = (&pgproto3.Bind{PreparedStatement: stmtName, ParameterFormatCodes: paramFormats, Parameters: paramValues, ResultFormatCodes: resultFormats}).Encode(batch.buf)
-	batch.buf = (&pgproto3.Describe{ObjectType: 'P'}).Encode(batch.buf)
-	batch.buf = (&pgproto3.Execute{}).Encode(batch.buf)
+	if batch.err != nil {
+		return
+	}
+
+	batch.buf, batch.err = (&pgproto3.Bind{PreparedStatement: stmtName, ParameterFormatCodes: paramFormats, Parameters: paramValues, ResultFormatCodes: resultFormats}).Encode(batch.buf)
+	if batch.err != nil {
+		return
+	}
+
+	batch.buf, batch.err = (&pgproto3.Describe{ObjectType: 'P'}).Encode(batch.buf)
+	if batch.err != nil {
+		return
+	}
+
+	batch.buf, batch.err = (&pgproto3.Execute{}).Encode(batch.buf)
+	if batch.err != nil {
+		return
+	}
 }
 
 // ExecBatch executes all the queries in batch in a single round-trip. Execution is implicitly transactional unless a
 // transaction is already in progress or SQL contains transaction control statements. This is a simpler way of executing
 // multiple queries in a single round trip than using pipeline mode.
 func (pgConn *PgConn) ExecBatch(ctx context.Context, batch *Batch) *MultiResultReader {
+	if batch.err != nil {
+		return &MultiResultReader{
+			closed: true,
+			err:    batch.err,
+		}
+	}
+
 	if err := pgConn.lock(); err != nil {
 		return &MultiResultReader{
 			closed: true,
 			err:    err,
 		}
@@ -1716,11 +1746,17 @@ func (pgConn *PgConn) ExecBatch(ctx context.Context, batch *Batch) *MultiResultR
 		default:
 		}
 		pgConn.contextWatcher.Watch(ctx)
 	}
 
-	batch.buf = (&pgproto3.Sync{}).Encode(batch.buf)
+	batch.buf, batch.err = (&pgproto3.Sync{}).Encode(batch.buf)
+	if batch.err != nil {
+		multiResult.closed = true
+		multiResult.err = batch.err
+		pgConn.unlock()
+		return multiResult
+	}
 
 	pgConn.enterPotentialWriteReadDeadlock()
 	defer pgConn.exitPotentialWriteReadDeadlock()
 	_, err := pgConn.conn.Write(batch.buf)
 	if err != nil {
diff --git a/pgconn/pgconn_test.go b/pgconn/pgconn_test.go
index f04fa79a..b77d21c1 100644
--- a/pgconn/pgconn_test.go
+++ b/pgconn/pgconn_test.go
@@ -3361,13 +3361,13 @@ func TestSNISupport(t *testing.T) {
 				if err := srv.Handshake(); err != nil {
 					serverErrChan <- fmt.Errorf("handshake: %w", err)
 					return
 				}
 
-				srv.Write((&pgproto3.AuthenticationOk{}).Encode(nil))
-				srv.Write((&pgproto3.BackendKeyData{ProcessID: 0, SecretKey: 0}).Encode(nil))
-				srv.Write((&pgproto3.ReadyForQuery{TxStatus: 'I'}).Encode(nil))
+				srv.Write(mustEncode((&pgproto3.AuthenticationOk{}).Encode(nil)))
+				srv.Write(mustEncode((&pgproto3.BackendKeyData{ProcessID: 0, SecretKey: 0}).Encode(nil)))
+				srv.Write(mustEncode((&pgproto3.ReadyForQuery{TxStatus: 'I'}).Encode(nil)))
 
 				serverSNINameChan <- sniHost
 			}()
 
 			_, port, _ := strings.Cut(ln.Addr().String(), ":")
@@ -3470,5 +3470,12 @@ func TestFatalErrorReceivedInPipelineMode(t *testing.T) {
 	require.Error(t, err)
 
 	err = pipeline.Close()
 	require.Error(t, err)
 }
+
+func mustEncode(buf []byte, err error) []byte {
+	if err != nil {
+		panic(err)
+	}
+	return buf
+}
diff --git a/pgproto3/authentication_cleartext_password.go b/pgproto3/authentication_cleartext_password.go
index d8f98b9a..ac2962e9 100644
--- a/pgproto3/authentication_cleartext_password.go
+++ b/pgproto3/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/pgproto3/authentication_gss.go b/pgproto3/authentication_gss.go
index 0d234222..178ef31d 100644
--- a/pgproto3/authentication_gss.go
+++ b/pgproto3/authentication_gss.go
@@ -25,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/pgproto3/authentication_gss_continue.go b/pgproto3/authentication_gss_continue.go
index 63789dc1..2ba3f3b3 100644
--- a/pgproto3/authentication_gss_continue.go
+++ b/pgproto3/authentication_gss_continue.go
@@ -29,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/pgproto3/authentication_md5_password.go b/pgproto3/authentication_md5_password.go
index 5671c84c..854c6404 100644
--- a/pgproto3/authentication_md5_password.go
+++ b/pgproto3/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/pgproto3/authentication_ok.go b/pgproto3/authentication_ok.go
index 88d648ae..ec11d39f 100644
--- a/pgproto3/authentication_ok.go
+++ b/pgproto3/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/pgproto3/authentication_sasl.go b/pgproto3/authentication_sasl.go
index 59650d4c..e66580f4 100644
--- a/pgproto3/authentication_sasl.go
+++ b/pgproto3/authentication_sasl.go
@@ -45,25 +45,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/pgproto3/authentication_sasl_continue.go b/pgproto3/authentication_sasl_continue.go
index 2ce70a47..70fba4a6 100644
--- a/pgproto3/authentication_sasl_continue.go
+++ b/pgproto3/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/pgproto3/authentication_sasl_final.go b/pgproto3/authentication_sasl_final.go
index a38a8b91..84976c2a 100644
--- a/pgproto3/authentication_sasl_final.go
+++ b/pgproto3/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/pgproto3/backend.go b/pgproto3/backend.go
index efa909c3..d146c338 100644
--- a/pgproto3/backend.go
+++ b/pgproto3/backend.go
@@ -14,11 +14,12 @@ type Backend struct {
 
 	// tracer is used to trace messages when Send or Receive is called. This means an outbound message is traced
 	// before it is actually transmitted (i.e. before Flush).
 	tracer *tracer
 
-	wbuf []byte
+	wbuf        []byte
+	encodeError error
 
 	// Frontend message flyweights
 	bind           Bind
 	cancelRequest  CancelRequest
 	_close         Close
@@ -53,22 +54,38 @@ const (
 func NewBackend(r io.Reader, w io.Writer) *Backend {
 	cr := newChunkReader(r, 0)
 	return &Backend{cr: cr, w: w}
 }
 
-// Send sends a message to the frontend (i.e. the client). The message is not guaranteed to be written until Flush is
-// called.
+// Send sends a message to the frontend (i.e. the client). The message is buffered until Flush is called. Any error
+// encountered will be returned from Flush.
 func (b *Backend) Send(msg BackendMessage) {
+	if b.encodeError != nil {
+		return
+	}
+
 	prevLen := len(b.wbuf)
-	b.wbuf = msg.Encode(b.wbuf)
+	newBuf, err := msg.Encode(b.wbuf)
+	if err != nil {
+		b.encodeError = err
+		return
+	}
+	b.wbuf = newBuf
+
 	if b.tracer != nil {
 		b.tracer.traceMessage('B', int32(len(b.wbuf)-prevLen), msg)
 	}
 }
 
 // Flush writes any pending messages to the frontend (i.e. the client).
 func (b *Backend) Flush() error {
+	if err := b.encodeError; err != nil {
+		b.encodeError = nil
+		b.wbuf = b.wbuf[:0]
+		return &writeError{err: err, safeToRetry: true}
+	}
+
 	n, err := b.w.Write(b.wbuf)
 
 	const maxLen = 1024
 	if len(b.wbuf) > maxLen {
 		b.wbuf = make([]byte, 0, maxLen)
diff --git a/pgproto3/backend_key_data.go b/pgproto3/backend_key_data.go
index 12c60817..23f5da67 100644
--- a/pgproto3/backend_key_data.go
+++ b/pgproto3/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/pgproto3/backend_test.go b/pgproto3/backend_test.go
index 5655122a..5107ef76 100644
--- a/pgproto3/backend_test.go
+++ b/pgproto3/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(server, nil)
diff --git a/pgproto3/bind.go b/pgproto3/bind.go
index fdd2d3b8..b32cd81c 100644
--- a/pgproto3/bind.go
+++ b/pgproto3/bind.go
@@ -106,14 +106,12 @@ 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)
@@ -137,13 +135,11 @@ func (src *Bind) Encode(dst []byte) []byte {
 	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/pgproto3/bind_complete.go b/pgproto3/bind_complete.go
index 3be256c8..bacf30d8 100644
--- a/pgproto3/bind_complete.go
+++ b/pgproto3/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/pgproto3/bind_test.go b/pgproto3/bind_test.go
new file mode 100644
index 00000000..6ec0e024
--- /dev/null
+++ b/pgproto3/bind_test.go
@@ -0,0 +1,20 @@
+package pgproto3_test
+
+import (
+	"testing"
+
+	"github.com/jackc/pgx/v5/pgproto3"
+	"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/pgproto3/cancel_request.go b/pgproto3/cancel_request.go
index 8fcf8217..6b52dd97 100644
--- a/pgproto3/cancel_request.go
+++ b/pgproto3/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/pgproto3/close.go b/pgproto3/close.go
index f99b5943..0b50f27c 100644
--- a/pgproto3/close.go
+++ b/pgproto3/close.go
@@ -2,12 +2,10 @@ package pgproto3
 
 import (
 	"bytes"
 	"encoding/json"
 	"errors"
-
-	"github.com/jackc/pgx/v5/internal/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/pgproto3/close_complete.go b/pgproto3/close_complete.go
index 1d7b8f08..833f7a12 100644
--- a/pgproto3/close_complete.go
+++ b/pgproto3/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/pgproto3/command_complete.go b/pgproto3/command_complete.go
index 814027ca..eba70947 100644
--- a/pgproto3/command_complete.go
+++ b/pgproto3/command_complete.go
@@ -1,12 +1,10 @@
 package pgproto3
 
 import (
 	"bytes"
 	"encoding/json"
-
-	"github.com/jackc/pgx/v5/internal/pgio"
 )
 
 type CommandComplete struct {
 	CommandTag []byte
 }
@@ -29,21 +27,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/pgproto3/copy_both_response.go b/pgproto3/copy_both_response.go
index 8840a89e..dbbd8e15 100644
--- a/pgproto3/copy_both_response.go
+++ b/pgproto3/copy_both_response.go
@@ -42,23 +42,19 @@ 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)
 	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/pgproto3/copy_both_response_test.go b/pgproto3/copy_both_response_test.go
index 4437de1d..1c988f21 100644
--- a/pgproto3/copy_both_response_test.go
+++ b/pgproto3/copy_both_response_test.go
@@ -3,16 +3,18 @@ package pgproto3_test
 import (
 	"testing"
 
 	"github.com/jackc/pgx/v5/pgproto3"
 	"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/pgproto3/copy_data.go b/pgproto3/copy_data.go
index 59e3dd94..89ecdd4d 100644
--- a/pgproto3/copy_data.go
+++ b/pgproto3/copy_data.go
@@ -1,12 +1,10 @@
 package pgproto3
 
 import (
 	"encoding/hex"
 	"encoding/json"
-
-	"github.com/jackc/pgx/v5/internal/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/pgproto3/copy_done.go b/pgproto3/copy_done.go
index 0e13282b..040814db 100644
--- a/pgproto3/copy_done.go
+++ b/pgproto3/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/pgproto3/copy_fail.go b/pgproto3/copy_fail.go
index 0041bbb1..72a85fd0 100644
--- a/pgproto3/copy_fail.go
+++ b/pgproto3/copy_fail.go
@@ -1,12 +1,10 @@
 package pgproto3
 
 import (
 	"bytes"
 	"encoding/json"
-
-	"github.com/jackc/pgx/v5/internal/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/pgproto3/copy_in_response.go b/pgproto3/copy_in_response.go
index 4584f7df..0a772afa 100644
--- a/pgproto3/copy_in_response.go
+++ b/pgproto3/copy_in_response.go
@@ -42,24 +42,20 @@ 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)
 	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/pgproto3/copy_out_response.go b/pgproto3/copy_out_response.go
index 3175c6a4..40525da6 100644
--- a/pgproto3/copy_out_response.go
+++ b/pgproto3/copy_out_response.go
@@ -41,25 +41,21 @@ 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)
 
 	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/pgproto3/data_row.go b/pgproto3/data_row.go
index 4de77977..cbc76dc2 100644
--- a/pgproto3/data_row.go
+++ b/pgproto3/data_row.go
@@ -61,14 +61,12 @@ 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')
 
 	dst = pgio.AppendUint16(dst, uint16(len(src.Values)))
 	for _, v := range src.Values {
 		if v == nil {
 			dst = pgio.AppendInt32(dst, -1)
@@ -77,13 +75,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/pgproto3/describe.go b/pgproto3/describe.go
index f131d1f4..89feff21 100644
--- a/pgproto3/describe.go
+++ b/pgproto3/describe.go
@@ -2,12 +2,10 @@ package pgproto3
 
 import (
 	"bytes"
 	"encoding/json"
 	"errors"
-
-	"github.com/jackc/pgx/v5/internal/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/pgproto3/empty_query_response.go b/pgproto3/empty_query_response.go
index 2b85e744..cb6cca07 100644
--- a/pgproto3/empty_query_response.go
+++ b/pgproto3/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/pgproto3/error_response.go b/pgproto3/error_response.go
index 45c9a981..6ef9bd06 100644
--- a/pgproto3/error_response.go
+++ b/pgproto3/error_response.go
@@ -1,10 +1,9 @@
 package pgproto3
 
 import (
 	"bytes"
-	"encoding/binary"
 	"encoding/json"
 	"strconv"
 )
 
 type ErrorResponse struct {
@@ -109,123 +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.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/pgproto3/example/pgfortune/server.go b/pgproto3/example/pgfortune/server.go
index 14ae71f8..06a45dda 100644
--- a/pgproto3/example/pgfortune/server.go
+++ b/pgproto3/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/pgproto3/execute.go b/pgproto3/execute.go
index a5fee7cb..31bc714d 100644
--- a/pgproto3/execute.go
+++ b/pgproto3/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/pgproto3/flush.go b/pgproto3/flush.go
index 2725f689..e5dc1fbb 100644
--- a/pgproto3/flush.go
+++ b/pgproto3/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/pgproto3/frontend.go b/pgproto3/frontend.go
index 60c34ef0..b41abbe1 100644
--- a/pgproto3/frontend.go
+++ b/pgproto3/frontend.go
@@ -16,11 +16,12 @@ type Frontend struct {
 	// tracer is used to trace messages when Send or Receive is called. This means an outbound message is traced
 	// before it is actually transmitted (i.e. before Flush). It is safe to change this variable when the Frontend is
 	// idle. Setting and unsetting tracer provides equivalent functionality to PQtrace and PQuntrace in libpq.
 	tracer *tracer
 
-	wbuf []byte
+	wbuf        []byte
+	encodeError error
 
 	// Backend message flyweights
 	authenticationOk                AuthenticationOk
 	authenticationCleartextPassword AuthenticationCleartextPassword
 	authenticationMD5Password       AuthenticationMD5Password
@@ -62,27 +63,43 @@ type Frontend struct {
 func NewFrontend(r io.Reader, w io.Writer) *Frontend {
 	cr := newChunkReader(r, 0)
 	return &Frontend{cr: cr, w: w}
 }
 
-// Send sends a message to the backend (i.e. the server). The message is not guaranteed to be written until Flush is
-// called.
+// Send sends a message to the backend (i.e. the server). The message is buffered until Flush is called. Any error
+// encountered will be returned from Flush.
 //
 // Send can work with any FrontendMessage. Some commonly used message types such as Bind have specialized send methods
 // such as SendBind. These methods should be preferred when the type of message is known up front (e.g. when building an
 // extended query protocol query) as they may be faster due to knowing the type of msg rather than it being hidden
 // behind an interface.
 func (f *Frontend) Send(msg FrontendMessage) {
+	if f.encodeError != nil {
+		return
+	}
+
 	prevLen := len(f.wbuf)
-	f.wbuf = msg.Encode(f.wbuf)
+	newBuf, err := msg.Encode(f.wbuf)
+	if err != nil {
+		f.encodeError = err
+		return
+	}
+	f.wbuf = newBuf
+
 	if f.tracer != nil {
 		f.tracer.traceMessage('F', int32(len(f.wbuf)-prevLen), msg)
 	}
 }
 
 // Flush writes any pending messages to the backend (i.e. the server).
 func (f *Frontend) Flush() error {
+	if err := f.encodeError; err != nil {
+		f.encodeError = nil
+		f.wbuf = f.wbuf[:0]
+		return &writeError{err: err, safeToRetry: true}
+	}
+
 	if len(f.wbuf) == 0 {
 		return nil
 	}
 
 	n, err := f.w.Write(f.wbuf)
@@ -114,75 +131,145 @@ func (f *Frontend) Trace(w io.Writer, options TracerOptions) {
 // Untrace stops tracing.
 func (f *Frontend) Untrace() {
 	f.tracer = nil
 }
 
-// SendBind sends a Bind message to the backend (i.e. the server). The message is not guaranteed to be written until
-// Flush is called.
+// SendBind sends a Bind message to the backend (i.e. the server). The message is buffered until Flush is called. Any
+// error encountered will be returned from Flush.
 func (f *Frontend) SendBind(msg *Bind) {
+	if f.encodeError != nil {
+		return
+	}
+
 	prevLen := len(f.wbuf)
-	f.wbuf = msg.Encode(f.wbuf)
+	newBuf, err := msg.Encode(f.wbuf)
+	if err != nil {
+		f.encodeError = err
+		return
+	}
+	f.wbuf = newBuf
+
 	if f.tracer != nil {
 		f.tracer.traceBind('F', int32(len(f.wbuf)-prevLen), msg)
 	}
 }
 
-// SendParse sends a Parse message to the backend (i.e. the server). The message is not guaranteed to be written until
-// Flush is called.
+// SendParse sends a Parse message to the backend (i.e. the server). The message is buffered until Flush is called. Any
+// error encountered will be returned from Flush.
 func (f *Frontend) SendParse(msg *Parse) {
+	if f.encodeError != nil {
+		return
+	}
+
 	prevLen := len(f.wbuf)
-	f.wbuf = msg.Encode(f.wbuf)
+	newBuf, err := msg.Encode(f.wbuf)
+	if err != nil {
+		f.encodeError = err
+		return
+	}
+	f.wbuf = newBuf
+
 	if f.tracer != nil {
 		f.tracer.traceParse('F', int32(len(f.wbuf)-prevLen), msg)
 	}
 }
 
-// SendClose sends a Close message to the backend (i.e. the server). The message is not guaranteed to be written until
-// Flush is called.
+// SendClose sends a Close message to the backend (i.e. the server). The message is buffered until Flush is called. Any
+// error encountered will be returned from Flush.
 func (f *Frontend) SendClose(msg *Close) {
+	if f.encodeError != nil {
+		return
+	}
+
 	prevLen := len(f.wbuf)
-	f.wbuf = msg.Encode(f.wbuf)
+	newBuf, err := msg.Encode(f.wbuf)
+	if err != nil {
+		f.encodeError = err
+		return
+	}
+	f.wbuf = newBuf
+
 	if f.tracer != nil {
 		f.tracer.traceClose('F', int32(len(f.wbuf)-prevLen), msg)
 	}
 }
 
-// SendDescribe sends a Describe message to the backend (i.e. the server). The message is not guaranteed to be written until
-// Flush is called.
+// SendDescribe sends a Describe message to the backend (i.e. the server). The message is buffered until Flush is
+// called. Any error encountered will be returned from Flush.
 func (f *Frontend) SendDescribe(msg *Describe) {
+	if f.encodeError != nil {
+		return
+	}
+
 	prevLen := len(f.wbuf)
-	f.wbuf = msg.Encode(f.wbuf)
+	newBuf, err := msg.Encode(f.wbuf)
+	if err != nil {
+		f.encodeError = err
+		return
+	}
+	f.wbuf = newBuf
+
 	if f.tracer != nil {
 		f.tracer.traceDescribe('F', int32(len(f.wbuf)-prevLen), msg)
 	}
 }
 
-// SendExecute sends an Execute message to the backend (i.e. the server). The message is not guaranteed to be written until
-// Flush is called.
+// SendExecute sends an Execute message to the backend (i.e. the server). The message is buffered until Flush is called.
+// Any error encountered will be returned from Flush.
 func (f *Frontend) SendExecute(msg *Execute) {
+	if f.encodeError != nil {
+		return
+	}
+
 	prevLen := len(f.wbuf)
-	f.wbuf = msg.Encode(f.wbuf)
+	newBuf, err := msg.Encode(f.wbuf)
+	if err != nil {
+		f.encodeError = err
+		return
+	}
+	f.wbuf = newBuf
+
 	if f.tracer != nil {
 		f.tracer.TraceQueryute('F', int32(len(f.wbuf)-prevLen), msg)
 	}
 }
 
-// SendSync sends a Sync message to the backend (i.e. the server). The message is not guaranteed to be written until
-// Flush is called.
+// SendSync sends a Sync message to the backend (i.e. the server). The message is buffered until Flush is called. Any
+// error encountered will be returned from Flush.
 func (f *Frontend) SendSync(msg *Sync) {
+	if f.encodeError != nil {
+		return
+	}
+
 	prevLen := len(f.wbuf)
-	f.wbuf = msg.Encode(f.wbuf)
+	newBuf, err := msg.Encode(f.wbuf)
+	if err != nil {
+		f.encodeError = err
+		return
+	}
+	f.wbuf = newBuf
+
 	if f.tracer != nil {
 		f.tracer.traceSync('F', int32(len(f.wbuf)-prevLen), msg)
 	}
 }
 
-// SendQuery sends a Query message to the backend (i.e. the server). The message is not guaranteed to be written until
-// Flush is called.
+// SendQuery sends a Query message to the backend (i.e. the server). The message is buffered until Flush is called. Any
+// error encountered will be returned from Flush.
 func (f *Frontend) SendQuery(msg *Query) {
+	if f.encodeError != nil {
+		return
+	}
+
 	prevLen := len(f.wbuf)
-	f.wbuf = msg.Encode(f.wbuf)
+	newBuf, err := msg.Encode(f.wbuf)
+	if err != nil {
+		f.encodeError = err
+		return
+	}
+	f.wbuf = newBuf
+
 	if f.tracer != nil {
 		f.tracer.traceQuery('F', int32(len(f.wbuf)-prevLen), msg)
 	}
 }
 
diff --git a/pgproto3/function_call.go b/pgproto3/function_call.go
index 2c4f38df..0b15fce2 100644
--- a/pgproto3/function_call.go
+++ b/pgproto3/function_call.go
@@ -69,14 +69,12 @@ 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)
 	dst = pgio.AppendUint16(dst, uint16(len(src.ArgFormatCodes)))
 	for _, argFormatCode := range src.ArgFormatCodes {
 		dst = pgio.AppendUint16(dst, argFormatCode)
 	}
@@ -88,8 +86,7 @@ func (src *FunctionCall) Encode(dst []byte) []byte {
 			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/pgproto3/function_call_response.go b/pgproto3/function_call_response.go
index 3d3606dd..1f273495 100644
--- a/pgproto3/function_call_response.go
+++ b/pgproto3/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/pgproto3/function_call_test.go b/pgproto3/function_call_test.go
index 8c08bb24..2a70fd30 100644
--- a/pgproto3/function_call_test.go
+++ b/pgproto3/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/pgproto3/gss_enc_request.go b/pgproto3/gss_enc_request.go
index 30ffc08d..70cb20cd 100644
--- a/pgproto3/gss_enc_request.go
+++ b/pgproto3/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/pgproto3/gss_response.go b/pgproto3/gss_response.go
index 64bfbd04..10d93775 100644
--- a/pgproto3/gss_response.go
+++ b/pgproto3/gss_response.go
@@ -1,11 +1,9 @@
 package pgproto3
 
 import (
 	"encoding/json"
-
-	"github.com/jackc/pgx/v5/internal/pgio"
 )
 
 type GSSResponse struct {
 	Data []byte
 }
@@ -16,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/pgproto3/no_data.go b/pgproto3/no_data.go
index d8f85d38..cbcaad40 100644
--- a/pgproto3/no_data.go
+++ b/pgproto3/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/pgproto3/notice_response.go b/pgproto3/notice_response.go
index 4ac28a79..497aba6d 100644
--- a/pgproto3/notice_response.go
+++ b/pgproto3/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/pgproto3/notification_response.go b/pgproto3/notification_response.go
index 228e0dac..243b6bf7 100644
--- a/pgproto3/notification_response.go
+++ b/pgproto3/notification_response.go
@@ -43,24 +43,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/pgproto3/parameter_description.go b/pgproto3/parameter_description.go
index 374d38a3..685e04b8 100644
--- a/pgproto3/parameter_description.go
+++ b/pgproto3/parameter_description.go
@@ -37,23 +37,19 @@ 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')
 
 	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/pgproto3/parameter_status.go b/pgproto3/parameter_status.go
index a303e453..9ee0720b 100644
--- a/pgproto3/parameter_status.go
+++ b/pgproto3/parameter_status.go
@@ -1,12 +1,10 @@
 package pgproto3
 
 import (
 	"bytes"
 	"encoding/json"
-
-	"github.com/jackc/pgx/v5/internal/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/pgproto3/parse.go b/pgproto3/parse.go
index b53200dc..a59154cd 100644
--- a/pgproto3/parse.go
+++ b/pgproto3/parse.go
@@ -50,14 +50,12 @@ 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)
@@ -65,13 +63,11 @@ func (src *Parse) Encode(dst []byte) []byte {
 	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/pgproto3/parse_complete.go b/pgproto3/parse_complete.go
index 92c9498b..cff9e27d 100644
--- a/pgproto3/parse_complete.go
+++ b/pgproto3/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/pgproto3/password_message.go b/pgproto3/password_message.go
index 41f98692..d820d327 100644
--- a/pgproto3/password_message.go
+++ b/pgproto3/password_message.go
@@ -1,12 +1,10 @@
 package pgproto3
 
 import (
 	"bytes"
 	"encoding/json"
-
-	"github.com/jackc/pgx/v5/internal/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/pgproto3.go b/pgproto3/pgproto3.go
index 8df383c2..480abfc0 100644
--- a/pgproto3/pgproto3.go
+++ b/pgproto3/pgproto3.go
@@ -2,21 +2,27 @@ package pgproto3
 
 import (
 	"encoding/hex"
 	"errors"
 	"fmt"
+
+	"github.com/jackc/pgx/v5/internal/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)
 }
 
 // FrontendMessage is a message sent by the frontend (i.e. the client).
 type FrontendMessage interface {
 	Message
@@ -90,5 +96,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/pgproto3_private_test.go b/pgproto3/pgproto3_private_test.go
new file mode 100644
index 00000000..15da1eaf
--- /dev/null
+++ b/pgproto3/pgproto3_private_test.go
@@ -0,0 +1,3 @@
+package pgproto3
+
+const MaxMessageBodyLen = maxMessageBodyLen
diff --git a/pgproto3/portal_suspended.go b/pgproto3/portal_suspended.go
index 1a9e7bfb..9e2f8cbc 100644
--- a/pgproto3/portal_suspended.go
+++ b/pgproto3/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/pgproto3/query.go b/pgproto3/query.go
index e963a0ec..aebdfde8 100644
--- a/pgproto3/query.go
+++ b/pgproto3/query.go
@@ -1,12 +1,10 @@
 package pgproto3
 
 import (
 	"bytes"
 	"encoding/json"
-
-	"github.com/jackc/pgx/v5/internal/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/pgproto3/query_test.go b/pgproto3/query_test.go
new file mode 100644
index 00000000..9551fc14
--- /dev/null
+++ b/pgproto3/query_test.go
@@ -0,0 +1,20 @@
+package pgproto3_test
+
+import (
+	"testing"
+
+	"github.com/jackc/pgx/v5/pgproto3"
+	"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/pgproto3/ready_for_query.go b/pgproto3/ready_for_query.go
index 67a39be3..a56af9fb 100644
--- a/pgproto3/ready_for_query.go
+++ b/pgproto3/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/pgproto3/row_description.go b/pgproto3/row_description.go
index 6f6f0681..c68f1d46 100644
--- a/pgproto3/row_description.go
+++ b/pgproto3/row_description.go
@@ -97,14 +97,12 @@ 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')
 
 	dst = pgio.AppendUint16(dst, uint16(len(src.Fields)))
 	for _, fd := range src.Fields {
 		dst = append(dst, fd.Name...)
 		dst = append(dst, 0)
@@ -115,13 +113,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/pgproto3/sasl_initial_response.go b/pgproto3/sasl_initial_response.go
index eeda4691..9eb1b6a4 100644
--- a/pgproto3/sasl_initial_response.go
+++ b/pgproto3/sasl_initial_response.go
@@ -37,24 +37,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/pgproto3/sasl_response.go b/pgproto3/sasl_response.go
index 54c3d96f..1b604c25 100644
--- a/pgproto3/sasl_response.go
+++ b/pgproto3/sasl_response.go
@@ -1,12 +1,10 @@
 package pgproto3
 
 import (
 	"encoding/hex"
 	"encoding/json"
-
-	"github.com/jackc/pgx/v5/internal/pgio"
 )
 
 type SASLResponse struct {
 	Data []byte
 }
@@ -20,17 +18,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/pgproto3/ssl_request.go b/pgproto3/ssl_request.go
index 1b00c16b..b0fc2847 100644
--- a/pgproto3/ssl_request.go
+++ b/pgproto3/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/pgproto3/startup_message.go b/pgproto3/startup_message.go
index 65de4a36..3af4587d 100644
--- a/pgproto3/startup_message.go
+++ b/pgproto3/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/pgproto3/sync.go b/pgproto3/sync.go
index 5db8e07a..ea4fc959 100644
--- a/pgproto3/sync.go
+++ b/pgproto3/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/pgproto3/terminate.go b/pgproto3/terminate.go
index 135191ea..35a9dc83 100644
--- a/pgproto3/terminate.go
+++ b/pgproto3/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: 	batch.buf = (&pgproto3.Parse{Query: sql, ParameterOIDs: paramOIDs}).Encode(batch.buf)
	batch.buf = (&pgproto3.Bind{PreparedStatement: stmtName, ParameterFormatCodes: paramFormats, Parameters: paramValues, ResultFormatCodes: resultFormats}).Encode(batch.buf)
	batch.buf = (&pgproto3.Describe{ObjectType: 'P'}).Encode(batch.buf)
	batch.buf = (&pgproto3.Execute{}).Encode(batch.buf)
	batch.buf = (&pgproto3.Sync{}).Encode(batch.buf)
				srv.Write((&pgproto3.AuthenticationOk{}).Encode(nil))
				srv.Write((&pgproto3.BackendKeyData{ProcessID: 0, SecretKey: 0}).Encode(nil))
				srv.Write((&pgproto3.ReadyForQuery{TxStatus: 'I'}).Encode(nil))
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
	wbuf []byte
// Send sends a message to the frontend (i.e. the client). The message is not guaranteed to be written until Flush is
// called.
	b.wbuf = msg.Encode(b.wbuf)
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/pgx/v5/internal/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/pgx/v5/internal/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/pgx/v5/internal/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/pgx/v5/internal/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/pgx/v5/internal/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.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)
	wbuf []byte
// Send sends a message to the backend (i.e. the server). The message is not guaranteed to be written until Flush is
// called.
	f.wbuf = msg.Encode(f.wbuf)
// SendBind sends a Bind message to the backend (i.e. the server). The message is not guaranteed to be written until
// Flush is called.
	f.wbuf = msg.Encode(f.wbuf)
// SendParse sends a Parse message to the backend (i.e. the server). The message is not guaranteed to be written until
// Flush is called.
	f.wbuf = msg.Encode(f.wbuf)
// SendClose sends a Close message to the backend (i.e. the server). The message is not guaranteed to be written until
// Flush is called.
	f.wbuf = msg.Encode(f.wbuf)
// SendDescribe sends a Describe message to the backend (i.e. the server). The message is not guaranteed to be written until
// Flush is called.
	f.wbuf = msg.Encode(f.wbuf)
// SendExecute sends an Execute message to the backend (i.e. the server). The message is not guaranteed to be written until
// Flush is called.
	f.wbuf = msg.Encode(f.wbuf)
// SendSync sends a Sync message to the backend (i.e. the server). The message is not guaranteed to be written until
// Flush is called.
	f.wbuf = msg.Encode(f.wbuf)
// SendQuery sends a Query message to the backend (i.e. the server). The message is not guaranteed to be written until
// Flush is called.
	f.wbuf = msg.Encode(f.wbuf)
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/pgx/v5/internal/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/pgx/v5/internal/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/pgx/v5/internal/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/pgx/v5/internal/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/pgx/v5/internal/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: 	err error
	if batch.err != nil {
		return
	}

	batch.buf, batch.err = (&pgproto3.Parse{Query: sql, ParameterOIDs: paramOIDs}).Encode(batch.buf)
	if batch.err != nil {
		return
	}
	if batch.err != nil {
		return
	}

	batch.buf, batch.err = (&pgproto3.Bind{PreparedStatement: stmtName, ParameterFormatCodes: paramFormats, Parameters: paramValues, ResultFormatCodes: resultFormats}).Encode(batch.buf)
	if batch.err != nil {
		return
	}

	batch.buf, batch.err = (&pgproto3.Describe{ObjectType: 'P'}).Encode(batch.buf)
	if batch.err != nil {
		return
	}

	batch.buf, batch.err = (&pgproto3.Execute{}).Encode(batch.buf)
	if batch.err != nil {
		return
	}
	if batch.err != nil {
		return &MultiResultReader{
			closed: true,
			err:    batch.err,
		}
	}

	batch.buf, batch.err = (&pgproto3.Sync{}).Encode(batch.buf)
	if batch.err != nil {
		multiResult.closed = true
		multiResult.err = batch.err
		pgConn.unlock()
		return multiResult
	}
				srv.Write(mustEncode((&pgproto3.AuthenticationOk{}).Encode(nil)))
				srv.Write(mustEncode((&pgproto3.BackendKeyData{ProcessID: 0, SecretKey: 0}).Encode(nil)))
				srv.Write(mustEncode((&pgproto3.ReadyForQuery{TxStatus: 'I'}).Encode(nil)))

func mustEncode(buf []byte, err error) []byte {
	if err != nil {
		panic(err)
	}
	return buf
}
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)
	wbuf        []byte
	encodeError error
// Send sends a message to the frontend (i.e. the client). The message is buffered until Flush is called. Any error
// encountered will be returned from Flush.
	if b.encodeError != nil {
		return
	}

	newBuf, err := msg.Encode(b.wbuf)
	if err != nil {
		b.encodeError = err
		return
	}
	b.wbuf = newBuf

	if err := b.encodeError; err != nil {
		b.encodeError = nil
		b.wbuf = b.wbuf[:0]
		return &writeError{err: err, safeToRetry: true}
	}

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)
func (src *Bind) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'B')
	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/pgx/v5/pgproto3"
	"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)
func (src *CopyBothResponse) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'W')
	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)
func (src *CopyInResponse) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'G')
	return finishMessage(dst, sp)
func (src *CopyOutResponse) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'H')
	return finishMessage(dst, sp)
func (src *DataRow) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'D')
	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
	wbuf        []byte
	encodeError error
// Send sends a message to the backend (i.e. the server). The message is buffered until Flush is called. Any error
// encountered will be returned from Flush.
	if f.encodeError != nil {
		return
	}

	newBuf, err := msg.Encode(f.wbuf)
	if err != nil {
		f.encodeError = err
		return
	}
	f.wbuf = newBuf

	if err := f.encodeError; err != nil {
		f.encodeError = nil
		f.wbuf = f.wbuf[:0]
		return &writeError{err: err, safeToRetry: true}
	}

// SendBind sends a Bind message to the backend (i.e. the server). The message is buffered until Flush is called. Any
// error encountered will be returned from Flush.
	if f.encodeError != nil {
		return
	}

	newBuf, err := msg.Encode(f.wbuf)
	if err != nil {
		f.encodeError = err
		return
	}
	f.wbuf = newBuf

// SendParse sends a Parse message to the backend (i.e. the server). The message is buffered until Flush is called. Any
// error encountered will be returned from Flush.
	if f.encodeError != nil {
		return
	}

	newBuf, err := msg.Encode(f.wbuf)
	if err != nil {
		f.encodeError = err
		return
	}
	f.wbuf = newBuf

// SendClose sends a Close message to the backend (i.e. the server). The message is buffered until Flush is called. Any
// error encountered will be returned from Flush.
	if f.encodeError != nil {
		return
	}

	newBuf, err := msg.Encode(f.wbuf)
	if err != nil {
		f.encodeError = err
		return
	}
	f.wbuf = newBuf

// SendDescribe sends a Describe message to the backend (i.e. the server). The message is buffered until Flush is
// called. Any error encountered will be returned from Flush.
	if f.encodeError != nil {
		return
	}

	newBuf, err := msg.Encode(f.wbuf)
	if err != nil {
		f.encodeError = err
		return
	}
	f.wbuf = newBuf

// SendExecute sends an Execute message to the backend (i.e. the server). The message is buffered until Flush is called.
// Any error encountered will be returned from Flush.
	if f.encodeError != nil {
		return
	}

	newBuf, err := msg.Encode(f.wbuf)
	if err != nil {
		f.encodeError = err
		return
	}
	f.wbuf = newBuf

// SendSync sends a Sync message to the backend (i.e. the server). The message is buffered until Flush is called. Any
// error encountered will be returned from Flush.
	if f.encodeError != nil {
		return
	}

	newBuf, err := msg.Encode(f.wbuf)
	if err != nil {
		f.encodeError = err
		return
	}
	f.wbuf = newBuf

// SendQuery sends a Query message to the backend (i.e. the server). The message is buffered until Flush is called. Any
// error encountered will be returned from Flush.
	if f.encodeError != nil {
		return
	}

	newBuf, err := msg.Encode(f.wbuf)
	if err != nil {
		f.encodeError = err
		return
	}
	f.wbuf = newBuf

func (src *FunctionCall) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'F')
	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)
func (src *ParameterDescription) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 't')
	return finishMessage(dst, sp)
func (src *ParameterStatus) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'S')
	return finishMessage(dst, sp)
func (src *Parse) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'P')
	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/pgx/v5/internal/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/pgx/v5/pgproto3"
	"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
func (src *RowDescription) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'T')
	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