なにこれ
gob バイナリの詳細仕様に言及してる記事はあんまりない, かつ公式ドキュメントにも網羅的な言及はあまりない
gob は一応ドキュメントが https://pkg.go.dev/encoding/gob#hdr-Encoding_Details あたりにあるけどわりとボトムアップな説明が多く全体像の理解に手間取った(個人の感想), そのため理解をふかめるにあたって簡単なメッセージのバイナリを目でパースしてみたりいろいろやってた
そんなこんなでバイナリをにらめて眼力パースしたメモが手元にあり, 毎度いちいち探し回るのが面倒なため記事にしておく!
自分向けメモなので仕様についてはあまり言及しない. 過去仕様を調べてるメモもあるのでそっちと合わせてみるといい感じかも
それではいってみよー
準備
以下みたいな go コードがあったとする
package main import ( "bytes" "encoding/gob" "fmt" "github.com/convto/bit" ) func main() { type item struct { Name string Price int } banana := item{Name: "banana", Price: 100} apple := item{Name: "apple", Price: 120} var buf bytes.Buffer enc := gob.NewEncoder(&buf) enc.Encode(banana) enc.Encode(apple) b := buf.Bytes() fmt.Println(bit.Dump(b)) }
ここで github.com/convto/bit
は稚作のライブラリで, バイナリを 0 or 1 にして出力できて io stream もくえるくんです
詳細気になる方は解説したことあるのでそっちをみてね
さて, さっきのコードを実行すると以下みたいな出力が得られる
00000000: 00100100 01111111 00000011 00000001 00000001 00000100 $..... 00000006: 01101001 01110100 01100101 01101101 00000001 11111111 item.. 0000000c: 10000000 00000000 00000001 00000010 00000001 00000100 ...... 00000012: 01001110 01100001 01101101 01100101 00000001 00001100 Name.. 00000018: 00000000 00000001 00000101 01010000 01110010 01101001 ...Pri 0000001e: 01100011 01100101 00000001 00000100 00000000 00000000 ce.... 00000024: 00000000 00001110 11111111 10000000 00000001 00000110 ...... 0000002a: 01100010 01100001 01101110 01100001 01101110 01100001 banana 00000030: 00000001 11111111 11001000 00000000 00001101 11111111 ...... 00000036: 10000000 00000001 00000101 01100001 01110000 01110000 ...app 0000003c: 01101100 01100101 00000001 11111111 11110000 00000000 le....
これで準備完了
型情報を目パース
gob は先頭に byte length, その後にその長さのメッセージ, その次にまた byte length ... という形式でメッセージが続きます
また, type と value でメッセージが分かれていて同一 stream で初めて登場するメッセージはそれより前に type 情報が流されます
このメッセージでは, 1バイト目を読むと 00100100
なのでそこから 36 bytes が型情報っぽいですね. まずは先頭から型をみてみましょう!
以下目パース結果です
00000000: 00100100 01111111 00000011 00000001 00000001 00000100 $..... 00000006: 01101001 01110100 01100101 01101101 00000001 11111111 item.. 0000000c: 10000000 00000000 00000001 00000010 00000001 00000100 ...... 00000012: 01001110 01100001 01101101 01100101 00000001 00001100 Name.. 00000018: 00000000 00000001 00000101 01010000 01110010 01101001 ...Pri 0000001e: 01100011 01100101 00000001 00000100 00000000 00000000 ce.... 00000024: 00000000
この部分が
00100100 len = 36 01111111 id=64 00000011 wireType = structType 00000001 struct type field 0 (common type で決め打ち) 00000001 commontype field 0 (name: string で決め打ち) 00000100 len = 4 01101001 01110100 01100101 01101101 val = item 00000001 commontype field 1 (id: int で決め打ち) 11111111 len = 1 (varint みたいな仕様で読むサイズを明示している。8byteまで) 10000000 id = 64 00000000 common type 終端 00000001 struct type field 1 ([]fieldType で決め打ち) 00000010 num = 2 00000001 fieldType[0] field 0 (name: string で決め打ち) 00000100 len = 4 01001110 01100001 01101101 01100101 name: Name 00000001 fieldType[0] field 1 (id: int できめうち) 00001100 id = 6 00000000 fieldType[0] 終端 00000001 fieldType[1] field 0 (name: string で決め打ち) 00000101 len = 5 01010000 01110010 01101001 01100011 01100101 name: Price 00000001 fieldType[1] field 1 (id: int で決め打ち) 00000100 id = 4 00000000 fieldType[1] 終端 00000000 structType 終端 00000000 wireType 終端
こう
自分用メモなので細かい仕様には言及しないけど, ちゃんと構造を完全に表現するための情報を持っている
value を目パース
00000024: 00001110 11111111 10000000 00000001 00000110 ..... 0000002a: 01100010 01100001 01101110 01100001 01101110 01100001 banana 00000030: 00000001 11111111 11001000 00000000 00001101 11111111 ...... 00000036: 10000000 00000001 00000101 01100001 01110000 01110000 ...app 0000003c: 01101100 01100101 00000001 11111111 11110000 00000000 le....
これが
00001110 len = 14 11111111 len = 1 (varint みたいな仕様で読むサイズを明示している。8byteまで) 10000000 id = 64 00000001 item(id = 64 type) field 0 00000110 len = 6 01100010 01100001 01101110 01100001 01101110 01100001 val = banana 00000001 item(id = 64 type) field 1 11111111 len = 1 (varint みたいな仕様で読むサイズを明示している。8byteまで) 11001000 val = 100 00000000 終端 00001101 len = 14 11111111 len = 1 (varint みたいな仕様で読むサイズを明示している。8byteまで) 10000000 id = 64 00000001 item(id = 64 type) field 0 00000101 len = 5 01100001 01110000 01110000 01101100 01100101 val = apple 00000001 item(id = 64 type) field 1 11111111 len = 1 (varint みたいな仕様で読むサイズを明示している。8byteまで) 11110000 val = 120 00000000 終端
こう
まとめ
gob の実際のバイナリについて, 簡単なメッセージのものを解釈してみた
gob 好きな人, あるいは大いなる事情によって gob を他の言語で喰いたい人の参考になれば嬉しいっす(見ればわかるけど型の取り扱いとかをうまいこと帳尻つければ他の言語でも全然食えます. 当たり前だけどね)