ubf解析 その1

まずは動かしてみる

ubfは仕様の文章化が進んでいないので,実装の動作から逆に仕様を探って行く必要があります.erlangのプログラムを解析するのが私には難しいので,テストケースを追いかけてみました.調べているのは

$ git log
commit 458aa25abcd14473b8723db1f6d377dd4fc4a704
Author: Joseph Wayne Norton
Date: Tue Jun 29 20:46:41 2010 +0900

UBF requires Erlang/OTP R13B01 or newer
...

というバージョンです.

make check

installまでの手順でmake checkする項目がありました.

$ make -n check
rm -f ./*.log
env ERL_MAX_ETS_TABLES=10007 erl +A 64 +K true -smp auto \
 +Mis true -sname runerl1 -pz ../ebin  \
-kernel net_ticktime 60 -config ../priv/sys \
-pz ./Unit-Test-Files -pz ./Unit-EUnit-Files \
-pz ./Unit-Quick-Files    -noinput -noshell \
-pz ./Unit-Test-Files -pz ./Unit-EUnit-Files \
                -s ubf_test tests \
                -s test_ubf tests \
                -s test_ebf tests \
                -s test_etf tests \
                -s stateless_plugin_test do_eunit \
                -s stateful_plugin_test do_eunit \
                -s erlang halt \
                > ./check.log

ずいぶん沢山引数がありますが,ざっとみてみると

引数 概要
env ERL_MAX_ETS_TABLES=10007 標準1400のETSテーブルの数を10007に
+A 64 async thread pool数
+K true emuで使えるようならkernel poll機能を使う
-smp auto SMPモードで起動
+Mis true Status over allocated memoryをemuで保持
-sname runerl1 dnsが動作してない環境で分散erlangを実行
-pz ../ebin 検索パスの最後に../ebinを追加
-kernel net_ticktime 60 分散ノードの死活監視時間間隔
-config ../priv/sys 設定として ../priv/sys.config を使う
-noinput 入力を受け付けない
-noshell shellなしで起動する
-s ubf_test tests ubf_test ファイルのtestsメソッドを実行

という意味になっています.これを元に,一番簡単そうなubf_test.erlのtest9というメソッドを

test9() ->
    test_ubf({abc,"kdjhkshfkhfkhsfkhaf", [a,c,d]}).

実行してみると,

$ env ERL_MAX_ETS_TABLES=10007 erl +A 64 +K true \
-smp auto +Mis true -sname runerl1 -pz ../ebin  \
-kernel net_ticktime 60 -config ../priv/sys \
-pz ./Unit-Test-Files -pz ./Unit-EUnit-Files \
-pz ./Unit-Quick-Files -noinput -noshell \
-s ubf_test test9 -s erlang halt
encode test #Bin=49
L={'abc',#102&97&104&107&102&115&104&107&102&104&107&102&104&115&107&104&106&100&107&,#'d'&'c'&'a'&}$
ubf size =99
Identical

となりました.この実行結果はubr_test.erlの

test_ubf(T) ->
    B = term_to_binary(T),
    io:format("encode test #Bin=~p~n",[size(B)]),
    L = encode(T),
    %% io:format("L=~s~n",[L]),
    io:format("ubf size =~p~n",[length(L)]),
    Val = decode(L),
    case Val of
        {ok, T, _} ->
            io:format("Identical~n");
        X ->
            io:format("Differences (~p)~n", [X]),
            io:format("Val=~p~n",[T])
    end.

からio:formatのコメントアウトをはずした状態で実行しています.encodeしてdecodeした結果が元と同じになるのは当然ですね.

$ perl -le 'print join ",", unpack "C*", "kdjhkshfkhfkhsfkhaf"'
107,100,106,104,107,115,104,102,107,104,102,107,104,115,102,107,104,97,102

とやってみてみると,文字もスタックにおしりから積まれている状況がわかります.

サーバを動かしてみる

元の論文にも出ていたfile serverを動かして,今回の記事を終わりにします.file serverの動かし方はREADMEにもでてないし,Makefileにも書いてなかったので試行錯誤の末に見つけました.参考にしたのはfile_client.erlに書いてあったコメントです.ただ,このコメントもそのままでは動きませんでした.

$ env ERL_MAX_ETS_TABLES=10007 erl +A 64 +K true \
-smp auto +Mis true -sname runerl1 -pz ../ebin \
-kernel net_ticktime 60 -config ../priv/sys \
-pz ./Unit-Test-Files
Erlang R13B04 (erts-5.7.5) [source] [64-bit] [rq:1] [async-threads:64] [kernel-poll:true]

Eshell V5.7.5  (abort with ^G)
(runerl1@foo)1> ubf_server:start([file_plugin],file_client:defaultPort()).
true
(runerl1@foo)2> file_client:test().
DEBUG: Arg [] Pid <0.45.0>
Info = {ok,{'#S',"I am a mini file server"}}
ls: H_Data myFirstData0_is_not_used
ls: Env    <0.45.0>
Files=["check.log.20100708","erl_crash.dump","contract_yecc.erl",
       "contract_lex.erl","ubf_utils.erl","ubf_server.erl",
       "ubf_plugin_stateless.erl","ubf_plugin_stateful.erl",
       "ubf_plugin_meta_stateless.erl","ubf_plugin_meta_stateless.con",
       "ubf_plugin_meta_stateful.erl","ubf_plugin_meta_stateful.con",
       "ubf_plugin_meta.con","ubf_plugin_handler.erl","ubf_driver.erl",
       "ubf_client.erl","ubf.erl","proc_utils.erl","proc_socket_server.erl",
       "ebf_driver.erl","ebf.erl","contracts_abnf.erl","contracts.erl",
       "contract_yecc.yrl","contract_proto.erl","contract_parser.erl",
       "contract_manager_tlog.erl","contract_manager.erl","contract_lex.xrl",
       "contract_driver.erl","Unit-Test-Files","Unit-EUnit-Files","Makefile"]
got 8897 bytes for ubf.erl
test worked
ok
(runerl1@foo)3>

正しく動いているようです.ついでに,サーバにtelnetからアクセスしてみると,file_client:defaultPort()が2000なので,

$ telnet localhost 2000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
{'ubf1.0',"meta_server","

 See http://www.sics.se/~joe/ubf/ for details of this service.
 See http://github.com/norton/ubf for source code
     extensions available as part of the larger OSS community.
 Type 'info'$ for information

"}$
'info'$
{"I am a meta server -

    type 'help'$

              ... to find out what I can do",'start'}$
'help'$
{1100~


This server speaks Universal Binary Format 1.0

                See http://www.sics.se/~joe/ubf.html
                See http://github.com/norton/ubf/tree/master for some
                source code extensions available as part of the larger
                OSS community.

UBF servers are introspective - which means the servers can describe
themselves. The following commands are always available:

'help'$          This information
'info'$          Short information about the current service
'description'$   Long information  about the current service
'services'$      A list of available services
'contract'$      Return the service contract
(Note this is encoded in UBF)

To start a service:

{'startSession', "Name", Arg}  Name should be one of the names in the
                                 services list.  Arg is an initial
argument for the Name service and is specific to that service; use
'foo' or # (the empty list) if the service ignores this argument.

Warning: Without reading the documentation you might find the output
from some of these commands difficult to understand :-)

~,'start'}$
'services'$
{#"file_server"&,'start'}$
{'startSession',"file_server",#}$
{{'ok',"I am a mini file server"},'start'}$
'ls'$
{{'files',#"Makefile"&"Unit-EUnit-Files"&"
Unit-Test-Files"&"contract_driver.erl"&"contract_lex.xrl"&"
contract_manager.erl"&"contract_manager_tlog.erl"&"
contract_parser.erl"&"contract_proto.erl"&"
contract_yecc.yrl"&"contracts.erl"&"contracts_abnf.erl"&"
ebf.erl"&"ebf_driver.erl"&"proc_socket_server.erl"&"
proc_utils.erl"&"ubf.erl"&"ubf_client.erl"&"
ubf_driver.erl"&"ubf_plugin_handler.erl"&"
ubf_plugin_meta.con"&"ubf_plugin_meta_stateful.con"&"
ubf_plugin_meta_stateful.erl"&"
ubf_plugin_meta_stateless.con"&"
ubf_plugin_meta_stateless.erl"&"ubf_plugin_stateful.erl"&"
ubf_plugin_stateless.erl"&"ubf_server.erl"&"ubf_utils.erl"&"
contract_lex.erl"&"contract_yecc.erl"&"erl_crash.dump"&"
check.log.20100708"&},'start'}$

となって正しく動いていることがわかります(画面の都合上適宜改行を入れています).次は中身の解析に進みます.