3. TLS 1.3/基本的なデータ構造

  1. TLS 1.3/暗号化通信
  2. TLS 1.3/プロトコルの概要
  3. TLS 1.3/基本的なデータ構造 ««« 現在のページ
目次の表示
    1. ネットワークバイトオーダー
    2. 表記方法
    3. 数値型
    4. ベクトル型
    5. 列挙型
    6. 構造体

この章ではRFC特有のデータ構造の表現方法と送受信データの構造について説明します。

ネットワークバイトオーダー

データブロックのサイズは1byte単位です。 TLSはネットワークのプロトコルなのでネットワークバイトオーダーです。つまり、複数バイトはビッグエンディアンのバイト順です。 例えばバイト列 01 02 を整数に変換するときは、$((01 \ll 8) + 02) = 256 + 2 = 258$ となります。

value = (byte[0] << 8*(n-1)) | (byte[1] << 8*(n-2)) | ... | byte[n-1];

表記方法

  • コメント : /* で始まり */ で終わります。
  • オプション : 任意の項目は [[ ]] (二重角括弧) で囲みます。
  • opaque : opaque型はバイト列を持つ型です。
  • エイリアス (別名) : 既存の型Tを使って別名の型T'は T T'; と定義します。

数値型

基本的な数値データ型は、符号なしバイト (uint8) です。 また、16, 24, 32, 64ビットの数値は、連続した固定長のバイト列から構成され、次のように定義されます。

uint8 uint16[2];
uint8 uint24[3];
uint8 uint32[4];
uint8 uint64[8];

ベクトル型

型Tの固定長ベクトルである新しい型T'は [ ] を使って T T'[n]; と定義します。 ただし、nは型T'が必要なバイト数で、必ず型Tのサイズの倍数になります。

次の例は、3byteのバイト列 (opaque) を持つDatum型と、3つのDatumを持つ合計で9バイトのData型を定義しています。 プログラミング言語の配列とは異なる点に注意してください。

opaque Datum[3];  // 合計で3byteのDatum型
Datum Data[9];    // 合計で9byteのData型

型Tの可変長ベクトルである新しい型T'は < > を使って T'<a..b> と定義します。

opaque mandatory<3..10>;

型mandatoryは3〜10byteの内容を持つデータで、データ長は最大10なのでデータ長フィールドはuint8を使います。 データ長フィールドの型は、データ長が最大255のときuint8、最大65535のときuint16、最大4294967295のときuint32、… となります。 上記の例で、mandatory型のデータが 01 02 03 04 05 のとき、データ長フィールドを先頭に加えた 05 01 02 03 04 05 が実際のバイト列となります。

列挙型

列挙型は同じタイプの列挙をするときに使います。 次の列挙型Teでは、Te.e1 の値が v1、Te.e2 の値が v2、…となっています。 また、最後の要素名がない (n) だけの部分は、その列挙の最大値を表します。 列挙の要素が使用するバイト数は、列挙の要素の最大値のを表すために必要なバイト数になります。

enum { e1(v1), e2(v2), ... , en(vn) [[, (n)]] } Te;

次の例は、最大値が7のためuint8の列挙Colorと、最大値が32000のためuint16の列挙Tasteを定義したものです。 次の例でバイト列に変換するときの例として Color.red は 03、Taste.bitter は 00 04 となります。

enum { red(3), blue(5), white(7) } Color;
enum { sweet(1), sour(2), bitter(4), (32000) } Taste;

構造体

構造体は複数の型を格納できる型で、C言語の構文と同じように定義します。

struct {
    T1 f1;
    T2 f2;
    ...
    Tn fn;
} T;

構造体のフィールドは常に固定値(定数)の場合は「=」を使って固定値を代入します。

struct {
    T1 f1 = 8;  /* T.f1 は常に固定値 8 */
    T2 f2;
} T;

TLS 1.3プロトコルで使われる構造体によって、構造体のフィールドの値によって構造体の後続のフィールドの構造が変わるときや、処理者がクライアントかサーバかで構造体の構造が変わる場合があります。 その場合は select case で条件ごとの構造を定義します。

enum { apple(0), orange(1) } VariantTag;

struct { uint16 number; opaque string<0..10>; } V1;
struct { uint32 number; opaque string[10];    } V2;

struct {
    VariantTag type;
    select (VariantRecord.type) {
        case apple:  V1;
        case orange: V2;
    };
} VariantRecord;