Protocol Buffersのwire formatを直接触ってみる
Protocol Buffersは基本的にwire formatというバイナリ形式でシリアライズされます。
wire formatを直接触ってどうなるかをGoで試してみました。
protoは下記の定義を使いました。
syntax = "proto3";
option go_package = "/my_proto";
enum Status {
STATUS_ACTIVE = 0;
STATUES_INACTIVE = 1;
}
message Param1 {
int32 id = 1;
string name = 2;
Status status = 3;
}
message Param2 {
int32 id_2 = 1;
string name_2 = 2;
Status status_2 = 3;
}
wire formatはfieldの型が同じであれば基本マッピングできるはずなので、Param1のメッセージをParam2であってもシリアライズできるはずなので、下記コードは動きます。
package main
import (
"fmt"
"io"
"os"
"proto_sample/my_proto"
"google.golang.org/protobuf/proto"
)
func main() {
p1 := my_proto.Param1{
Id: 1,
Name: "gopher",
Status: my_proto.Status_STATUES_INACTIVE,
}
fmt.Println("P1 Unmarshaled data:", p1.String())
file, err := os.Create("output.bin")
if err != nil {
fmt.Println("ファイルの作成に失敗しました:", err)
return
}
defer file.Close()
data, err := proto.Marshal(&p1)
if err != nil {
fmt.Println("データのマーシャリングに失敗しました:", err)
return
}
_, err = file.Write(data)
if err != nil {
fmt.Println("ファイルへの書き込みに失敗しました:", err)
return
}
fr, err := os.Open("output.bin")
if err != nil {
return
}
defer fr.Close()
fileData, err := io.ReadAll(fr)
if err != nil {
return
}
p2 := my_proto.Param2{}
proto.Unmarshal(fileData, &p2)
fmt.Println("Unmarshaled data:", p2.String())
}
結果は下記の通り
p1 Unmarshaled data: id:1 name:"gopher" status:STATUES_INACTIVE
p2 Unmarshaled data: id_2:1 name_2:"gopher" status_2:STATUES_INACTIVE
field名を変えてもシリアライズは可能です。
生成したバイナリを見てみます。
% xxd output.bin
00000000: 0801 1206 676f 7068 6572 1801 ....gopher..
wire formatはkey-valueになっていて、keyの部分は、TLVになっていて、field_numberとwire typeなどが格納されている。
ロジックは下記を参照してください。
最初の08
は field_number 1で wire_type VARIANT(0)を表し、次の01
はint32の値を示す。
12
は field_number 2で wire_type LEN(2)を表していて、次の 06
は長さ、そして6バイトの gopher
の文字が入ります。
gopherは ASCII文字コードで表すと 676f 7068 6572
。
18
は field_number 3で wire_type VARIANT(0)を表していて、次の01
はenumの1を格納してあります。
ファイルに出力したwire formatの内容を書き換えて読み込んでみます。
% xxd output_1.bin
00000000: 0803 1206 676f 7068 6572 1801 ....gopher.
2バイト目を 01 から 03に変えています。
これを読み込んでみます。
package main
import (
"fmt"
"io"
"os"
"proto_sample/my_proto"
"google.golang.org/protobuf/proto"
)
func main() {
fr, err := os.Open("output_1.bin")
if err != nil {
return
}
defer fr.Close()
fileData, err := io.ReadAll(fr)
if err != nil {
return
}
p2 := my_proto.Param2{}
proto.Unmarshal(fileData, &p2)
fmt.Println("p2 Unmarshaled data:", p2.String())
}
結果は下記のように、id_2の値が3に変化しています。
p2 Unmarshaled data: id_2:3 name_2:"gopher" status_2:STATUES_INACTIVE
整数値はBase 128 Varints方式でエンコーディングされるので、もっと大きい値で試すとよかったかもですが、一旦シンプルに確認するために小さい値で実行しました。
Discussion