web-dev-qa-db-ja.com

protobufs形式のrawデコーダー

.protoファイルを使用せずに、バイナリprotobufメッセージを含まれているデータの人間が読める形式の説明に変換する方法を見つけたいと思います。

背景には、Androidのパーサーによって拒否されたという.protoメッセージがありますが、その理由は完全には明らかではありません。私は手でメッセージを読むことができましたが、それはかなり退屈です。

私は試した protoc --decode_rawですが、「入力の解析に失敗しました。」というエラーが表示されるだけです。私は誰かがこれを行うかもしれない素敵なウェブユーティリティをしただろうと期待/期待してグーグルしましたが、明白なものは何も見つかりませんでした。

次のような出力が得られることを望んでいます。

field 1: varint: 128
field 4: string: "foo"

正しい方向へのポインタは大歓迎です!

22
JosephH

Wiresharkプラグインを介して強制するか、一部の実装の「リーダー」部分を借用することができます(たとえば、C#でこれを行う方法は知っていますが、それが意図したことではないかと思います)。

ただし、注意が必要です。プロトコルバッファ内の文字列は、実際には「文字列」を意味するわけではありません。次のようになります。

  • uTF-8文字列
  • 任意のデータの生のBLOB
  • サブメッセージ
  • 「パックされた」配列
  • (おそらく私が忘れている何か他のもの)
6
Marc Gravell

後世のために:Googleのプロトコルバッファ tools rawバッファをデコードする機能があります。

不明なバッファを送信して、--decode_rawフラグを渡すだけです。

$ cat has_no_proto.buff | protoc --decode_raw
2 {
  2: "Error retrieving information from server. [RH-02]"
}

これは、フィールド2が埋め込みメッセージに設定されているメッセージです。2番目のフィールドは文字列に設定されており、GooglePlayに腹を立てていることを示しています。

型情報は明確ではありません(すべてのバイナリデータを文字列として表示しようとするようですが、varint/string/submessageを区別するための要件は満たされています)。

33
anq

Michel de Ruiterの回答 に記載されているように、protobufメッセージに length-prefix が含まれている可能性があります。そうだとすれば、この答えが役立つはずです。

(注:以下のほとんどのコマンドでは、protobufメッセージがinputというファイルに保存されていると想定しています。)

単一のメッセージの場合はprotoc --decode_raw + dd

単一のメッセージの場合は、実際にprotoc --decode_rawを活用できますが、最初に長さプレフィックスヘッダーを削除する必要があります。ヘッダーの長さが4バイトであるとすると、ddを使用してヘッダーをinputから取り除き、出力をprotocにフィードできます。

dd bs=1 skip=4 if=input 2>/dev/null | protoc --decode_raw

単一のメッセージの場合はprotoc-decode-lenprefix --decode_raw

また、ヘッダーストリッピングを自動的に処理する script も作成しました。

protoc-decode-lenprefix --decode_raw < input

このスクリプトは、protoc --decode_rawの上にある単なるラッパーですが、長さプレフィックスヘッダーを解析してからprotocを呼び出す処理を行います。

さて、この場合、このスクリプトはそれほど有用ではありません。上記のddトリックを使用して、ヘッダーを取り除くことができるからです。ただし、長さプレフィックスヘッダーで囲まれた複数のメッセージを含むデータストリーム(ファイルまたはTCPストリーム)など)があるとします。

メッセージのストリームの場合はprotoc-decode-lenprefix --decode_raw

入力ファイル内の単一のprotobufメッセージの代わりに、inputに長さプレフィックスヘッダーで囲まれた複数のprotobufメッセージが含まれているとします。この場合、長さプレフィックスヘッダーの内容を実際に読み取る必要があるため、justddトリックを使用することはできません。ストリーム内の後続のメッセージの長さ、つまり次のヘッダー+メッセージの何バイト先にあるかを判別します。したがって、そのすべてを心配する代わりに、protoc-decode-lenprefixをもう一度使用するだけです。

protoc-decode-lenprefix --decode_raw < input

protoc-decode-lenprefix --decode ... foo.protoメッセージのストリームの場合

このスクリプトを使用して、長さのプレフィックスが付いたメッセージを完全にデコードすることもできます(メッセージを「生でデコード」するだけではありません)。ラップされたprotocコマンドと同様に、protobufメッセージを定義する.protoファイルにアクセスできることを前提としています。呼び出し構文はprotoc --decodeと同じです。たとえば、protoc --decodeddトリックを使用し、入力が Mesostask.info ファイルである場合、構文は次のようになります。

dd bs=1 skip=4 if=task.info 2>/dev/null | \
protoc --decode mesos.internal.Task \
                      -I MESOS_CODE/src -I MESOS_CODE/include \
                      MESOS_CODE/src/messages/messages.proto

また、protoc-decode-lenprefixを使用する場合のパラメーターは同じです。

cat task.info | \
protoc-decode-lenprefix --decode mesos.internal.Task \
                      -I MESOS_CODE/src -I MESOS_CODE/include \
                      MESOS_CODE/src/messages/messages.proto
11
erik.weathers

(複数?)長さプレフィックス protobufメッセージを含むバイナリファイルがある場合、protoc ‒‒decode_raw < fileプレフィックスが長さのため、解析できません。これを回避する簡単な方法は、ファイルを連続するメッセージに分割し、その後それぞれをprotocで変換することです。

私の見解:

var fs = File.OpenRead(filename));
var buffer = new byte[4096];
int size;
for (int part = 1; Serializer.TryReadLengthPrefix(fs, PrefixStyle.Base128, out size); part++) {
  long startPosition = fs.Position;
  using (var writer = File.OpenWrite(string.Format("{0}[{1}].pb", filename, part))) {
    for (int bytesToRead = size; bytesToRead > 0; ) {
      int bytesRead = fs.Read(buffer, 0, Math.Min(bytesToRead, buffer.Length));
      bytesToRead -= bytesRead;
      if (bytesRead <= 0) // End of file.
        break;
      writer.Write(buffer, 0, bytesRead);
    }
  }
}
9