mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-21 12:16:44 +00:00
merge frame function
This commit is contained in:
parent
edf7f7b473
commit
e1d27be1f8
2 changed files with 126 additions and 128 deletions
|
|
@ -35,31 +35,15 @@ const (
|
|||
frameHeader = 4 // payload_len prefix
|
||||
)
|
||||
|
||||
// ReadFrame reads a frame with 12-byte ClusterMetadata and no payload-length cap.
|
||||
func ReadFrame(r io.Reader) (*Frame, error) {
|
||||
return readFrame(r, metaSize, 0)
|
||||
}
|
||||
|
||||
// ReadFrameWithMaxPayload reads a frame with 12-byte ClusterMetadata and rejects frames
|
||||
// whose payload_len exceeds maxPayloadLen before allocation.
|
||||
func ReadFrameWithMaxPayload(r io.Reader, maxPayloadLen uint32) (*Frame, error) {
|
||||
if maxPayloadLen == 0 {
|
||||
return nil, errors.New("maxPayloadLen must be greater than zero")
|
||||
}
|
||||
// ReadFrame reads a frame with 12-byte ClusterMetadata.
|
||||
// maxPayloadLen == 0 disables payload-size checking.
|
||||
func ReadFrame(r io.Reader, maxPayloadLen uint32) (*Frame, error) {
|
||||
return readFrame(r, metaSize, maxPayloadLen)
|
||||
}
|
||||
|
||||
// ReadFrameNoMeta reads a frame with 0-byte metadata and no payload-length cap.
|
||||
func ReadFrameNoMeta(r io.Reader) (*Frame, error) {
|
||||
return readFrame(r, 0, 0)
|
||||
}
|
||||
|
||||
// ReadFrameNoMetaWithMaxPayload reads a frame with 0-byte metadata and rejects
|
||||
// frames whose payload_len exceeds maxPayloadLen before allocation.
|
||||
func ReadFrameNoMetaWithMaxPayload(r io.Reader, maxPayloadLen uint32) (*Frame, error) {
|
||||
if maxPayloadLen == 0 {
|
||||
return nil, errors.New("maxPayloadLen must be greater than zero")
|
||||
}
|
||||
// ReadFrameNoMeta reads a frame with 0-byte metadata.
|
||||
// maxPayloadLen == 0 disables payload-size checking.
|
||||
func ReadFrameNoMeta(r io.Reader, maxPayloadLen uint32) (*Frame, error) {
|
||||
return readFrame(r, 0, maxPayloadLen)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
@ -19,14 +20,14 @@ var (
|
|||
// - root_tip = None (Ping only)
|
||||
// - opcode = 0x81 (PING), 0x82 (PONG) from ClusterOp (CLUSTER_OP_BASE=128)
|
||||
pythonClusterPing = "0000001300000001000000000000303981000000000000000100000002696400000002000000010000000200"
|
||||
pythonClusterPong = "00000012000000010000000000003039820000000000000001000000026964000000020000000100000002"
|
||||
pythonNoMetaPing = "0000001381000000000000000100000002696400000002000000010000000200"
|
||||
pythonNoMetaPong = "00000012820000000000000001000000026964000000020000000100000002"
|
||||
)
|
||||
|
||||
func TestPythonVectors_ClusterPing(t *testing.T) {
|
||||
wire := mustPythonVectorBytes(t, pythonClusterPing)
|
||||
f, err := ReadFrame(bytes.NewReader(wire))
|
||||
// ---- wire compatibility (pyquarkchain vectors) ----
|
||||
|
||||
func TestWireCompat_Meta(t *testing.T) {
|
||||
wire := mustHex(t, pythonClusterPing)
|
||||
f, err := ReadFrame(bytes.NewReader(wire), 0)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFrame: %v", err)
|
||||
}
|
||||
|
|
@ -45,30 +46,9 @@ func TestPythonVectors_ClusterPing(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPythonVectors_ClusterPong(t *testing.T) {
|
||||
wire := mustPythonVectorBytes(t, pythonClusterPong)
|
||||
f, err := ReadFrame(bytes.NewReader(wire))
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFrame: %v", err)
|
||||
}
|
||||
if f.Opcode != 0x82 || f.RPCID != 1 {
|
||||
t.Fatalf("unexpected header: opcode=0x%02x rpcid=%d", f.Opcode, f.RPCID)
|
||||
}
|
||||
if f.Meta.Branch != 1 || f.Meta.ClusterPeerID != 12345 {
|
||||
t.Fatalf("unexpected meta: %+v", f.Meta)
|
||||
}
|
||||
var out bytes.Buffer
|
||||
if err := WriteFrame(&out, f); err != nil {
|
||||
t.Fatalf("WriteFrame: %v", err)
|
||||
}
|
||||
if !bytes.Equal(out.Bytes(), wire) {
|
||||
t.Fatalf("frame mismatch:\n go %x\n py %x", out.Bytes(), wire)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPythonVectors_NoMetaPing(t *testing.T) {
|
||||
wire := mustPythonVectorBytes(t, pythonNoMetaPing)
|
||||
f, err := ReadFrameNoMeta(bytes.NewReader(wire))
|
||||
func TestWireCompat_NoMeta(t *testing.T) {
|
||||
wire := mustHex(t, pythonNoMetaPing)
|
||||
f, err := ReadFrameNoMeta(bytes.NewReader(wire), 0)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFrameNoMeta: %v", err)
|
||||
}
|
||||
|
|
@ -84,74 +64,39 @@ func TestPythonVectors_NoMetaPing(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPythonVectors_NoMetaPong(t *testing.T) {
|
||||
wire := mustPythonVectorBytes(t, pythonNoMetaPong)
|
||||
f, err := ReadFrameNoMeta(bytes.NewReader(wire))
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFrameNoMeta: %v", err)
|
||||
}
|
||||
if f.Opcode != 0x82 || f.RPCID != 1 {
|
||||
t.Fatalf("unexpected header: opcode=0x%02x rpcid=%d", f.Opcode, f.RPCID)
|
||||
}
|
||||
var out bytes.Buffer
|
||||
if err := WriteFrameNoMeta(&out, f); err != nil {
|
||||
t.Fatalf("WriteFrameNoMeta: %v", err)
|
||||
}
|
||||
if !bytes.Equal(out.Bytes(), wire) {
|
||||
t.Fatalf("frame mismatch:\n go %x\n py %x", out.Bytes(), wire)
|
||||
}
|
||||
}
|
||||
// ---- round-trip: write → read ----
|
||||
|
||||
func mustPythonVectorBytes(t *testing.T, hexStr string) []byte {
|
||||
t.Helper()
|
||||
b, err := hex.DecodeString(hexStr)
|
||||
if err != nil {
|
||||
t.Fatalf("decode python hex: %v", err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func TestRoundTrip_Meta(t *testing.T) {
|
||||
func TestRoundTrip(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
f *Frame
|
||||
meta ClusterMetadata
|
||||
opcode byte
|
||||
rpcID uint64
|
||||
payload []byte
|
||||
}{
|
||||
{"empty", &Frame{Opcode: 1, RPCID: 0, Payload: nil}},
|
||||
{"with_meta", &Frame{Meta: ClusterMetadata{Branch: 5, ClusterPeerID: 999}, Opcode: 0x10, RPCID: 7, Payload: []byte("hello")}},
|
||||
{"large_rpc_id", &Frame{Opcode: 0xC4, RPCID: 0xFFFFFFFFFFFFFFFF, Payload: []byte("x")}},
|
||||
{"zero_meta", &Frame{Meta: ClusterMetadata{}, Opcode: 0x81, RPCID: 1, Payload: []byte{}}},
|
||||
{"nil_payload", ClusterMetadata{}, 0x01, 0, nil},
|
||||
{"with_meta", ClusterMetadata{Branch: 5, ClusterPeerID: 999}, 0x10, 7, []byte("hello")},
|
||||
{"max_rpc_id", ClusterMetadata{}, 0xC4, 0xFFFFFFFFFFFFFFFF, []byte("x")},
|
||||
{"empty_payload", ClusterMetadata{}, 0x81, 1, []byte{}},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
wire := writeFrameForTest(tc.f.Meta, tc.f.Opcode, tc.f.RPCID, tc.f.Payload)
|
||||
got, err := ReadFrame(bytes.NewReader(wire))
|
||||
wire := writeFrameForTest(tc.meta, tc.opcode, tc.rpcID, tc.payload)
|
||||
got, err := ReadFrame(bytes.NewReader(wire), 0)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFrame: %v", err)
|
||||
}
|
||||
if got.Opcode != tc.f.Opcode || got.RPCID != tc.f.RPCID || got.Meta != tc.f.Meta {
|
||||
t.Errorf("mismatch: got %+v, want %+v", got, tc.f)
|
||||
if got.Opcode != tc.opcode || got.RPCID != tc.rpcID || got.Meta != tc.meta {
|
||||
t.Errorf("mismatch: got %+v, want Meta=%+v Opcode=%x RPCID=%d", got, tc.meta, tc.opcode, tc.rpcID)
|
||||
}
|
||||
if !bytes.Equal(got.Payload, tc.f.Payload) {
|
||||
if !bytes.Equal(got.Payload, tc.payload) {
|
||||
t.Errorf("payload mismatch")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundTrip_NoMeta(t *testing.T) {
|
||||
original := &Frame{Opcode: 0x93, RPCID: 99, Payload: []byte("xshard-data")}
|
||||
wire := writeFrameNoMetaForTest(original.Opcode, original.RPCID, original.Payload)
|
||||
got, err := ReadFrameNoMeta(bytes.NewReader(wire))
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFrameNoMeta: %v", err)
|
||||
}
|
||||
if got.Opcode != original.Opcode || got.RPCID != original.RPCID {
|
||||
t.Errorf("mismatch: got %+v", got)
|
||||
}
|
||||
if !bytes.Equal(got.Payload, original.Payload) {
|
||||
t.Errorf("payload mismatch")
|
||||
}
|
||||
}
|
||||
// ---- wire format layout ----
|
||||
|
||||
func TestWireFormatLayout(t *testing.T) {
|
||||
f := &Frame{
|
||||
|
|
@ -182,6 +127,8 @@ func TestWireFormatLayout(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// ---- multi-frame stream ----
|
||||
|
||||
func TestMultiFrameStream(t *testing.T) {
|
||||
frames := []*Frame{
|
||||
{Meta: ClusterMetadata{Branch: 0, ClusterPeerID: 0}, Opcode: 0x81, RPCID: 0, Payload: []byte("ping")},
|
||||
|
|
@ -195,7 +142,7 @@ func TestMultiFrameStream(t *testing.T) {
|
|||
|
||||
reader := bytes.NewReader(stream.Bytes())
|
||||
for i, want := range frames {
|
||||
got, err := ReadFrame(reader)
|
||||
got, err := ReadFrame(reader, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("frame %d: %v", i, err)
|
||||
}
|
||||
|
|
@ -208,51 +155,108 @@ func TestMultiFrameStream(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestReadFrame_EOF(t *testing.T) {
|
||||
if _, err := ReadFrame(bytes.NewReader(nil)); err == nil {
|
||||
t.Error("expected error on empty stream")
|
||||
// ---- error paths ----
|
||||
|
||||
func TestReadErrors(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
r io.Reader
|
||||
read func(io.Reader, uint32) (*Frame, error)
|
||||
}{
|
||||
{"meta_empty_stream", bytes.NewReader(nil), ReadFrame},
|
||||
{"meta_truncated", bytes.NewReader(truncatedHeader()), ReadFrame},
|
||||
{"nometa_empty_stream", bytes.NewReader(nil), ReadFrameNoMeta},
|
||||
{"nometa_truncated", bytes.NewReader(truncatedHeader()), ReadFrameNoMeta},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if _, err := tc.read(tc.r, 0); err == nil {
|
||||
t.Error("expected error, got nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadFrame_Truncated(t *testing.T) {
|
||||
func truncatedHeader() []byte {
|
||||
hdr := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(hdr, 100)
|
||||
if _, err := ReadFrame(bytes.NewReader(hdr)); err == nil {
|
||||
t.Error("expected error on truncated frame")
|
||||
}
|
||||
return hdr
|
||||
}
|
||||
|
||||
// ---- payload size limit ----
|
||||
|
||||
func TestReadFrame_PayloadLimit(t *testing.T) {
|
||||
hdr := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(hdr, 100)
|
||||
payload := bytes.Repeat([]byte{0xAB}, 100)
|
||||
frame := &Frame{Meta: ClusterMetadata{Branch: 1, ClusterPeerID: 2}, Opcode: 0x81, RPCID: 1, Payload: payload}
|
||||
|
||||
if _, err := ReadFrameWithMaxPayload(bytes.NewReader(hdr), 64); err == nil {
|
||||
t.Fatal("expected error when payload_len exceeds limit")
|
||||
// limit == 0: unbounded — full frame with 100-byte payload is accepted.
|
||||
{
|
||||
wire := writeFrameForTest(frame.Meta, frame.Opcode, frame.RPCID, frame.Payload)
|
||||
got, err := ReadFrame(bytes.NewReader(wire), 0)
|
||||
if err != nil {
|
||||
t.Fatalf("limit=0 should accept full frame, got: %v", err)
|
||||
}
|
||||
if !bytes.Equal(got.Payload, payload) {
|
||||
t.Fatalf("limit=0 payload mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := ReadFrameWithMaxPayload(bytes.NewReader(hdr), 0); err == nil {
|
||||
t.Fatal("expected error when maxPayloadLen is zero")
|
||||
// limit == payload length: boundary, accepted.
|
||||
{
|
||||
wire := writeFrameForTest(frame.Meta, frame.Opcode, frame.RPCID, frame.Payload)
|
||||
if _, err := ReadFrame(bytes.NewReader(wire), uint32(len(payload))); err != nil {
|
||||
t.Fatalf("limit==payload_len should accept, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := ReadFrame(bytes.NewReader(hdr)); err == nil {
|
||||
t.Error("expected error on truncated frame without payload limit")
|
||||
// limit < payload length: rejected before metadata is read.
|
||||
{
|
||||
wire := writeFrameForTest(frame.Meta, frame.Opcode, frame.RPCID, frame.Payload)
|
||||
_, err := ReadFrame(bytes.NewReader(wire), uint32(len(payload)-1))
|
||||
if err == nil {
|
||||
t.Fatal("limit < payload_len should be rejected")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadFrameNoMeta_PayloadLimit(t *testing.T) {
|
||||
hdr := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(hdr, 32)
|
||||
payload := bytes.Repeat([]byte{0xCD}, 32)
|
||||
frame := &Frame{Opcode: 0x82, RPCID: 7, Payload: payload}
|
||||
|
||||
if _, err := ReadFrameNoMetaWithMaxPayload(bytes.NewReader(hdr), 16); err == nil {
|
||||
t.Fatal("expected error when payload_len exceeds limit")
|
||||
// limit == 0: unbounded.
|
||||
{
|
||||
wire := writeFrameNoMetaForTest(frame.Opcode, frame.RPCID, frame.Payload)
|
||||
got, err := ReadFrameNoMeta(bytes.NewReader(wire), 0)
|
||||
if err != nil {
|
||||
t.Fatalf("limit=0 should accept full frame, got: %v", err)
|
||||
}
|
||||
|
||||
if _, err := ReadFrameNoMetaWithMaxPayload(bytes.NewReader(hdr), 0); err == nil {
|
||||
t.Fatal("expected error when maxPayloadLen is zero")
|
||||
if !bytes.Equal(got.Payload, payload) {
|
||||
t.Fatalf("limit=0 payload mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClusterMetadata_RoundTrip(t *testing.T) {
|
||||
// limit == payload length: boundary, accepted.
|
||||
{
|
||||
wire := writeFrameNoMetaForTest(frame.Opcode, frame.RPCID, frame.Payload)
|
||||
if _, err := ReadFrameNoMeta(bytes.NewReader(wire), uint32(len(payload))); err != nil {
|
||||
t.Fatalf("limit==payload_len should accept, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// limit < payload length: rejected.
|
||||
{
|
||||
wire := writeFrameNoMetaForTest(frame.Opcode, frame.RPCID, frame.Payload)
|
||||
_, err := ReadFrameNoMeta(bytes.NewReader(wire), uint32(len(payload)-1))
|
||||
if err == nil {
|
||||
t.Fatal("limit < payload_len should be rejected")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- ClusterMetadata serialization ----
|
||||
|
||||
func TestClusterMetadata(t *testing.T) {
|
||||
// Round-trip: edge cases.
|
||||
cases := []ClusterMetadata{
|
||||
{Branch: 0, ClusterPeerID: 0},
|
||||
{Branch: 1, ClusterPeerID: 12345},
|
||||
|
|
@ -268,9 +272,8 @@ func TestClusterMetadata_RoundTrip(t *testing.T) {
|
|||
t.Errorf("round-trip mismatch: got %+v, want %+v", got, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalClusterMetadata_InvalidLength(t *testing.T) {
|
||||
// Invalid lengths.
|
||||
for _, n := range []int{0, 4, 8, 11, 13, 16} {
|
||||
b := make([]byte, n)
|
||||
if _, err := UnmarshalClusterMetadata(b); err == nil {
|
||||
|
|
@ -279,6 +282,17 @@ func TestUnmarshalClusterMetadata_InvalidLength(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// ---- helpers ----
|
||||
|
||||
func mustHex(t *testing.T, hexStr string) []byte {
|
||||
t.Helper()
|
||||
b, err := hex.DecodeString(hexStr)
|
||||
if err != nil {
|
||||
t.Fatalf("decode hex: %v", err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func writeFrameForTest(meta ClusterMetadata, opcode byte, rpcID uint64, payload []byte) []byte {
|
||||
var buf bytes.Buffer
|
||||
_ = WriteFrame(&buf, &Frame{Meta: meta, Opcode: opcode, RPCID: rpcID, Payload: payload})
|
||||
|
|
|
|||
Loading…
Reference in a new issue