言語非依存でバイナリデータをやりとりするには

はじめに

色々なシステム間でバイナリデータのやり取りをする方法に興味を持っています.調べている中でUniversal Binary Format(UBF)という規格を知ったので,その元になっている資料をまとめてみました.これは,Getting Erlang to talk to the outside worldという名前で,プログラミングErlangプログラミングErlangの著者でもあるJoe Armstrong氏が書いています.この資料を章ごとにまとめてみました.

ここから

ABSTRACT

Erlangを使ってErlang以外の世界とデータをやりとりするにはどうすればいいでしょう.これは,Erlangを構成要素として使う分散アプリケーションを構築する際には重要な課題です.一般的な解決策は,XML,XML-schemas,SOAP,WSDLと言ったXML標準を使うことですが,非効率,かつ,複雑すぎます.そこで我々はUniversal Binaru Format(UBF)という簡単なバイナリスキーマを提案します.UBFスキーマXML標準と同じ程度の記述能力を持ちますが,非常に簡単です.

INTRODUCTION

この資料で扱うのは,構成要素が非同期に通信する信頼性のある分散システムです.各構成要素は別々の言語で作られ,別々のOS上で動作し,ネットワーク上のどこかで動いています.このシステムでの課題は,これらの構成要素をどのように連携させればよいのか,ということと,システムを容易に構築可能な,使いやすい言語非依存なtransport layerをどのように作るか,の2点です.XMLを使うのは解決策のひとつですが,複雑すぎで冗長です.

XMLが大変だと言ってる

次の"PROBLEMS WITH XML"の章では,XMLの大変さについて書いてます.

OUR ARCHITECTURE

この資料では,下図のようなアーキテクチャのシステムを提案します.この図でBは"Black box"で,Cは"Contract Checker"を意味します.

        +-+     +-+
        |B|--C--|B|
        +-+     +-+
         |
         C
         |
+-+     +-+     +-+
|B|--C--|B|--C--|B|
+-+     +-+     +-+

Black boxはシステムの構成要素で,それぞれが必要に応じてクライアントやサーバとして動作します.構成要素間にはcontract checkerがあります.contract checkerは構成要素間でのメッセージが正当かどうかをチェックします.

        X     X
        -->   -->
+------+         +------+
|Client|----C----|Server|
+------+         +------+
        <--   <--
       (M,S1)  (M,S1)

contract checkerの初期状態がSだとします.クライアントがサーバに向けてXというメッセージを送ると,contract checkerはXが型に従っているかどうか,状態Sで受けてよい型かどうかをチェックします.チェックして問題なければサーバに送信します.サーバがメッセージと状態から構成される組み合わせ{M,S1}を返すと,contract checkerはこのメッセージが現在の状態で受けてよいレスポンスかどうかをチェックします.{M,S1}が問題なければクライアントに送信すると供にcontract checkerの状態をS1にします.contract checkerはクライアントサーバ間で許容されるメッセージの順番や型に関するcontractによってパターン化できます.contractは簡単なnon-deterministic finite state machineと簡単なtype languageを使って定義できるので,以下のようなモデル化できます.

{S_{in}, T_{in}, T_{out}, S_{out}}

これは,サーバがS_{in}という状態で,T_{in}という型のメッセージを受け取ると,T_{out}という型のメッセージを返し,状態がS_{out}になるということを現しています.

UBF - A UNIVERSAL BINARY FORMAT

contractはUBFという言語で記述します.UBFには

があります.

UBF(A) - A BINARY TRANSPORT FORMAT

この章の内容は間違いがあるようなので,quick summaryを見ながらまとめました.

表記 概要
'...' constantをスタックに積む
"..." stringをスタックに積む
Int ~...~ 長さIntのバイナリをスタックに積む
`xxx` スタックにxxx型というタグを付ける
%...% コメント
[-][0-9]+ Integer 資料上だと+でなく*になってるけど,おそらく間違い
\s\n\r\t (white spaceとして)無視する
# スタックにnilを積む
& スタックから2個エントリを取り出して(cons X Y)と置き換える
{ 構造体の開始
} 構造体の終了
$ スタックからエントリを返す
Control '"~%-0123456789{}#&$> の21個
>C スタックから取り出してregister[C]にstoreする
C register[C]をスタックに積む

となります.実際の例だと

'person'>p # {p,"Joe",123} & {p, 'fred', ~abc~} & $

というUBF(A)は

[{person, "Joe", 123}, {person, fred, <>}].

というerlangで記述したUBF(B)になります.4種類の基本型の後に`...`というsemantic tagを付けることができます.このタグはUBF(A)の世界では意味を持ちませんが,UBF(B)の世界で意味を持ちます.例えば

12456 ~...~ `jpg`

はsemangtic tag "jpg"がついた12456バイトのバイナリデータを現すことになります.複雑な構造を作るにはStructとListを使います.Structは,

{ Obj1 Obj2 ... Objn }

と表記します.Obj1..Objnは任意のUBF(A) objectで要素数は固定です.例えばC言語なら構造体に該当します.Listsは,

# ObjN & ObjN-1 & ... & Obj2 & Obj1 &

と表記します.Obj1..ObjNはある特定の型で要素数は任意です.最初の要素がObj1でObj2と続くので注意が必要です.

PROGRAMMING BY CONTRACT

UBFの特徴はcontractにあります.contractはUBF(B)で記述します.

+------+   +----------+   +--------+   +----------+   +------+
|      |-->|          |-->|Contract|-->|          |-->|      |
|Client|   |UBF Driver|   |checker |   |UBF Driver|   |Server|
|      |<--|          |<--|        |<--|          |<--|      |
+------+   +----------+   +--------+   +----------+   +------+
Java     /              /    /       /              /    Erlang
       Java        UBF(A)  UBF(B)    UBF(A)       Erlang
       objects     objects Contract  objects      terms

UBF(B)の実態はtype systemとprotocol description languageです.
type systemは

表記 概要
int() UBF(A) integer
sting() UBF(A) string
constant() UBF(A) constant
bin() UBF(A) binary data
X() Object type X

となります.複合型は再帰的に定義します.

表記 概要
{T1, T2, ..., Tn} T1~Tnが型を現す要素数が固定された組み合わせ
[T] 要素がT型である要素数が任意の組み合わせ
T1 | T2 T1型かT2型の型であることを意味します

さらに,UBF(B)では,新しい型を以下のように表現します.

+TYPE X() = Type

例えば

+TYPE persion() = {person, firstname(), lastname(), sex(), age()}.
+TYPE firstname() = string().
+TYPE lastname() = string().
+TYPE age() = int().
+TYPE sex() = male | female.
+TYPE people() = [person()].

と定義すると,

'person' >p
# {p "jim" "smith" male 10} &
# {p "susan" "jones" female 14} & $

はpeople()型になります.
さて,ここからはfile_serverというcontractについて考えてみます.

+NAME("file_server").
+VSN("ubf1.0").
+TYPES
info() = info;
description() = description;
services()    = services;
contract()    = contract;

file()        = string();
ls()          = ls;
files()       = {files, [file()]};
getFile()     = {get, file()};
noSuchFile()  = noSuchFile.

+STATE start
  ls()      => files()      & start;
  getFile() => binary()     & start
            |  noSuchFile() & stop.

+ANYSTATE
  info()        => string();
  description() => string();
  contract()    => term().

+TYPESは型定義が複数あることを意味します.STATEは状態についての記述で,start状態にあるときにls()というメッセージを受け取ったら,files()というレスポンスを返し,start状態に戻ります.同じように,getFile()を受け取ったらbinary()型のレスポンスを返しstart状態に戻るか,noSuchFile()型のレスポンスを返しstop状態に移行することを意味します.NAME,VSN,ANYSTATEに関しては資料に説明がなくてよくわからないですが,ANYSTATEはstatelessな状況を記述するために使っているようです.

省略

この後"IMPLEMENTATION DETAILS"と"PERFORMANCE"という章はerlangで作った場合の話がでます.さらに,"FUTURE WORK"の章ではさらに高度な状態記述を書く話がでています.

RUNNING THE SYSTEM

前章で定義したcontractで動いているサーバにtelnetできたとすると,以下のような応答になります.表記の都合上$が入力,|がサーバ出力としました.

$ 'info'$
| {"I am a mini file server",'start'}$
$'ls'$
| {{'files',
|   #
|   "ubf.erl"&
|   "client.erl"&
|   "Makefile"& ...}
|   'start'}$

さらに,'contract'$と入力すると,システムのcontractを出力するようですが,資料中にはその機能説明がありません.

$ 'contract'$
| {'contract',
|  {{'name',"file_server"},
|   {'info',"I am a mini file server"},
|   {'description',"
|
| Commands:
|   'ls'$ List files
|   {'get' File} => Length ~ ... ~
|                | noSuchFile
| "},
|   {'services',#},
|   {'states',
|   #{'start',
|     #{'input',{'tuple',#{'prim','file'}&
|       {'constant','get'}&},
|     #{'output',{'constant','noSuchFile'},'stop'}&
|      {'output',{'prim','binary'},'start'}&}&
|     {'input',{'constant','ls'},
|     #{'output',
|       {'tuple',
|         #{'list',{'prim','string'}}&
|       {'constant','files'}&},'start'}&}&}&},
|   {'types',
|       #{'file',{'prim','string'}}&}}}$

もういちど省略

この後の"A LARGER CONTRACT","EXPERIENCE","ACKNOWLEDGEMENTS"という章は省略します.

おわりに

概略はなんとなくわかってきたのですが,実際にシステム間でどんなデータをやりとりするのかはこの資料からはよくわかりません.githubから落としたプログラムがなんとか動き始めたので,それを使って確かめてみます.