Flatbuffers 从流中验证SizePrefixedBuffer

Flatbuffers VerifySizePrefixedBuffer from stream

提问人:tchatow 提问时间:5/6/2023 更新时间:5/11/2023 访问量:212

问:

这个问题是关于从 C++ 中的流中读取大小前缀为 Flatbuffers 的正确模式。

考虑一个数据流 - 即。消息所在的 TCP 由其大小前缀分隔。我最初的印象是可以使用以下代码提取这些:

while (true) {
  flatbuffers::Verifier v(buf.data(), buf.size());
  if (v.VerifySizePrefixedBuffer<Message>(nullptr)) {
    const flatbuffers::uoffset_t off = flatbuffers::GetPrefixedSize(buf.data());
    const auto msg = flatbuffers::GetSizePrefixedRoot<Message>(buf.data());

    // Use message

    buf.erase(buf.begin(), buf.begin() + 4 + off);
  } else {
    break;
  }
}

但是,这被证明是不正确的 - 检查提供给验证程序的缓冲区的大小是否等于 size 前缀,而不是“至少”。VerifySizePrefixedBuffer

来自 verifier.h:

Check(ReadScalar<uoffset_t>(buf_) == size_ - sizeof(uoffset_t))

这意味着,如果缓冲区中有多条消息,我们不能使用验证程序来检查是否只有第一条消息有效。相反,必须首先读取消息大小并将其提供给验证程序:

while (buf.size() >= 4) {
  const flatbuffers::uoffset_t off = flatbuffers::GetPrefixedSize(buf.data());
  flatbuffers::Verifier v(buf.data(), 4 + off);
  if (v.VerifySizePrefixedBuffer<Message>(nullptr)) {
    const auto msg = flatbuffers::GetSizePrefixedRoot<Message>(buf.data());

    // Use message

    buf.erase(buf.begin(), buf.begin() + 4 + off);
  } else {
    break;
  }
}

这似乎是在重复验证者检查前缀有效性的工作。我应该使用其他模式还是这是预期的用法?

C++ 平面缓冲区

评论

0赞 Moop 5/9/2023
答案有问题吗?

答:

0赞 Moop 5/6/2023 #1

仅适用于“已完成”缓冲区,它无法验证部分缓冲区。它验证所有偏移量是否都指向缓冲区中的有效区域,即您不会尝试读取不在缓冲区中的数据。verifier

假设你的第一个块进来了,并且那个卡盘指向第二个块中的某个区域。验证程序将失败,说 超出了它目前拥有的缓冲区的范围。OffsetOffset

Size 前缀用于帮助分块代码正确接收缓冲区的长度并将块拼接在一起。您应该使用 to 获取预期的大小,然后使用它来将块拼接在一起,直到读取那么多数据。之后,您可以使用大小的前缀验证程序。GetPrefixedSize()

你想要这样的东西:

const auto expected_size = flatbuffers::GetPrefixedSize(buf.data());
while(buf.size() < expected_size) {
  append_to_buf(buf); // however you get your next chunk
}

flatbuffers::Verifier v(buf.data(), expected_size + sizeof(expected_size));
if (!v.VerifySizePrefixedBuffer<Message>()) {
  // ERROR in verification
}
const auto msg = flatbuffers::GetSizePrefixedRoot<Message>(buf.data());

// Use the msg.

// Optionally, consume the buffer to start reading the next message if you have more than one.
buf.erase(buf.begin(), buf.begin() + sizeof(expected_size) + expected_size);

评论

0赞 tchatow 5/11/2023
嗨,我不确定这是否真的是我所问的关键。 如果缓冲区的大小不完全相等,则失败。因此,您被迫将完全相同的大小传递给验证者 - 仅此而已。我更多地从设计的角度质疑这一点——为什么验证者要求你向它提供它可以自己推断的信息?一个更明智的实现,IMO,是验证器检查你的缓冲区至少足够大(甚至可能有一个out参数来返回它找到的大小)。VerifySizePrefixedBuffersizeof(expected_size) + expected_size
0赞 Moop 5/11/2023
好的,所以问题更多的是关于验证器中的 == vs <= 检查,而不是关于流方面,这可能只是一个错误,我不得不考虑一下它可能会破坏任何东西。但是关于你关于流媒体的问题,你仍然需要调用buff,这样你就知道你从流媒体中读取了足够多的内容,从而拥有一个完整的平坦缓冲区。这就是我在这个答案中试图展示的。一旦你有了预期的大小,创建 .GetPrefixedSizeVerifierv(buf.data(), expected_size + sizeof(expected_size))
0赞 Moop 5/12/2023
为此提交了 github.com/google/flatbuffers/issues/7956