この付録では、XDR プロトコル言語の仕様について説明します。この付録では次の内容について説明します。
外部データ表現 (external data representation: XDR) は、データの記述と符号化の標準規約です。XDR プロトコルは、異なるコンピュータアーキテクチャ間のデータ伝送に利用できます。これまで、種々のマシン間のデータ通信に使用されてきました。XDR は、ISO の参照モデルのプレゼンテーション層 (第 6 層) に対応するもので、X.409 「ISO 抽象構文表記」におおむね従っています。XDR と X.409 との一番大きな違いは、XDR が暗黙的データ型を使用するのに対して、X.409 は明示的データ型を使用する点です。
XDR では、言語を使用してデータ形式を記述しますが、データの記述のためにだけしか使用できません。XDR はプログラミング言語ではありません。XDR 言語を使用すると、複雑なデータ形式も簡潔に表現できます。XDR 言語は C 言語に似ています。RPC や NFS のようなプロトコルでは、XDR でデータ形式を記述しています。
XDR 標準規約では、バイトまたは (オクテット) は移植可能な 8 ビットデータとみなしています。
この章では、データの説明や比較のときに、グラフィックボックス表現を使用します。ほとんどの場合、各ボックスが 1 バイトを表します。全項目を表現するには、複数の 4 バイト(32ビット) のデータが必要です。各バイトには 0 から n -1 の番号が付けられています。 バイトは、バイト m+1 の前に常にバイト m が位置するという関係が保たれる、複数のバイトストリームへと読み書きされます。n バイトのデータの後には、0 〜 3 個の余分なゼロバイト r が付加されて、全体のバイト数が 4 の倍数になるように調整されます。次の図で、ボックスの間の省略記号 (...) の場所には必要に応じて 0 以上の追加バイトが入ります。
XDR のブロックサイズの選択はさまざまな条件の兼ね合いで決まります。2 のような小さな値を選択して符号化データを小さくすると、そのようなデータ境界を使用しないマシンでは整合の問題が起こります。8 のような大きな値にすると、事実上すべてのマシンでデータ整合が可能になりますが、符号化データが大きくなり過ぎます。妥協案として 4 が選ばれました。4 は適度な大きさでほとんどのアーキテクチャに対応できます。
基本ブロックサイズが 4 であるということは、コンピュータで標準 XDR が使用できないことを意味するわけではありません。各データ項目のオーバヘッドが、4 バイト (32 ビット) アーキテクチャのマシンより大きくなるという意味です。4 という値は符号化データを妥当なサイズに押さえる意味でも適当な値です。
どのマシンでも同じデータは同じ値に符号化されなければ、符号化データを比較したりチェックサムを取ったりできません。したがって、可変長データの最後は、ゼロデータでパディングしなければなりません。
XDR 標準規約で定義されているデータ型を説明します。
XDR 言語でどのようにデータ型を宣言するかを示します。
符号化方法を図示します。
XDR 言語で使用できる各データ型の宣言方法を示します。大小記号による括弧 (< と >) は可変長のデータシーケンスを示し、角括弧 [ と ]) は固定長のデータシーケンスを示します。n、m、r は整数を表します。XDR 言語の詳細仕様については、XDR 言語仕様の節を参照してください。
一部のデータ型については、具体的なデータ記述例も示しました。 より詳しいデータ記述例については、XDR データ記述 を参照してください。
XDR の符号付き整数は、[-2147483648, 2147483647] の範囲の整数が符号化された 32 ビットデータです。整数は 2 の補数で表されます。最上位バイト (MSB) と最下位バイト (LSB) はそれぞれバイト 0 とバイト 3 です。
整数は次のように宣言します。
int identifier;
XDR の符号なし整数は、[0, 4294967295] の範囲の正の整数が符号化された 32 ビットデータです。整数は符号なしの 2 進数で表されます。最上位バイト (MSB) と最下位バイト (LSB) はそれぞれバイト 0 とバイト 3 です。
符号なし整数は次のように宣言します。
unsigned int identifier;
列挙型のデータ表現方法は符号付き整数と同じで、整数のサブセットを記述する際に便利です。列挙型の符号化は 符号付き整数の符号化 に示したものと同じです。
列挙型は次のように宣言します。
enum {name-identifier = constant , ... } identifier;
たとえば、列挙型を使用して赤、黄、青の 3 色を次のように表すことができます。
enum {RED = 2, YELLOW = 3, BLUE = 5} colors;
列挙型に、enum
宣言で指定されていない整数を代入しないでください。
ブール型は、標準規約の明示型に対応するための型で、よく使用される重要なデータ型です。ブール型には、整数の 0 と 1 を使用します。ブール型の符号化は 符号付き整数の符号化 に示したものと同じです。
ブール型は次のように宣言します。
bool identifier;
これは、次の宣言と同じです。
enum {FALSE = 0, TRUE = 1} identifier;
標準規約では 64 ビット (8 バイト) の整数として hyper
int
と unsigned
hyper
int
を定義しています。その表現方法は明らかに、以前に説明した integer
と unsigned integer
を拡張したものです。hyper
整数は 2 の補数で表されます。最上位バイト (MSB) と最下位バイト (LSB) はそれぞれバイト 0 とバイト 7 です。
Hyper 整数は次のように宣言します。
hyper int identifier; unsigned hyper int identifier;
標準規約では、浮動小数点型 float
(32 ビット、すなわち 4 バイト) を定義しています。符号化方法は、正規化された単精度浮動小数点に関するIEEE 標準規約 [1] に従います。単精度浮動小数点は次の 3 つのフィールドで記述されます。
S: 数値の符号を表します。値 0 は正、1 は負を表します。 このフィールドには 1 ビットが入ります。
E: 数値の指数部 (基数は 2) を表します。このフィールドには 8 ビットが入ります。指数部の値を 127 だけバイアスした値が入っています。
F: 数値の仮数部 (基数は 2) を表します。このフィールドには 23 ビットが入ります。
したがって、浮動小数点型の値は次のように記述されます。
(-1)**S * 2**(E-Bias) * 1.F
単精度浮動小数点データは次のように宣言します。
float identifier;
倍精度浮動小数点データは次のように宣言します。
double identifier;
整数の最上位バイトと最下位バイトがバイト 0 とバイト 3 であるのと同様に、倍精度浮動小数点型の値の最上位ビットと最下位ビットはビット 0 とビット 63 になります。S、E、F の各フィールドの開始ビット、最上位ビット、オフセットはそれぞれ 0、1、12 になります。
これらのオフセットは論理的ビット位置を示すもので、物理的位置を示すものではありません。物理的位置は媒体によって異なります。
符号付きゼロ、符号付き無限大 (オーバーフロー)、正規化されない値 (アンダーフロー) の符号化については、IEEE 標準規約を参照してください。IEEE 標準規約によると、NaN (not a number) は、システムごとに異なるため外部表現では使用できません。
標準規約では、4 倍精度浮動小数点型 quadruple
(128 ビット、すなわち 16 バイト) を定義しています。符号化方法は、正規化された 4 倍精度浮動小数点に関する IEEE 標準規約 [1] に従います。標準規約では、4 倍精度浮動小数点は次の 3 つのフィールドに符号化されます。
S: 数値の符号を示します。値 0 は正、1 は負を表します。このフィールドには 1 ビットが入ります。
E: 数値の指数部 (基数は 2) を表します。このフィールドには 15 ビットが入ります。指数部の値を 16383 だけバイアスした値が入っています。
F: 数値の仮数部 (基数は 2) を表します。このフィールドには 111 ビットが入ります。
したがって、4 倍精度浮動小数点型の値は次のように記述されます。
(-1)**S * 2**(E-Bias) * 1.F
quadruple identifier;
整数の最上位バイトと最下位バイトがバイト 0 とバイト 3 であるのと同様に、4 倍精度浮動小数点型の値の最上位ビットと最下位ビットはビット0 とビット 127 になります。S、E、F の各フィールドの開始ビット、最上位ビット、オフセットはそれぞれ 0、1、16 になります。これらのオフセットは論理的ビット位置を示すもので、物理的位置を示すものではありません。物理的位置は媒体によって異なります。
符号付きゼロ、符号付き無限大 (オーバーフロー)、正規化されない値 (アンダーフロー) の符号化については、IEEE 標準規約を参照してください。 IEEE 標準規約によると、NaN (not a number) は、システムごとに異なるため外部表現では使用できません。
内容を解釈しない固定長データをマシン間で受け渡さなければならない場合があります。このデータを隠されたデータといいます。
隠されたデータは次のように宣言します。
opaque identifier[n];
この宣言内で、定数 n は隠されたデータを格納するため必要となる静的なバイト数です。n バイトの隠されたデータの後には、0 〜 3 個の余分なゼロバイト r が付加されて、隠されたオブジェクト全体のバイト数が 4 の倍数になるように調整されます。
n バイトの隠されたデータの後には、0 〜 3 個の余分なゼロバイト r が付加されて、隠されたオブジェクト全体のバイト数が 4 の倍数になるように調整されます。
標準規約では、可変長 (カウント付き) の隠されたデータも定義されます。このようなデータは、n バイトの任意のバイトシーケンス (バイト番号は 0 から n-1) が、次に示すように符号なし整数 w に符号化され、その後に、n バイトのシーケンスが続くように定義されています。
シーケンス内のバイト b は必ずバイト b+1 の直前に位置し、シーケンス内のバイト 0 はシーケンスの長さの次に位置しています。n バイトのデータの後には、0 〜 3 個の余分なゼロバイト r が付加されて、全体のバイト数が 4 の倍数になるように調整されます。
可変長の隠されたデータは次のように宣言します。
opaque identifier<m>;
または
opaque identifier<>;;
定数 m は、シーケンスに含まれるバイト数の上限を示します。2 番目の宣言のように、m を指定しないと、最大バイト数は (2**32) - 1 となります。たとえば、ファイル伝送プロトコルで最大データ伝送サイズを 8192 バイトとするには、次のように宣言します。
opaque filedata<8192>;
標準規約では、n バイトの ASCII 文字列 (バイト番号は 0〜 n-1) を次のように定義します。バイト数が符号なし整数 n に符号化されたものに、n バイトの文字列が続きます。文字列のバイト b は必ずバイト b+1 の直前に位置し、文字列のバイト 0 は文字列の長さの次に位置しています。n バイトのデータの後には、0 〜 3 個の余分なゼロバイト r が付加されて、全体のバイト数が 4 の倍数になるように調整されます。
カウント付きバイト文字列は次のように宣言します。
string object<m>;
または
string object<>;
定数 m は、文字列に含まれるバイト数の上限を示します。2 番目の宣言のように、m を指定しないと、最大バイト数は (2**32) - 1 となります。定数 m は、通常プロトコル仕様で決められています。たとえば、ファイル伝送プロトコルでファイル名を最大 255 バイトとするには、次のように宣言します。
string filename<255>;
固定長配列の要素番号は 0 〜 n-1 で、個々の配列要素が 0 〜 n-1 の番号順に符号化されます。各配列要素のバイト数は 4 の倍数になっています。全要素が同一のデータ型であっても、要素のサイズが異なることがあります。たとえば、文字列の固定長配列の場合、要素のデータ型はすべて string
型ですが、個々の要素の長さは異なります。
各要素のデータ型がすべて同じである固定長配列は、次のように宣言します。
type-name identifier[n];
可変長配列をカウント付きで符号化することによって、固定長の要素と同じように符号化できます。要素カウント n 符号なし整数に続けて、要素番号 0 〜 n-1 の順に各要素が符号化されます。
可変長配列は次のように宣言します。
type-name identifier<m>;
または
type-name identifier<>;
定数 m は、配列に含まれる要素数の上限を示します。m を指定しないと、最大要素数は (2**32) - 1 とみなされます。
構造体の構成要素は、構造体の宣言で並べた順に符号化されます。各構成要素のサイズはそれぞれ異なる可能性がありますが、各々が 4 の倍数に調整されます。
構造体は次のように宣言します。
struct { component-declaration-A; component-declaration-B; ... } identifier;
識別型の共用体には、要素識別子に続いて、あらかじめ配置された一連のデータ型から要素識別子の値に応じて選択されたものが入ります。要素識別子のデータ型は、int
、unsigned int
、bool
などの列挙型、のいずれかです。共用体の構成要素の型を“アーム”といい、符号化を暗黙に示す要素識別子に続けて記述されます。
識別型の共用体は次のように宣言します。
union switch (discriminant-declaration) { case discriminant-value-A: arm-declaration-A; case discriminant-value-B: arm-declaration-B; ... default: default-declaration; } identifier;
キーワード case の後には、要素識別子として指定できる値を書きます。デフォルトアームは省略できますが、その場合は要素識別子として定義されていない値を持つものを正しく符号化できません。各アームのサイズは、それぞれ 4 の倍数になります。
識別型の共用体は、要素識別子に続けて、それに対応するアームが符号化されます。
XDR の void
型は 0 バイトのデータです。void
は、入力データまたは出力データを持たない操作を記述するときに使用します。また、共用体で、アームによってデータを持つものと持たないときがある場合にも使用できます。
void の宣言は次のように簡単です。
void;
const
型は定数を表すシンボル名を定義するのに使用します。const
型は、データを宣言するものではありません。シンボル定数は、通常の定数を使用できるところならどこでも使用できます。
次の例では、12 を表すシンボル定数 DOZEN を定義します。
const DOZEN = 12;
定数は次のように宣言します。
const name-identifier = n;
typedef
はデータを宣言するものではなく、新たな識別子でデータを宣言できるようにするためのものです。typedef
の構文を次に示します。
typedef declaration;
typedef
の宣言部分の変数名が、新たな型名になります。次の例では、既存の型 egg とシンボル定数 DOZEN を使用して、eggbox という新たな型を定義しています。
typedef egg eggbox[DOZEN];
新たな型名で宣言した変数は、typedef
で変数として見た場合の型と同じ型を持ちます。したがって、次の 2 つの宣言は同じ型の変数 fresheggs: を宣言しています。
eggbox fresheggs; egg fresheggs[DOZEN];
typedef
に struct
、enum
、union
の定義が含まれるときは、同じ型を定義するのに、別のより望ましい構文が使用できます。一般に、typedef
は次の形式で指定します。
typedef <<struct, union, or enum definition>> identifier;
この形式から typedef
を取り去り、最後の識別子を struct
、enum
、union
のキーワードの後に置くこともできます。bool
型を宣言する 2 つの方法を次に示します。
typedef enum {/* typedef を使用 */ FALSE = 0, TRUE = 1 } bool; enum bool {/* 望ましい方法 */ FALSE = 0, TRUE = 1 };
最初の構文では宣言の最後まで見ないと新しい型名がわからないので、後の構文の方が望ましい方法です。
オプションデータ共用体は、次のような特殊な構文を持ち、非常によく使用されます。次のように宣言されます。
type-name *identifier;
この構文は次の共用体と同じです。
union switch (bool opted) { case TRUE: type-name element; case FALSE: void; } identifier;
このオプションデータ構文は、次の可変長配列宣言とも同じです。これはブール型 opted を配列の長さと解釈できるためです。
type-name identifier<1>;
オプションデータは再帰的データ構造体、たとえば、リンクリストやツリーの宣言に便利です。
この節では XDR 言語の仕様について説明します。
この節では、XDR 言語を修正バッカス-ナウア記法で記述します。その表記規則を次に簡単に説明します。
特殊文字として、|、(、)、[、]、* を使用する
終端記号は、引用符 (") で囲んだ文字列または文字とする
非終端記号は、非特殊文字からなる文字列で、イタリックで表示する
代替項目は縦棒 (|) で区切って並べる
省略可能な項目は角括弧で囲む
項目をまとめてグループ化するときは、括弧で囲む
項目の後に * が付いている場合は、その項目の 0 回以上の繰り返しを表す
たとえば、次のパターンを考えてみます。
"a " "very" (", " " very")* [" cold " "and"] " rainy " ("day" | "night")
このパターンには、次の文字列を始めとして無数の文字列が一致します。
a very rainy day a very, very rainy day a very cold and rainy day a very, very, very cold and rainy night
仕様では次の決まりが使用されます。
空白は項目と項目の区切りに使用し、意味を持たない
定数は 1 つ以上の 10 進数のシーケンスで、次のコーディング例のように、オプションで先頭にマイナス記号 (-) を付けることができる。
Syntax Information declaration: type-specifier identifier | type-specifier identifier "[" value "]" | type-specifier identifier "<" [ value ] ">" | "opaque" identifier "[" value "]" | "opaque" identifier "<" [ value ] ">" | "string" identifier "<" [ value ] ">" | type-specifier "*" identifier | "void" value: constant | identifier type-specifier: [ "unsigned" ] "int" | [ "unsigned" ] "hyper" | "float" | "double" | "quadruple" | "bool" | enum-type-spec | struct-type-spec | union-type-spec | identifier enum-type-spec: "enum" enum-body enum-body: "{" ( identifier "=" value ) ( "," identifier "=" value )* "}" struct-type-spec: "struct" struct-body struct-body: "{" ( declaration ";" ) ( declaration ";" )* "}" union-type-spec: "union" union-body union-body: "switch" "(" declaration ")" "{" ( "case" value ":" declaration ";" ) ( "case" value ":" declaration ";" )* [ "default" ":" declaration ";" ] "}" constant-def: "const" identifier "=" constant ";" type-def: "typedef" declaration ";" | "enum" identifier enum-body ";" | "struct" identifier struct-body ";" | "union" identifier union-body ";" definition: type-def | constant-def specification: definition *
次に示すものはキーワードとして予約されており、識別子として使用できません。
配列のサイズ指定に使用できるのは、符号なし定数だけです。識別子で指定するときは、その識別子をそれまでに const
定義を使用して符号なし定数として宣言しておく必要があります。
指定範囲内の識別子の定数と型は、同じ名前空間内にあり、この範囲内で一意に宣言されている必要があります。
同様に、構造体と共用体の宣言の有効範囲内では、変数名は一意にする必要があります。構造体と共用体の宣言が入れ子になっている場合は、新しい有効範囲ができます。
共用体の要素識別子は、整数を表す型にする必要があります。すなわち、int
、unsigned int
、bool
、enum
、または、このどれかの型を typedef
で定義したものでなければなりません。case で指定する値は、要素識別子の型に応じた値にする必要があります。また、union
宣言の有効範囲内で case の値を 2 回以上指定することはできません。
ファイルのデータ構造を XDR で記述した簡単な例を次に示します。このデータは、マシン間のファイル転送に使用することができます。
const MAXUSERNAME = 32;/* ユーザー名の長さの最大値 */ const MAXFILELEN = 65535; /* ファイルの長さの最大値 */ const MAXNAMELEN = 255; /* ファイル名の長さの最大値 */ /* ファイルのタイプ: */ enum filekind { TEXT = 0, /* ASCII データ */ DATA = 1, /* raw データ */ EXEC = 2 /* 実行可能ファイル */ }; /* ファイルタイプ別のファイル情報: */ union filetype switch (filekind kind) { case TEXT: void; /* 追加情報なし */ case DATA: string creator<MAXNAMELEN>; /* データ作成者 */ case EXEC: string interpreter<MAXNAMELEN>; /*プログラムインタプリタ*/ }; /* 完全なファイル: */ struct file { string filename<MAXNAMELEN>; /* ファイル名 */ filetype type; /* ファイル情報 */ string owner<MAXUSERNAME>; /* ファイルの所有者 */ opaque data<MAXFILELEN>; /* ファイルデータ */ };
linda というユーザーが、(quit) というデータだけが入った自分の LISP プログラム sillyprog を XDR 形式で保存するとします。このファイルは次の表に示すように符号化されます。
表 C–1 XDR データ記述の例
オフセット |
バイト (16 進) |
ASCII |
説明 |
---|---|---|---|
0 |
00 00 00 09 |
– |
ファイル名の長さ = 9 |
4 |
73 69 6c 6c |
sill |
ファイル名の文字列 |
8 |
79 70 72 6f |
ypro |
ファイル名の文字列 (続き) |
12 |
67 00 00 00 |
g |
ファイル名の文字列 (続き) と 3 バイトのヌルパディング |
16 |
00 00 00 02 |
– |
ファイルタイプ Filekind は EXEC = 2 |
20 |
00 00 00 04 |
– |
インタプリタ名の長さ = 4 |
24 |
6c 69 73 70 |
lisp |
インタプリタの文字列 |
28 |
00 00 00 04 |
– |
所有者名の長さ = 4 |
32 |
6a 6f 68 6e |
linda |
所有者名 |
36 |
00 00 00 06 |
– |
ファイルデータの長さ = 6 |
40 |
28 71 75 69 |
(qu |
ファイルデータ |
44 |
74 29 00 00 |
t) |
ファイルデータ (続き) と 2 バイトのヌルパディング |
RPC 言語は XDR 言語を拡張したものです。唯一の拡張は program 型と version 型の追加です。
XDR 言語への RPC 拡張の説明については、 付録 B 「RPC プロトコルおよび言語の仕様」 を参照してください。