外出先での電報:パート1、スキームの解析

私のお気に入りのメッセンジャーのために高品質のクライアントを外出先で書きたいという願望は長い間熟してきましたが、ほんの1か月前に、その時が来たと判断し、これには十分な資格がありました。





開発はまだ進行中です(そして完全にオープンソースです)が、魅力的な道はすでにプロトコルの完全な理解の欠如から比較的安定したクライアントへと移っています。一連の記事では、私が直面した課題とその対処方法について説明します。私が適用した手法は、スキーマを使用して任意のバイナリプロトコル用のクライアントを開発するときに役立ちます。






タイプ言語

タイプ言語またはTL、プロトコル記述スキームから始めましょうフォーマットの説明については詳しく説明しません。Habréにはすでに分析があります。簡単に説明します。これはgRPCにいくぶん似ており、クライアントとサーバー間の相互作用スキーム(データ構造と一連のメソッド)について説明しています。





タイプの説明の例を次に示します。





error#1fbadfee code:int32 message:string = Error;
      
      



ここでは1fbadfee



、これはid型で、error



その名前は、コードとメッセージがフィールドであり、Error



これはクラス名です。





メソッドは同様の方法で記述されますが、タイプ名の代わりにメソッド名があり、クラスの代わりに結果タイプがあります。





sendPM#3faceff text:string habrauser:string = Error;	
      
      



これは、メソッドsendPM



が引数text



とを受け取りhabrauser



Error



以前に説明されたバリアント(コンストラクター)を返すことを意味しますerror#1fbadfee







, - . : ad-hoc, .. . participle, go, . ad-hoc .





, , , . : , , .





, . Definition



, :





func TestDefinition(t *testing.T) {
	for _, tt := range []struct {
		Case       string
		Input      string
		String     string
		Definition Definition
	}{
		{
			Case:  "inputPhoneCall",
			Input: "inputPhoneCall#1e36fded id:long access_hash:long = InputPhoneCall",
			Definition: Definition{
				ID:   0x1e36fded,
				Name: "inputPhoneCall",
				Params: []Parameter{
					{
						Name: "id",
						Type: bareLong,
					},
					{
						Name: "access_hash",
						Type: bareLong,
					},
				},
				Type: Type{Name: "InputPhoneCall"},
			},
		},
    // ...
  } {
		t.Run(tt.Case, func(t *testing.T) {
			var d Definition
			if err := d.Parse(tt.Input); err != nil {
				t.Fatal(err)
			}
			require.Equal(t, tt.Definition, d)
		})
  } 
}  
      
      



, Flag



( , ), .





, , . :





	t.Run("Error", func(t *testing.T) {
		for _, invalid := range []string{
			"=0",
			"0 :{.0?InputFi00=0",
		} {
			t.Run(invalid, func(t *testing.T) {
				var d Definition
				if err := d.Parse(invalid); err == nil {
					t.Error("should error")
				}
			})
		}
	})
      
      



testdata

. _testdata



: , , go .





Sample.tl _testdata :





func TestParseSample(t *testing.T) {
	data, err := ioutil.ReadFile(filepath.Join("_testdata", "Sample.tl"))
	if err != nil {
		t.Fatal(err)
	}
	schema, err := Parse(bytes.NewReader(data))
	if err != nil {
		t.Fatal(err)
	}
  // ...
}
      
      



go , , filepath.Join



-.





(golden)

"golden files". , . , ( -update



). , . goldie .





func TestParser(t *testing.T) {
	for _, v := range []string{
		"td_api.tl",
		"telegram_api.tl",
		"telegram_api_header.tl",
		"layer.tl",
	} {
		t.Run(v, func(t *testing.T) {
			data, err := ioutil.ReadFile(filepath.Join("_testdata", v))
			if err != nil {
				t.Fatal(err)
			}
			schema, err := Parse(bytes.NewReader(data))
			if err != nil {
				t.Fatal(err)
			}
			t.Run("JSON", func(t *testing.T) {
				g := goldie.New(t,
					goldie.WithFixtureDir(filepath.Join("_golden", "parser", "json")),
					goldie.WithDiffEngine(goldie.ColoredDiff),
					goldie.WithNameSuffix(".json"),
				)
				g.AssertJson(t, v, schema)
			})
		})
	}
}
      
      



, json ( json). -update



, , _golden



.





(, json ) , .





Decode-Encode-Decode

, , decode-encode-decode, .





String() string



:





// Annotation represents an annotation comment, like //@name value.
type Annotation struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

func (a Annotation) String() string {
	var b strings.Builder
	b.WriteString("//")
	b.WriteRune('@')
	b.WriteString(a.Name)
	b.WriteRune(' ')
	b.WriteString(a.Value)
	return b.String()
}
      
      



, strings.Builder, String()



.





, , .





Fuzzing

() . , , (coverage-guided fuzzing). go go-fuzz . ( ) , . , syzkaller, go, Linux .





, , , , .





, Definition:





// +build fuzz

package tl

import "fmt"

func FuzzDefinition(data []byte) int {
	var d Definition
	if err := d.Parse(string(data)); err != nil {
		return 0
	}

	var other Definition
	if err := other.Parse(d.String()); err != nil {
		fmt.Printf("input: %s\n", string(data))
		fmt.Printf("parsed: %#v\n", d)
		panic(err)
	}

	return 1
}

      
      



, .





Decode-encode-decode-encode

We need to go deeper. :













  1. (2)





  2. (3)





  3. (4) (2)





(4) (2) , .. - . , .





go-fuzz

Denial of Service , .. OOM. , go-fuzz , , .





corpus, , ( crashers, , , ). crashers , 0, . , , corpus , .





, go, , , .





, , , - . (STUN, TURN, SDP, MTProto, ...) .





, - . , , ( ) Telegram go:









  • ( )









  • ネットワーク通信テスト(ユニット、e2e)





  • 副作用(時間、タイムアウト、PRNG)を伴うテスト作業





  • CI、またはマージボタンを押すのが怖くないようにパイプラインを設定します





また、プロジェクトに参加したプロジェクト参加者のおかげで、彼らがいなければそれははるかに困難になるでしょう。








All Articles