Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 10387] ファイルの概要

コミット

Author: Russ Cox
Date: Mon Nov 14 15:21:08 2011 -0500
Hash: 1df62ca638107df5a51f969fc411a6bd091518fd
Subject: crypto/tls: fix handshake message test

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/1df62ca638107df5a51f969fc411a6bd091518fd

元コミット内容

変更ファイル数: 2ファイル
変更行数: 164行追加、1行削除
対象ファイル:

  • src/pkg/crypto/tls/handshake_messages.go (161行追加)
  • src/pkg/crypto/tls/handshake_messages_test.go (4行変更)

このコミットは、Go 1.0リリース前の2011年における重要な修正で、TLSハンドシェイクメッセージのテストがreflect.DeepEqualの動作変更に対応するため、カスタムの等価性比較メソッドを導入しました。

変更の背景

このコミットが作成された背景には、Goのreflect.DeepEqual関数の動作変更があります。コミットメッセージによると、「This test breaks when I make reflect.DeepEqual distinguish empty slices from nil slices」とあり、reflect.DeepEqualがnil sliceと空のsliceを区別するように変更されたことが原因でした。

2011年当時、Goはまだバージョン1.0のリリース前であり、言語仕様やライブラリの動作が固まっていない状況でした。このような基本的な動作変更は、後に多くのコードに影響を与える可能性があったため、慎重に対応する必要がありました。

前提知識の解説

reflect.DeepEqualの動作

reflect.DeepEqualは、Goにおいて深い等価性を比較するための関数です。しかし、sliceの比較において特別な動作があります:

  1. nil sliceと空のslice

    • nil slice: var x []int (値は nil)
    • 空のslice: y := []int{} (長さ0の非nilスライス)
  2. reflect.DeepEqualの判定

    • 両方がnilまたは両方が非nilである必要がある
    • 内部的には異なる表現を持つため、nil sliceと空のsliceは等価でない

TLSハンドシェイクメッセージの構造

TLS(Transport Layer Security)プロトコルでは、クライアントとサーバー間でハンドシェイクを行い、セキュアな通信を確立します。主要なメッセージタイプには以下があります:

  • ClientHello: クライアントが送信する最初のメッセージ
  • ServerHello: サーバーの応答メッセージ
  • Certificate: 証明書の交換
  • ClientKeyExchange: クライアントによる鍵交換
  • Finished: ハンドシェイクの完了確認

技術的詳細

解決策の実装

このコミットでは、reflect.DeepEqualへの依存を避けるため、各TLSハンドシェイクメッセージ構造体に独自のequalメソッドを追加しました:

  1. equal メソッドの追加

    • 各メッセージタイプに対してカスタムの等価性比較メソッドを実装
    • 型安全性を保証(型アサーションによる検証)
    • フィールドごとの適切な比較ロジック
  2. 補助関数の実装

    • eqUint16s: uint16スライスの比較
    • eqStrings: 文字列スライスの比較
    • eqByteSlices: バイトスライスの多次元配列比較
  3. テストの修正

    • reflect.DeepEqualの代わりにequalメソッドを使用
    • testMessageインターフェイスにequalメソッドを追加

実装の特徴

  • 型安全性: 各equalメソッドは型アサーションを使用して型の整合性を確認
  • パフォーマンス: bytes.Equalや専用の比較関数を使用することで効率的な比較を実現
  • 保守性: 各メッセージタイプに固有の比較ロジックを分離

コアとなるコードの変更箇所

1. bytes パッケージのインポート追加

import "bytes"

2. 各メッセージタイプへのequalメソッド追加

clientHelloMsg(行35-52):

func (m *clientHelloMsg) equal(i interface{}) bool {
    m1, ok := i.(*clientHelloMsg)
    if !ok {
        return false
    }
    return bytes.Equal(m.raw, m1.raw) &&
        m.vers == m1.vers &&
        bytes.Equal(m.random, m1.random) &&
        // ... その他のフィールド比較
}

3. 補助関数の実装

eqUint16s(行236-246):

func eqUint16s(x, y []uint16) bool {
    if len(x) != len(y) {
        return false
    }
    for i, v := range x {
        if y[i] != v {
            return false
        }
    }
    return true
}

4. テストの修正

handshake_messages_test.go(行292-293):

// Before
if !reflect.DeepEqual(m1, m2) {
// After
if !m1.equal(m2) {

コアとなるコードの解説

equalメソッドの設計思想

equalメソッドは以下の原則に従って設計されています:

  1. 型チェック: 最初に型アサーションを行い、異なる型の場合は即座にfalseを返す
  2. フィールドごとの比較: 各フィールドを適切な方法で比較
  3. バイトスライスの比較: bytes.Equalを使用してnilと空のスライスを区別しない比較
  4. カスタム比較: 複雑な型(スライス配列など)には専用の比較関数を使用

比較関数の実装詳細

  • eqUint16s: 長さチェック後、要素ごとに比較
  • eqStrings: 文字列スライスの要素ごと比較
  • eqByteSlices: 2次元バイトスライスの比較で、各要素をbytes.Equalで比較

この設計により、reflect.DeepEqualの動作変更に影響されない、独立した等価性比較が可能になりました。

関連リンク

参考にした情報源リンク