Ctrl K
ComunidadGuías…

pkg/grpc_framing_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package pkg

import (
	"encoding/binary"
	"errors"
	"testing"

	"github.com/protocolbuffers/protoscope"
)

func protoscopeWrite(b []byte) string {
	return protoscope.Write(b, protoscope.WriterOptions{})
}

// wire builds a length-prefixed gRPC message from a raw protobuf payload.
func wire(payload []byte) []byte {
	out := make([]byte, 5)
	binary.BigEndian.PutUint32(out[1:5], uint32(len(payload)))
	return append(out, payload...)
}

// pb builds a trivial protobuf message: field 1, length-delimited string.
func pb(s string) []byte { return append([]byte{0x0a, byte(len(s))}, s...) }

// TestSplitLengthPrefixedMessages_FindsBoundariesByPrefix is the core of the
// streaming work: message boundaries come from the 5-byte prefix, never from
// how the bytes were chunked on the way in.
func TestSplitLengthPrefixedMessages_FindsBoundariesByPrefix(t *testing.T) {
	a, b, c := pb("alpha"), pb("beta"), pb("gamma")
	stream := append(append(wire(a), wire(b)...), wire(c)...)

	msgs, err := SplitLengthPrefixedMessages(stream)
	if err != nil {
		t.Fatalf("splitting a well-formed 3-message stream: %v", err)
	}
	if len(msgs) != 3 {
		t.Fatalf("got %d messages, want 3", len(msgs))
	}
	for i, want := range []int{len(a), len(b), len(c)} {
		if int(msgs[i].MessageLength) != want {
			t.Fatalf("message %d declares %d bytes, want %d", i, msgs[i].MessageLength, want)
		}
	}
	if msgs[0].DecodedData == msgs[1].DecodedData {
		t.Fatal("distinct messages decoded identically; boundaries were not honoured")
	}
}

// TestSplitLengthPrefixedMessages_IsIndependentOfChunking pins the property
// that makes the mock deterministic: the same stream split into different
// DATA-frame-sized pieces must yield the same messages. Frame boundaries are
// set by MTU, flow control and write batching, so anything that recorded them
// would produce a different mock on every run.
func TestSplitLengthPrefixedMessages_IsIndependentOfChunking(t *testing.T) {
	stream := append(wire(pb("alpha")), wire(pb("beta"))...)

	whole, err := SplitLengthPrefixedMessages(stream)
	if err != nil {
		t.Fatalf("whole: %v", err)
	}
	// Re-assemble from arbitrary chunk sizes, as the recorder does from DATA
	// frames, and split again. Every split point is exercised, including ones
	// that land mid-prefix and mid-payload.
	for cut := 1; cut < len(stream); cut++ {
		reassembled := append(append([]byte{}, stream[:cut]...), stream[cut:]...)
		got, err := SplitLengthPrefixedMessages(reassembled)
		if err != nil {
			t.Fatalf("cut at %d: %v", cut, err)
		}
		if len(got) != len(whole) {
			t.Fatalf("cut at %d produced %d messages, want %d. Message count must not depend "+
				"on how the bytes were chunked in transit.", cut, len(got), len(whole))
		}
		for i := range got {
			if got[i] != whole[i] {
				t.Fatalf("cut at %d changed message %d", cut, i)
			}
		}
	}
}

// TestSplitLengthPrefixedMessages_RejectsPartialTail: a chain that does not
// consume its buffer exactly means the capture was cut off. Inventing a
// message from the remainder would record a body that cannot reproduce.
func TestSplitLengthPrefixedMessages_RejectsPartialTail(t *testing.T) {
	full := append(wire(pb("alpha")), wire(pb("beta"))...)

	for _, tc := range []struct {
		name string
		data []byte
	}{
		{"truncated mid-payload", full[:len(full)-3]},
		{"truncated mid-prefix", full[:len(wire(pb("alpha")))+2]},
		{"declared length runs past the end", wire(pb("alpha"))[:5+2]},
	} {
		t.Run(tc.name, func(t *testing.T) {
			msgs, err := SplitLengthPrefixedMessages(tc.data)
			if !errors.Is(err, ErrTrailingGrpcBytes) {
				t.Fatalf("got err=%v, want ErrTrailingGrpcBytes. A partial capture must be "+
					"reported, not silently turned into a message.", err)
			}
			// Whatever decoded cleanly before the break is still returned so
			// the caller can decide.
			_ = msgs
		})
	}
}

func TestSplitLengthPrefixedMessages_EmptyIsNotAnError(t *testing.T) {
	msgs, err := SplitLengthPrefixedMessages(nil)
	if err != nil || len(msgs) != 0 {
		t.Fatalf("empty buffer gave (%v, %d messages), want (nil, 0). A direction carrying only "+
			"trailers is legitimate.", err, len(msgs))
	}
}

// TestCreateLengthPrefixedMessageFromPayload_HonoursDeclaredLength is the
// regression for the decode bug.
//
// The old implementation protoscoped EVERYTHING after byte 5, ignoring the
// declared length. On a stream that swallows the next message's 5-byte header
// as if it were protobuf payload, producing a DecodedData that disagrees with
// the MessageLength sitting beside it.
func TestCreateLengthPrefixedMessageFromPayload_HonoursDeclaredLength(t *testing.T) {
	a := pb("alpha")
	stream := append(wire(a), wire(pb("beta"))...)

	got := CreateLengthPrefixedMessageFromPayload(stream)
	if int(got.MessageLength) != len(a) {
		t.Fatalf("MessageLength = %d, want %d", got.MessageLength, len(a))
	}
	// The decoded body must be message 0 alone — identical to decoding it in
	// isolation.
	alone := CreateLengthPrefixedMessageFromPayload(wire(a))
	if got.DecodedData != alone.DecodedData {
		t.Fatalf("decoding the first message of a stream gave:\n  %q\nbut decoding it alone gave:\n  %q\n"+
			"Decoding past the declared length swallows the next message's header as payload.",
			got.DecodedData, alone.DecodedData)
	}
}

// TestLengthPrefixedRoundTrip: split then re-encode must reproduce the wire
// bytes exactly, or replay sends something different from what was recorded.
func TestLengthPrefixedRoundTrip(t *testing.T) {
	stream := append(append(wire(pb("alpha")), wire(pb("beta"))...), wire(pb("gamma"))...)

	msgs, err := SplitLengthPrefixedMessages(stream)
	if err != nil {
		t.Fatalf("split: %v", err)
	}
	back, err := CreatePayloadFromLengthPrefixedMessages(msgs)
	if err != nil {
		t.Fatalf("re-encode: %v", err)
	}
	if string(back) != string(stream) {
		t.Fatalf("round trip changed the wire bytes:\n got % x\nwant % x", back, stream)
	}
}

// oldCreateLengthPrefixedMessageFromPayload is origin/main's implementation,
// verbatim. It is the reference this package must not diverge from for any
// input that exists in a recording today.
func oldCreateLengthPrefixedMessageFromPayload(data []byte) (flag uint, length uint32, decoded string) {
	if len(data) < 5 {
		return 0, 0, ""
	}
	flag = uint(data[0])
	length = binary.BigEndian.Uint32(data[1:5])
	if len(data) > 5 {
		decoded = protoscopeWrite(data[5:])
	}
	return flag, length, decoded
}

// TestCreateLengthPrefixedMessageFromPayload_MatchesOldOnEverySingleMessage
// is the no-regression proof for the decode change.
//
// Honouring the declared length is a behaviour change, so the question is not
// "is the new behaviour better" but "can it alter anything already recorded".
// It cannot: for a well-formed single message 5+length == len(data), so the
// old slice data[5:] and the new data[5:5+length] are the SAME slice. This
// sweeps payload lengths, compression flags and payload shapes and requires
// byte-equality with the old implementation on every one.
//
// The divergence is confined to inputs the old code decoded WRONGLY — a
// multi-message stream, where it swallowed the next message's 5-byte header
// as payload — and to truncated captures, where both clamp to the bytes
// present.
func TestCreateLengthPrefixedMessageFromPayload_MatchesOldOnEverySingleMessage(t *testing.T) {
	payloads := [][]byte{
		nil,
		{},
		pb(""),
		pb("a"),
		pb("alpha"),
		pb("a longer field value that spans more bytes"),
		{0x08, 0x96, 0x01},       // varint field
		{0x12, 0x02, 0xff, 0xfe}, // bytes field with non-UTF8
		{0x0a, 0x00},             // empty length-delimited
	}
	flags := []byte{0, 1}

	for _, flag := range flags {
		for i, p := range payloads {
			w := wire(p)
			w[0] = flag

			oldFlag, oldLen, oldDecoded := oldCreateLengthPrefixedMessageFromPayload(w)
			got := CreateLengthPrefixedMessageFromPayload(w)

			if got.CompressionFlag != oldFlag || got.MessageLength != oldLen || got.DecodedData != oldDecoded {
				t.Fatalf("payload %d (flag %d) decoded differently from origin/main:\n"+
					" old: flag=%d len=%d data=%q\n new: flag=%d len=%d data=%q\n"+
					"Every mock on disk today is a single message; this path must be byte-identical for them.",
					i, flag, oldFlag, oldLen, oldDecoded,
					got.CompressionFlag, got.MessageLength, got.DecodedData)
			}
		}
	}
}

// TestCreateLengthPrefixedMessageFromPayload_TruncatedMatchesOld: a capture
// cut off mid-payload must decode the same as before — both take the bytes
// present and invent nothing.
func TestCreateLengthPrefixedMessageFromPayload_TruncatedMatchesOld(t *testing.T) {
	full := wire(pb("alpha"))
	for cut := 5; cut < len(full); cut++ {
		trunc := full[:cut]
		oldFlag, oldLen, oldDecoded := oldCreateLengthPrefixedMessageFromPayload(trunc)
		got := CreateLengthPrefixedMessageFromPayload(trunc)
		if got.CompressionFlag != oldFlag || got.MessageLength != oldLen || got.DecodedData != oldDecoded {
			t.Fatalf("truncated at %d decoded differently from origin/main:\n old: len=%d data=%q\n new: len=%d data=%q",
				cut, oldLen, oldDecoded, got.MessageLength, got.DecodedData)
		}
	}
}