[インデックス 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の比較において特別な動作があります:
-
nil sliceと空のslice:
- nil slice:
var x []int
(値は nil) - 空のslice:
y := []int{}
(長さ0の非nilスライス)
- nil slice:
-
reflect.DeepEqualの判定:
- 両方がnilまたは両方が非nilである必要がある
- 内部的には異なる表現を持つため、nil sliceと空のsliceは等価でない
TLSハンドシェイクメッセージの構造
TLS(Transport Layer Security)プロトコルでは、クライアントとサーバー間でハンドシェイクを行い、セキュアな通信を確立します。主要なメッセージタイプには以下があります:
- ClientHello: クライアントが送信する最初のメッセージ
- ServerHello: サーバーの応答メッセージ
- Certificate: 証明書の交換
- ClientKeyExchange: クライアントによる鍵交換
- Finished: ハンドシェイクの完了確認
技術的詳細
解決策の実装
このコミットでは、reflect.DeepEqual
への依存を避けるため、各TLSハンドシェイクメッセージ構造体に独自のequal
メソッドを追加しました:
-
equal メソッドの追加:
- 各メッセージタイプに対してカスタムの等価性比較メソッドを実装
- 型安全性を保証(型アサーションによる検証)
- フィールドごとの適切な比較ロジック
-
補助関数の実装:
eqUint16s
: uint16スライスの比較eqStrings
: 文字列スライスの比較eqByteSlices
: バイトスライスの多次元配列比較
-
テストの修正:
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
メソッドは以下の原則に従って設計されています:
- 型チェック: 最初に型アサーションを行い、異なる型の場合は即座にfalseを返す
- フィールドごとの比較: 各フィールドを適切な方法で比較
- バイトスライスの比較:
bytes.Equal
を使用してnilと空のスライスを区別しない比較 - カスタム比較: 複雑な型(スライス配列など)には専用の比較関数を使用
比較関数の実装詳細
- eqUint16s: 長さチェック後、要素ごとに比較
- eqStrings: 文字列スライスの要素ごと比較
- eqByteSlices: 2次元バイトスライスの比較で、各要素を
bytes.Equal
で比較
この設計により、reflect.DeepEqual
の動作変更に影響されない、独立した等価性比較が可能になりました。
関連リンク
- reflect.DeepEqual ドキュメント
- bytes.Equal ドキュメント
- crypto/tls パッケージ
- TLS プロトコル RFC 5246
- Go Issue #4133: reflect: document that DeepEqual distinguishes nil and empty slice