递归数据结构解组在 Go Lang Protobuf 中给出错误 "cannot parse invalid wire-format data"

Recursive data structure unmarshalling gives error "cannot parse invalid wire-format data" in Go Lang Protobuf

OS 和 protobuf 版本

go1.18.1 linux/amd64, github.com/golang/protobuf v1.5.2

简介

我正在尝试使用递归原型定义。

.proto 文件

message AsyncConsensus {
  int32 sender = 1;
  int32 receiver = 2;
  string unique_id = 3; // to specify the fall back block id to which the vote asyn is for
  int32 type = 4; // 1-propose, 2-vote, 3-timeout, 4-propose-async, 5-vote-async, 6-timeout-internal, 7-consensus-external-request, 8-consensus-external-response, 9-fallback-complete
  string note = 5;
  int32 v = 6 ; // view number
  int32 r = 7;// round number
  message Block {
    string id = 1;
    int32 v = 2 ; // view number
    int32 r = 3;// round number
    Block parent = 4;
    repeated int32 commands = 5;
    int32 level = 6; // for the fallback mode
  }
  Block blockHigh = 8;
  Block blockNew = 9;
  Block blockCommit = 10;
}

下面是我编组和解组的方法

func (t *AsyncConsensus) Marshal(wire io.Writer) error {
    data, err := proto.Marshal(t)
    if err != nil {
        return err
    }
    lengthWritten := len(data)
    var b [8]byte
    bs := b[:8]
    binary.LittleEndian.PutUint64(bs, uint64(lengthWritten))
    _, err = wire.Write(bs)
    if err != nil {
        return err
    }
    _, err = wire.Write(data)
    if err != nil {
        return err
    }
    return nil
}

func (t *AsyncConsensus) Unmarshal(wire io.Reader) error {

    var b [8]byte
    bs := b[:8]
    _, err := io.ReadFull(wire, bs)
    if err != nil {
        return err
    }
    numBytes := binary.LittleEndian.Uint64(bs)
    data := make([]byte, numBytes)
    length, err := io.ReadFull(wire, data)
    if err != nil {
        return err
    }
    err = proto.Unmarshal(data[:length], t)
    if err != nil {
        return err
    }
    return nil
}

func (t *AsyncConsensus) New() Serializable {
    return new(AsyncConsensus)
}

我的预期结果

当通过 TCP 编组并发送到同一进程时,它应该正确解组并生成正确的数据结构。

结果错误

错误"cannot parse invalid wire-format data"

附加信息

我尝试使用非递归 .proto 定义,之前从未遇到过这个问题。

我能想到的最愚蠢的错误是 wire.Write(bs) 写入的字节数不如 io.ReadFull(wire, bs) 读取的字节数多 - 所以我只是确保它们的 return 值在两种情况下实际上都是 8。

那我对golang/protobuf不是很了解,不过我想应该可以做到这一点。你不应该创建 go-code 然后调用它吗?不知道怎么称呼。

如果你认为实际上是protobuf实现的问题,网上有一些protobuf-decoders可以提供帮助。但他们有时会错误地解释流,递归模式可能就是这种情况,所以你必须小心。但至少他们帮助我调试了 dedis/protobuf 包不止一次。

作为最后的手段,你可以用递归数据做一个最小的例子,检查它是否有效,然后慢慢添加字段直到它中断......

这不是 Protobuff 的错误,而是您如何 marshalunmarshal protobuff 结构的问题。

作为一个具体的指导方针,永远不要同时使用 marshalunmarshal protobuff 结构,因为它会导致竞争条件。

在您提供的具体示例中,我看到了递归数据结构,因此即使您对 marshalunmarshal 的每次调用使用单独的结构,也可能parent 可以导致共享指针。

使用深层复制技术删除任何依赖项,这样您就不会 运行 进入竞争条件。

func CloneMyStruct(orig *proto.AsyncConsensus_Block) (*proto.AsyncConsensus_Block, error) {
    origJSON, err := json.Marshal(orig)
    if err != nil {
        return nil, err
    }

    clone := proto.AsyncConsensus_Block{}
    if err = json.Unmarshal(origJSON, &clone); err != nil {
        return nil, err
    }

    return &clone, nil
}