Kaitai Struct:有什么方法可以使整个主体类型依赖于第一个字节的presence/type?

Kaitai Struct: Any way to make entire body type dependent on presence/type of first byte?

我正在尝试为 Postgres Wire Protocol V3 编写 Katai 定义:

我 运行 遇到的问题是,除了 StartupMessage 之外的每条消息都遵循相同的格式。 StartupMessage 的形状不同。

所以我需要以某种方式说“对象可以是这两种类型之一”,但我不确定该怎么做。

大多数消息的布局是:

|-----------------------------------------------|
| Type  | Length | (Rest of payload)
|-----------------------------------------------|
| Char  | Int32  |  Bytes
|-----------------------------------------------|

但是对于启动消息,开头没有 Type 个字符来标识它:

 |-----------------------------------------------|
 | Length | Protocol Version | (Rest of payload)
 |-----------------------------------------------|
 |  Int32 |      Int32       |  Bytes
 |-----------------------------------------------|

到目前为止,我试过这样的方法:

meta:
    id: postgres_wire_protocol_frontend_v3
    file-extension: postgres_wire_protocol_frontend_v3
    endian: be

seq:
    - id: type
      type: str
      encoding: ASCII
      size: 1

    - id: length
      type: u4

    - id: body
      size: length
      type:
          switch-on: type
          cases:
              '"B"': bind_message
              '"E"': execute_message
              '"Q"': query_message
              _: startup_message

但不幸的是这似乎不起作用=/

有什么方法可以在 Kaitai 中对其进行编码吗?

附属免责声明:我是 Kaitai Struct maintainer (see my GitHub profile)。

查看 PostgreSQL docs,似乎 StartupMessage 只能是“第一条消息”:

The first byte of a message identifies the message type, and the next four bytes specify the length of the rest of the message (this length count includes itself, but not the message-type byte). The remaining contents of the message are determined by the message type. For historical reasons, the very first message sent by the client (the startup message) has no initial message-type byte.

我不知道您使用 Kaitai Struct-generated 解析器的应用程序将如何运行,因此我不确定如何以适合您的方式使用此信息。您问题中的 .ksy 片段表明您将使用解析器 class PostgresWireProtocolFrontendV3 的新实例处理每条消息,因此我建议只创建一个新的 .ksy 文件对于 StartupMessage:

meta:
  id: postgres_protocol_startup_message
seq:
  - id: len_message
    type: u4
  - id: body
    size: len_message - len_message._sizeof
    type: message_body
types:
  message_body:
    seq:
      - id: version_major
        type: u2
        valid: 3
      - id: version_minor
        type: u2
        valid: 0
      # ...

注意:len_message._sizeof 将在编译时转换为 4(这是必需的,因为该字段在 PostgreSQL 文档中被描述为“消息内容的长度,以字节为单位,包括自己。”)。虚拟 sizeof 运算符是 0.9 feature:

  • Implement compile-time sizeof and bitsizeof operators (#84)
    • Type-based: sizeof<u4>, bitsizeof<b13>, sizeof<user_type>
    • Value-based: file_header._sizeof, flags._bitsizeof (file_header, flags are fields defined in the current type)

valid 也在 0.9 中引入,目前还没有合适的文档(抱歉),但是您可以在 #435.

中阅读它的描述

在您的应用程序代码中,您可能会知道通信的状态(即您是否正在处理第一条消息),所以我假设您会这样做:

message_raw = b'...'  # TODO: receive from the socket (probably)

if is_first_message:
    startup_message = PostgresProtocolStartupMessage(KaitaiStream(BytesIO(message_raw)))
    # ...
    is_first_message = False
else:
    message = PostgresWireProtocolFrontendV3(KaitaiStream(BytesIO(message_raw)))

当然,我不知道你的用例,所以我只是猜测它可能是什么,但希望至少其中一些对你有用。


So I need to somehow say "The object can be one of these two types", but I'm unsure how to do it.

这不是 Kaitai Struct works. Kaitai Struct has (intentionally) no backtracking, it's designed to handle non-ambiguous binary formats (see ). While it is possible to use instances 做某种前瞻性决定接下来发生什么的方式,除非你真的需要它,否则最好避免它。