diff --git a/edoc/abf.html b/edoc/abf.html
new file mode 100644
index 0000000..304cd12
--- /dev/null
+++ b/edoc/abf.html
@@ -0,0 +1,48 @@
+
+
+
+Protocol driver process for Avro protocol sessions.
+
+ The process executing
+abf
+abf_driver
contract_driver
contract_lex
contract_manager
@@ -19,6 +21,8 @@
jsf
jsf_driver
jsf_utils
+pbf
+pbf_driver
proc_socket_server
proc_utils
tbf
diff --git a/edoc/pbf.html b/edoc/pbf.html
new file mode 100644
index 0000000..d456e99
--- /dev/null
+++ b/edoc/pbf.html
@@ -0,0 +1,114 @@
+
+
+
+Module pbf
+
+
+
+
+
+
+Module pbf
+Functions for Google's Protocol Buffers<->Erlang data conversion.
+
+
+Functions for Google's Protocol Buffers<->Erlang data conversion.
+
+ For most purposes, these functions are not called by code outside
+ of this library: Erlang client & Erlang server application code
+usually have no need to use these functions.
+
+
+
+
+ http://code.google.com/p/protobuf/
+
+
+
+
+
+
+
+
+
+
atom_to_binary(X) -> any()
+
+
+
+
+
binary_to_existing_atom(X) -> any()
+
+
+
+
+
contract_records() -> any()
+
+
+
+
+
+
+
+
decode(X, Mod) -> any()
+
+
+
+
+
decode(X, Mod, X3) -> any()
+
+
+
+
+
decode_init() -> any()
+
+
+
+
+
decode_print(X) -> any()
+
+
+
+
+
decode_print(X, Mod) -> any()
+
+
+
+
+
+
+
+
encode(X, Mod) -> any()
+
+
+
+
+
encode_print(X) -> any()
+
+
+
+
+
encode_print(X, Mod) -> any()
+
+
+
+
+Generated EDoc, $Id$
+
+
diff --git a/edoc/pbf_driver.html b/edoc/pbf_driver.html
new file mode 100644
index 0000000..a2e8876
--- /dev/null
+++ b/edoc/pbf_driver.html
@@ -0,0 +1,55 @@
+
+
+
+Module pbf_driver
+
+
+
+
+
+
+Module pbf_driver
+Protocol driver process for PBF (Google's Protocol Buffers
+Format) protocol sessions.
+
+
+Protocol driver process for PBF (Google's Protocol Buffers
+Format) protocol sessions.
+
+ The process executing loop() in this module is represented in the
+ diagram below by the "UBF Driver" circle.
+
+
+
+
+
+
+
+
+
decode(Contract, Cont, Binary, CallBack) -> any()
+
+
+
+
+
encode(Contract, Term) -> any()
+
+
+
+
+
init(Contract) -> any()
+
+
+
+
+
start(Contract) -> any()
+
+
+
+
+Generated EDoc, $Id$
+
+
diff --git a/edoc/tbf.html b/edoc/tbf.html
index d74c375..aa48847 100644
--- a/edoc/tbf.html
+++ b/edoc/tbf.html
@@ -117,21 +117,22 @@ usually have no need to use these functions.
| 'T-I08' | 'T-I16' | 'T-I32' | 'T-U64' | 'T-I64' | 'T-DOUBLE'
| 'T-BINARY' | 'T-STRUCT' | 'T-MAP' | 'T-SET' | 'T-LIST'.
tbf::field_id() = integer().
- tbf::field_data() = tbf::void() | tbf::boolean() | integer() | double() | binary() | binary() |
- | tbf::struct() | tbf::map() | tbf::set() | tbf::list().
+ tbf::field_data() = tbf::void() | tbf::boolean() | integer()
+ | integer() | float()
+ | binary() | tbf::struct() | tbf::map() | tbf::set() | tbf::list().
tbf::map() = {'map', tbf::map_type(), [tbf::map_data()]}.
tbf::map_type() = {tbf::field_type(), tbf::field_type()}.
tbf::map_data() = {tbf::field_data(), tbf::field_data()}.
- tbf::list() = {'list', tbf::list_type(), [tbf::list_data()]}.
- tbf::list_type() = tbf::field_type().
- tbf::list_data() = tbf::field_data().
-
tbf::set() = {'set', tbf::set_type(), [tbf::set_data()]}.
tbf::set_type() = tbf::field_type().
tbf::set_data() = tbf::field_data().
+ tbf::list() = {'list', tbf::list_type(), [tbf::list_data()]}.
+ tbf::list_type() = tbf::field_type().
+ tbf::list_data() = tbf::field_data().
+
tbf::void() = 'undefined'.
tbf::boolean() = 'true' | 'false'.
diff --git a/edoc/ubf_client.html b/edoc/ubf_client.html
index 766587a..5bbdef7 100644
--- a/edoc/ubf_client.html
+++ b/edoc/ubf_client.html
@@ -25,8 +25,9 @@
client process.
- Note that this library can support UBF(A), EBF, and JSF transport.
- See the connect() function arguments for details.
+ Note that this library can support UBF(A), EBF, JSF, TBF, PBF, and
+ ABF transport. See the connect() function arguments for
+details.
This module also provides an alternative client-side function for
calling's UBF contract manager and a UBF contract's implementation
@@ -43,7 +44,7 @@ procedure call to a contract's implementation.
A DNS hostname or IP address.
-connect_options() = list({proto, ubf | ebf | jsf})
+connect_options() = list({proto, ubf | ebf | jsf | tbf | pbf | abf})
An OTP-style property list, see 'proplists' module for details.
diff --git a/edoc/ubf_server.html b/edoc/ubf_server.html
index ed61ba4..b963b0a 100644
--- a/edoc/ubf_server.html
+++ b/edoc/ubf_server.html
@@ -37,13 +37,19 @@ implementation of a UBF(B) protocol-checking server:
terms between a UBF(B) contract checking server and a client
that does not support the UBF(A) wire format but does support
Erlang's native wire formats.
- JSF, a.k.a the JSON Server Format. Similar to EBF, except
+ JSF, a.k.a the JSon Format. Similar to EBF, except
that JavaScript's JSON encoding is used for the wire protocol
- instead of UBF(A) or Erlang's native wire formats.
- NOTE: This server is currently incomplete. Source code from
- Gemini Mobile Technologies, Inc. is not yet available to
- help glue JSF-style encoding to a full HTTP/JSON-RPC service.
-
+ instead of UBF(A) or Erlang's native wire formats.
+ TBF, a.k.a the Thrift Binary Format. Similar to EBF, except
+ that Thrift's binary encoding is used for the wire protocol
+ instead of UBF(A) or Erlang's native wire formats.
+ PBF, a.k.a the Google's Protocol Buffers Format. Similar to
+ EBF, except that Google's Protocol Buffers binary encoding is used
+ for the wire protocol instead of UBF(A) or Erlang's native wire
+ formats.
+ ABF, a.k.a the Avro Binary Format. Similar to EBF, except
+ that Avro's binary encoding is used for the wire protocol
+ instead of UBF(A) or Erlang's native wire formats.
There is no "stop" function. To stop the server, instead stop the
@@ -125,15 +131,17 @@ undefined, the server is not registered
{maxconn, integer()} ... Maximum number of simultaneous TCP
connections allowed.
Default: 10,000.
- {proto, {ubf | ebf | jsf}} ... Enable the UBF, EBF, or JSF version
- of the protocol's wire format.
+ {proto, {ubf | ebf | jsf | tbf | pbf | abf}} ... Enable the
+ UBF, EBF, JSF, TBF, PBF or ABF version of the protocol's wire
+ format.
Default: ubf.
{registeredname, atom()} ... Set the name to be registered for
the TCP listener. If undefined, a default name is automatically
registered.
Default: undefined.
- {serverhello, string()} ... Meta contract greeting string, sent
- when a client first connects to the server.
+ {serverhello, string() | undefined} ... Meta contract greeting
+ string, sent when a client first connects to the server. If
+ undefined, server hello is not sent to the client.
Default: "meta_server"
{statelessrpc, true | false} ... run the stateless variety of
a UBF(B) contract. A stateless contract is an extension of
diff --git a/src/Makefile b/src/Makefile
index 6bfc795..0dd2cb0 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -25,6 +25,10 @@ MODULES = \
ebf_driver \
tbf \
tbf_driver \
+ pbf \
+ pbf_driver \
+ abf \
+ abf_driver \
ubf_plugin_meta_stateful \
ubf_plugin_meta_stateless
diff --git a/src/Unit-EUnit-Files/stateful_plugin_sup.erl b/src/Unit-EUnit-Files/stateful_plugin_sup.erl
index 94b3b9e..3c88fb3 100644
--- a/src/Unit-EUnit-Files/stateful_plugin_sup.erl
+++ b/src/Unit-EUnit-Files/stateful_plugin_sup.erl
@@ -104,7 +104,67 @@ init(Args) ->
[JSFServer]
end,
- {ok, {{one_for_one, 2, 60}, CUBF ++ CEBF ++ CJSF}}.
+ CTBF = case proplists:get_value(test_tbf_tcp_port, Args, 0) of
+ undefined ->
+ [];
+ TBFPort ->
+ TBFMaxConn = proplists:get_value(test_tbf_maxconn, Args, DefaultMaxConn),
+ TBFIdleTimer = proplists:get_value(test_tbf_timeout, Args, DefaultTimeout),
+ TBFOptions = [{serverhello, undefined}
+ , {statelessrpc,false}
+ , {proto,tbf}
+ , {maxconn,TBFMaxConn}
+ , {idletimer,TBFIdleTimer}
+ , {registeredname,test_tbf_tcp_port}
+ ],
+ TBFServer =
+ {tbf_server, {ubf_server, start_link, [test_tbf, DefaultPlugins, TBFPort, TBFOptions]},
+ permanent, 2000, worker, [tbf_server]},
+
+ [TBFServer]
+ end,
+
+ CPBF = case proplists:get_value(test_pbf_tcp_port, Args, 0) of
+ undefined ->
+ [];
+ PBFPort ->
+ PBFMaxConn = proplists:get_value(test_pbf_maxconn, Args, DefaultMaxConn),
+ PBFIdleTimer = proplists:get_value(test_pbf_timeout, Args, DefaultTimeout),
+ PBFOptions = [{serverhello, undefined}
+ , {statelessrpc,false}
+ , {proto,pbf}
+ , {maxconn,PBFMaxConn}
+ , {idletimer,PBFIdleTimer}
+ , {registeredname,test_pbf_tcp_port}
+ ],
+ PBFServer =
+ {pbf_server, {ubf_server, start_link, [test_pbf, DefaultPlugins, PBFPort, PBFOptions]},
+ permanent, 2000, worker, [pbf_server]},
+
+ [PBFServer]
+ end,
+
+ CABF = case proplists:get_value(test_abf_tcp_port, Args, 0) of
+ undefined ->
+ [];
+ ABFPort ->
+ ABFMaxConn = proplists:get_value(test_abf_maxconn, Args, DefaultMaxConn),
+ ABFIdleTimer = proplists:get_value(test_abf_timeout, Args, DefaultTimeout),
+ ABFOptions = [{serverhello, undefined}
+ , {statelessrpc,false}
+ , {proto,ubf} %% @TODO ubf->abf
+ , {maxconn,ABFMaxConn}
+ , {idletimer,ABFIdleTimer}
+ , {registeredname,test_abf_tcp_port}
+ ],
+ ABFServer =
+ {abf_server, {ubf_server, start_link, [test_abf, DefaultPlugins, ABFPort, ABFOptions]},
+ permanent, 2000, worker, [abf_server]},
+
+ [ABFServer]
+ end,
+
+ {ok, {{one_for_one, 2, 60}, CUBF++CEBF++CJSF++CTBF++CPBF++CABF}}.
%%%----------------------------------------------------------------------
%%% Internal functions
diff --git a/src/Unit-EUnit-Files/stateful_plugin_test.erl b/src/Unit-EUnit-Files/stateful_plugin_test.erl
index 3ad4382..bf4cfea 100644
--- a/src/Unit-EUnit-Files/stateful_plugin_test.erl
+++ b/src/Unit-EUnit-Files/stateful_plugin_test.erl
@@ -37,8 +37,11 @@ all_tests_(Setup,Teardown) ->
non_existing ->
[];
_ ->
- [] %% TODO: fix failure (stateless_plugin_test:all_actual_tests_(jsf,false,start))(not_used)
+ [] %% @TODO fix failure (stateless_plugin_test:all_actual_tests_(jsf,false,start))(not_used)
end
+ ++ (stateless_plugin_test:all_actual_tests_(tbf,false,start))(not_used)
+ %% @TODO ++ (stateless_plugin_test:all_actual_tests_(pbf,false,start))(not_used)
+ %% @TODO ++ (stateless_plugin_test:all_actual_tests_(abf,false,start))(not_used)
++ (stateless_plugin_test:all_actual_tests_(etf,false,start))(not_used)
++ (stateless_plugin_test:all_actual_tests_(lpc,false,start))(not_used)
}.
diff --git a/src/Unit-EUnit-Files/stateless_plugin_sup.erl b/src/Unit-EUnit-Files/stateless_plugin_sup.erl
index a519dae..504df6e 100644
--- a/src/Unit-EUnit-Files/stateless_plugin_sup.erl
+++ b/src/Unit-EUnit-Files/stateless_plugin_sup.erl
@@ -104,7 +104,67 @@ init(Args) ->
[JSFServer]
end,
- {ok, {{one_for_one, 2, 60}, CUBF ++ CEBF ++ CJSF}}.
+ CTBF = case proplists:get_value(test_tbf_tcp_port, Args, 0) of
+ undefined ->
+ [];
+ TBFPort ->
+ TBFMaxConn = proplists:get_value(test_tbf_maxconn, Args, DefaultMaxConn),
+ TBFIdleTimer = proplists:get_value(test_tbf_timeout, Args, DefaultTimeout),
+ TBFOptions = [{serverhello, undefined}
+ , {statelessrpc,true}
+ , {proto,tbf}
+ , {maxconn,TBFMaxConn}
+ , {idletimer,TBFIdleTimer}
+ , {registeredname,test_tbf_tcp_port}
+ ],
+ TBFServer =
+ {tbf_server, {ubf_server, start_link, [test_tbf, DefaultPlugins, TBFPort, TBFOptions]},
+ permanent, 2000, worker, [tbf_server]},
+
+ [TBFServer]
+ end,
+
+ CPBF = case proplists:get_value(test_pbf_tcp_port, Args, 0) of
+ undefined ->
+ [];
+ PBFPort ->
+ PBFMaxConn = proplists:get_value(test_pbf_maxconn, Args, DefaultMaxConn),
+ PBFIdleTimer = proplists:get_value(test_pbf_timeout, Args, DefaultTimeout),
+ PBFOptions = [{serverhello, undefined}
+ , {statelessrpc,true}
+ , {proto,pbf}
+ , {maxconn,PBFMaxConn}
+ , {idletimer,PBFIdleTimer}
+ , {registeredname,test_pbf_tcp_port}
+ ],
+ PBFServer =
+ {pbf_server, {ubf_server, start_link, [test_pbf, DefaultPlugins, PBFPort, PBFOptions]},
+ permanent, 2000, worker, [pbf_server]},
+
+ [PBFServer]
+ end,
+
+ CABF = case proplists:get_value(test_abf_tcp_port, Args, 0) of
+ undefined ->
+ [];
+ ABFPort ->
+ ABFMaxConn = proplists:get_value(test_abf_maxconn, Args, DefaultMaxConn),
+ ABFIdleTimer = proplists:get_value(test_abf_timeout, Args, DefaultTimeout),
+ ABFOptions = [{serverhello, undefined}
+ , {statelessrpc,true}
+ , {proto,ubf} %% @TODO ubf->abf
+ , {maxconn,ABFMaxConn}
+ , {idletimer,ABFIdleTimer}
+ , {registeredname,test_abf_tcp_port}
+ ],
+ ABFServer =
+ {abf_server, {ubf_server, start_link, [test_abf, DefaultPlugins, ABFPort, ABFOptions]},
+ permanent, 2000, worker, [abf_server]},
+
+ [ABFServer]
+ end,
+
+ {ok, {{one_for_one, 2, 60}, CUBF++CEBF++CJSF++CTBF++CPBF++CABF}}.
%%%----------------------------------------------------------------------
%%% Internal functions
diff --git a/src/Unit-EUnit-Files/stateless_plugin_test.erl b/src/Unit-EUnit-Files/stateless_plugin_test.erl
index d92562b..9ac4360 100644
--- a/src/Unit-EUnit-Files/stateless_plugin_test.erl
+++ b/src/Unit-EUnit-Files/stateless_plugin_test.erl
@@ -10,11 +10,15 @@ do_eunit() ->
_ -> erlang:halt(1)
end.
-
-define(APPLICATION, stateless_plugin).
-define(UBF_PORT, server_port(test_ubf_tcp_port)).
-define(EBF_PORT, server_port(test_ebf_tcp_port)).
-define(JSF_PORT, server_port(test_jsf_tcp_port)).
+-define(TBF_PORT, server_port(test_tbf_tcp_port)).
+-define(PBF_PORT, server_port(test_pbf_tcp_port)).
+-define(ABF_PORT, server_port(test_abf_tcp_port)).
+
+-define(SLEEP, 25).
-record(args, {host, port, proto, stateless, state}).
@@ -44,6 +48,9 @@ all_tests_(Setup,Teardown) ->
_ ->
(all_actual_tests_(jsf,true,none))(not_used)
end
+ ++ (all_actual_tests_(tbf,true,none))(not_used)
+ %% @TODO ++ (all_actual_tests_(pbf,true,none))(not_used)
+ %% @TODO ++ (all_actual_tests_(abf,true,none))(not_used)
++ (all_actual_tests_(etf,true,none))(not_used)
++ (all_actual_tests_(lpc,true,none))(not_used)
}.
@@ -54,6 +61,12 @@ all_actual_tests_(ebf=Proto,Stateless,State) ->
all_actual_tests_("localhost",fun() -> ?EBF_PORT end,Proto,Stateless,State);
all_actual_tests_(jsf=Proto,Stateless,State) ->
all_actual_tests_("localhost",fun() -> ?JSF_PORT end,Proto,Stateless,State);
+all_actual_tests_(tbf=Proto,Stateless,State) ->
+ all_actual_tests_("localhost",fun() -> ?TBF_PORT end,Proto,Stateless,State);
+all_actual_tests_(pbf=Proto,Stateless,State) ->
+ all_actual_tests_("localhost",fun() -> ?PBF_PORT end,Proto,Stateless,State);
+all_actual_tests_(abf=Proto,Stateless,State) ->
+ all_actual_tests_("localhost",fun() -> ?ABF_PORT end,Proto,Stateless,State);
all_actual_tests_(Proto,Stateless,State) ->
all_actual_tests_(undefined,fun() -> undefined end,Proto,Stateless,State).
@@ -137,15 +150,13 @@ test_002(#args{host=Host,port=Port}=Args,How) ->
%% connect -> close
test_003(#args{state=State}=Args) ->
assert_process(Args, 0, 0, 0, 0, 0),
- {ok,Pid1,?S("test_meta_server")} = client_connect(Args),
+ {ok,Pid1} = client_connect(Args),
assert_process(Args, 1, 1, 1, 1, 1),
client_stop(Pid1),
assert_process(Args, 0, 0, 0, 0, 0),
- {ok,Pid2,?S("test_meta_server")} = client_connect(Args),
+ {ok,Pid2} = client_connect(Args),
client_stop(Pid1),
assert_process(Args, 1, 1, 1, 1, 1),
- {reply,{ok,ok},State} = client_rpc(Pid2,{startSession,?S("test"),[]}),
- assert_process(Args, 1, 1, 1, 1, 1),
{reply,ok,State} = client_rpc(Pid2,keepalive),
client_stop(Pid2),
assert_process(Args, 0, 0, 0, 0, 0).
@@ -153,9 +164,7 @@ test_003(#args{state=State}=Args) ->
%% connect -> client breaks -> close
test_004(#args{state=State}=Args) ->
assert_process(Args, 0, 0, 0, 0, 0),
- {ok,Pid,?S("test_meta_server")} = client_connect(Args),
- assert_process(Args, 1, 1, 1, 1, 1),
- {reply,{ok,ok},State} = client_rpc(Pid,{startSession,?S("test"),[]}),
+ {ok,Pid} = client_connect(Args),
assert_process(Args, 1, 1, 1, 1, 1),
{reply,ok,State} = client_rpc(Pid,keepalive),
assert_process(Args, 1, 1, 1, 1, 1),
@@ -170,9 +179,7 @@ test_004(#args{state=State}=Args) ->
%% connect -> client timeout -> close
test_005(#args{state=State}=Args) ->
assert_process(Args, 0, 0, 0, 0, 0),
- {ok,Pid,?S("test_meta_server")} = client_connect(Args),
- assert_process(Args, 1, 1, 1, 1, 1),
- {reply,{ok,ok},State} = client_rpc(Pid,{startSession,?S("test"),[]}),
+ {ok,Pid} = client_connect(Args),
assert_process(Args, 1, 1, 1, 1, 1),
{reply,ok,State} = client_rpc(Pid,keepalive),
assert_process(Args, 1, 1, 1, 1, 1),
@@ -184,9 +191,7 @@ test_005(#args{state=State}=Args) ->
%% connect -> server breaks -> close
test_006(#args{state=State}=Args) ->
assert_process(Args, 0, 0, 0, 0, 0),
- {ok,Pid,?S("test_meta_server")} = client_connect(Args),
- assert_process(Args, 1, 1, 1, 1, 1),
- {reply,{ok,ok},State} = client_rpc(Pid,{startSession,?S("test"),[]}),
+ {ok,Pid} = client_connect(Args),
assert_process(Args, 1, 1, 1, 1, 1),
{reply,ok,State} = client_rpc(Pid,keepalive),
assert_process(Args, 1, 1, 1, 1, 1),
@@ -201,9 +206,7 @@ test_006(#args{state=State}=Args) ->
%% connect -> server timeout -> close
test_007(#args{state=State}=Args) ->
assert_process(Args, 0, 0, 0, 0, 0),
- {ok,Pid,?S("test_meta_server")} = client_connect(Args),
- assert_process(Args, 1, 1, 1, 1, 1),
- {reply,{ok,ok},State} = client_rpc(Pid,{startSession,?S("test"),[]}),
+ {ok,Pid} = client_connect(Args),
assert_process(Args, 1, 1, 1, 1, 1),
{reply,ok,State} = client_rpc(Pid,keepalive),
assert_process(Args, 1, 1, 1, 1, 1),
@@ -217,9 +220,7 @@ test_007(#args{state=State}=Args) ->
%% connect -> server crash -> close
test_008(#args{proto=Proto,state=State}=Args) ->
assert_process(Args, 0, 0, 0, 0, 0),
- {ok,Pid,?S("test_meta_server")} = client_connect(Args),
- assert_process(Args, 1, 1, 1, 1, 1),
- {reply,{ok,ok},State} = client_rpc(Pid,{startSession,?S("test"),[]}),
+ {ok,Pid} = client_connect(Args),
assert_process(Args, 1, 1, 1, 1, 1),
{reply,ok,State} = client_rpc(Pid,keepalive),
assert_process(Args, 1, 1, 1, 1, 1),
@@ -244,9 +245,7 @@ test_009(#args{proto=Proto})
ok;
test_009(#args{state=State}=Args) ->
assert_process(Args, 0, 0, 0, 0, 0),
- {ok,Pid,?S("test_meta_server")} = client_connect(Args),
- assert_process(Args, 1, 1, 1, 1, 1),
- {reply,{ok,ok},State} = client_rpc(Pid,{startSession,?S("test"),[]}),
+ {ok,Pid} = client_connect(Args),
assert_process(Args, 1, 1, 1, 1, 1),
{reply,ok,State} = client_rpc(Pid,keepalive),
assert_process(Args, 1, 1, 1, 1, 1),
@@ -268,9 +267,7 @@ test_010(#args{proto=Proto})
ok;
test_010(#args{state=State}=Args) ->
assert_process(Args, 0, 0, 0, 0, 0),
- {ok,Pid,?S("test_meta_server")} = client_connect(Args),
- assert_process(Args, 1, 1, 1, 1, 1),
- {reply,{ok,ok},State} = client_rpc(Pid,{startSession,?S("test"),[]}),
+ {ok,Pid} = client_connect(Args),
assert_process(Args, 1, 1, 1, 1, 1),
{reply,ok,State} = client_rpc(Pid,keepalive),
assert_process(Args, 1, 1, 1, 1, 1),
@@ -367,9 +364,7 @@ server_port(Name) ->
%% connect -> driver socket is shutdown or closed -> close
test_shutdown_socket(#args{state=State}=Args,Who,Reason) ->
assert_process(Args, 0, 0, 0, 0, 0),
- {ok,Pid,?S("test_meta_server")} = client_connect(Args),
- assert_process(Args, 1, 1, 1, 1, 1),
- {reply,{ok,ok},State} = client_rpc(Pid,{startSession,?S("test"),[]}),
+ {ok,Pid} = client_connect(Args),
assert_process(Args, 1, 1, 1, 1, 1),
{reply,ok,State} = client_rpc(Pid,keepalive),
assert_process(Args, 1, 1, 1, 1, 1),
@@ -387,15 +382,25 @@ test_shutdown_socket(#args{state=State}=Args,Who,Reason) ->
client_connect(#args{proto=lpc,stateless=Stateless,state=State}) ->
Mod = if Stateless -> stateless_plugin; true -> stateful_plugin end,
- {ok,{Mod,State},?S("test_meta_server")};
-client_connect(#args{proto=etf,stateless=Stateless}) ->
+ {ok,{Mod,State}};
+client_connect(#args{proto=etf,stateless=Stateless,state=State}) ->
Plugins = if Stateless -> [stateless_plugin]; true -> [stateful_plugin] end,
Server = test_ubf,
Options = [{serverhello, "test_meta_server"},{proto,etf},{statelessrpc,Stateless}],
- ubf_client:connect(Plugins,Server,Options,infinity);
-client_connect(#args{host=Host,port=Port,proto=Proto,stateless=Stateless}) ->
+ {ok,Pid,?S("test_meta_server")} = ubf_client:connect(Plugins,Server,Options,infinity),
+ {reply,{ok,ok},State} = client_rpc(Pid,{startSession,?S("test"),[]}),
+ {ok,Pid};
+client_connect(#args{host=Host,port=Port,proto=Proto,stateless=Stateless,state=State})
+ when Proto =:= tbf; Proto =:= pbf; Proto =:= abf ->
+ Options = [{proto,Proto},{statelessrpc,Stateless},{serverhello,undefined}],
+ {ok,Pid,undefined} = ubf_client:connect(Host,Port,Options,infinity),
+ {reply,{ok,ok},State} = client_rpc(Pid,{startSession,?S("test"),[]}), %% @TODO remove this line
+ {ok,Pid};
+client_connect(#args{host=Host,port=Port,proto=Proto,stateless=Stateless,state=State}) ->
Options = [{proto,Proto},{statelessrpc,Stateless}],
- ubf_client:connect(Host,Port,Options,infinity).
+ {ok,Pid,?S("test_meta_server")} = ubf_client:connect(Host,Port,Options,infinity),
+ {reply,{ok,ok},State} = client_rpc(Pid,{startSession,?S("test"),[]}),
+ {ok,Pid}.
client_rpc(X,Y) ->
@@ -419,28 +424,49 @@ client_stop(Pid) ->
assert_process(#args{proto=ubf}=Args, Driver, Contract, Plugin, Client, ClientDriver) ->
- timer:sleep(50),
+ timer:sleep(?SLEEP),
assert_process(Args, ubf_driver, Driver),
assert_process(Args, contract_manager, Contract),
assert_process(Args, ubf_plugin_handler, Plugin),
assert_process(Args, ubf_client, Client),
assert_process(Args, ubf_client_driver, ClientDriver);
assert_process(#args{proto=ebf}=Args, Driver, Contract, Plugin, Client, ClientDriver) ->
- timer:sleep(50),
+ timer:sleep(?SLEEP),
assert_process(Args, ebf_driver, Driver),
assert_process(Args, contract_manager, Contract),
assert_process(Args, ubf_plugin_handler, Plugin),
assert_process(Args, ubf_client, Client),
assert_process(Args, ebf_client_driver, ClientDriver);
assert_process(#args{proto=jsf}=Args, Driver, Contract, Plugin, Client, ClientDriver) ->
- timer:sleep(50),
+ timer:sleep(?SLEEP),
assert_process(Args, jsf_driver, Driver),
assert_process(Args, contract_manager, Contract),
assert_process(Args, ubf_plugin_handler, Plugin),
assert_process(Args, ubf_client, Client),
assert_process(Args, jsf_client_driver, ClientDriver);
+assert_process(#args{proto=tbf}=Args, Driver, Contract, Plugin, Client, ClientDriver) ->
+ timer:sleep(?SLEEP),
+ assert_process(Args, tbf_driver, Driver),
+ assert_process(Args, contract_manager, Contract),
+ assert_process(Args, ubf_plugin_handler, Plugin),
+ assert_process(Args, ubf_client, Client),
+ assert_process(Args, tbf_client_driver, ClientDriver);
+assert_process(#args{proto=pbf}=Args, Driver, Contract, Plugin, Client, ClientDriver) ->
+ timer:sleep(?SLEEP),
+ assert_process(Args, pbf_driver, Driver),
+ assert_process(Args, contract_manager, Contract),
+ assert_process(Args, ubf_plugin_handler, Plugin),
+ assert_process(Args, ubf_client, Client),
+ assert_process(Args, pbf_client_driver, ClientDriver);
+assert_process(#args{proto=abf}=Args, Driver, Contract, Plugin, Client, ClientDriver) ->
+ timer:sleep(?SLEEP),
+ assert_process(Args, abf_driver, Driver),
+ assert_process(Args, contract_manager, Contract),
+ assert_process(Args, ubf_plugin_handler, Plugin),
+ assert_process(Args, ubf_client, Client),
+ assert_process(Args, abf_client_driver, ClientDriver);
assert_process(#args{proto=etf}=Args, _, Contract, Plugin, Client, _) ->
- timer:sleep(50),
+ timer:sleep(?SLEEP),
assert_process(Args, contract_manager, Contract),
assert_process(Args, ubf_plugin_handler, Plugin),
assert_process(Args, ubf_client, Client);
@@ -449,7 +475,7 @@ assert_process(#args{proto=lpc}=_Args, _, _Contract, _Plugin, _Client, _) ->
assert_process(#args{stateless=false}=Args, ubf_plugin_handler=M, CheckNum) ->
- assert_process(Args, M, CheckNum, -3); % adjust for manager plugins
+ assert_process(Args, M, CheckNum, -6); % adjust for manager plugins
assert_process(Args, M, CheckNum) ->
assert_process(Args, M, CheckNum, 0).
@@ -477,12 +503,20 @@ exit_process(#args{proto=ebf}, driver, Reason) ->
do_exit_process(ebf_driver, Reason);
exit_process(#args{proto=jsf}, driver, Reason) ->
do_exit_process(jsf_driver, Reason);
+exit_process(#args{proto=tbf}, driver, Reason) ->
+ do_exit_process(tbf_driver, Reason);
exit_process(#args{proto=ubf}, client_driver, Reason) ->
do_exit_process(ubf_client_driver, Reason);
exit_process(#args{proto=ebf}, client_driver, Reason) ->
do_exit_process(ebf_client_driver, Reason);
exit_process(#args{proto=jsf}, client_driver, Reason) ->
- do_exit_process(jsf_client_driver, Reason).
+ do_exit_process(jsf_client_driver, Reason);
+exit_process(#args{proto=tbf}, client_driver, Reason) ->
+ do_exit_process(tbf_client_driver, Reason);
+exit_process(#args{proto=pbf}, client_driver, Reason) ->
+ do_exit_process(pbf_client_driver, Reason);
+exit_process(#args{proto=abf}, client_driver, Reason) ->
+ do_exit_process(abf_client_driver, Reason).
shutdown_socket(Args, Process) ->
@@ -494,12 +528,24 @@ shutdown_socket(#args{proto=ebf}, driver, Reason) ->
do_shutdown_socket(ebf_driver, Reason);
shutdown_socket(#args{proto=jsf}, driver, Reason) ->
do_shutdown_socket(jsf_driver, Reason);
+shutdown_socket(#args{proto=tbf}, driver, Reason) ->
+ do_shutdown_socket(tbf_driver, Reason);
+shutdown_socket(#args{proto=pbf}, driver, Reason) ->
+ do_shutdown_socket(pbf_driver, Reason);
+shutdown_socket(#args{proto=abf}, driver, Reason) ->
+ do_shutdown_socket(abf_driver, Reason);
shutdown_socket(#args{proto=ubf}, client_driver, Reason) ->
do_shutdown_socket(ubf_client_driver, Reason);
shutdown_socket(#args{proto=ebf}, client_driver, Reason) ->
do_shutdown_socket(ebf_client_driver, Reason);
shutdown_socket(#args{proto=jsf}, client_driver, Reason) ->
- do_shutdown_socket(jsf_client_driver, Reason).
+ do_shutdown_socket(jsf_client_driver, Reason);
+shutdown_socket(#args{proto=tbf}, client_driver, Reason) ->
+ do_shutdown_socket(tbf_client_driver, Reason);
+shutdown_socket(#args{proto=pbf}, client_driver, Reason) ->
+ do_shutdown_socket(bbf_client_driver, Reason);
+shutdown_socket(#args{proto=abf}, client_driver, Reason) ->
+ do_shutdown_socket(abf_client_driver, Reason).
do_exit_process(M) ->
diff --git a/src/abf.erl b/src/abf.erl
new file mode 100644
index 0000000..37a98eb
--- /dev/null
+++ b/src/abf.erl
@@ -0,0 +1,109 @@
+%%
+%% For most purposes, these functions are not called by code outside of
+%% this library: Erlang client & Erlang server application code usually
+%% have no need to use these functions.
+%%
+%% == Links ==
+%%
+%%
+%% http://hadoop.apache.org/avro/docs/current/spec.html
+%% http://lucene.apache.org/java/2_4_0/fileformats.html#VInt
+%% http://code.google.com/apis/protocolbuffers/docs/encoding.html#types
+%%
+%%
+%% == Avro Data Types ==
+%% ```
+%% typeval = prim/ complex
+%% prim = null/ boolean/ int/ long/ float/ double/ bytes/ string
+%% complex = record/ enum/ array/ map/ union/ fixed
+%%
+%% null = "" ; zero-length
+%% boolean = %x00/ %x01
+%% int = vint
+%% long = vint
+%% float = 4*OCTET ; 32-bit IEEE 754 floating-point number
+%% double = 8*OCTET ; 64-bit IEEE 754 floating-point number
+%% bytes = long *OCTET ; bytes and that bytes of data
+%% string = long *OCTET ; bytes and that bytes of utf-8 characters
+%%
+%% record = *typeval ; list of other type values decleared by the schema
+%% enum = int ; zero-based position
+%% array = *ablock end
+%% map = *mblock end
+%% union = long typeval ; zero-based position and its value
+%% fixed = *OCTET ; length is declared in the schema
+%%
+%% end = 0x00
+%% ablock = long *typeval ; number of array items and that many items
+%% mblock = long *keyval ; number of key-val pairs and that many key-val pairs
+%% keyval = string typeval ; key is always string
+%% vint = OCTET *OCTET ; variable length zig-zag encoding
+%% ; http://lucene.apache.org/java/2_4_0/fileformats.html#VInt
+%% ; http://code.google.com/apis/protocolbuffers/docs/encoding.html#types
+%% '''
+%% == Mapping: Avro -> Erlang Terms
+%% ```
+%% abf::null() = null.
+%% abf::boolean() = true | false.
+%% abf::int() = integer().
+%% abf::long() = integer().
+%% abf::float() = float().
+%% abf::double() = float().
+%% abf::bytes() = binary().
+%% abf::string() = {'$S', [integer()]}.
+%%
+%% '''
+%% == Mapping: UBF -> Erlang Terms ==
+%% ```
+%% todo
+%% '''
+%% == Mapping: UBF value -> Avro value ==
+%% ```
+%% todo
+%% '''
+
+
+-module(abf).
+
+-include("ubf.hrl").
+
+-export([encode/3]).
+-export([decode_init/0, decode/3, decode/4]).
+
+
+%%
+%%---------------------------------------------------------------------
+%%
+-type state() :: term(). %% todo
+
+-spec encode(Input::term(), module(), state()) -> {state(), iolist()}.
+
+-type ok() :: {ok, Output::term(), Remainder::binary()}.
+-type error() :: {error, Reason::term()}.
+-type cont() :: {more, fun()}.
+
+-spec decode_init() -> fun().
+-spec decode(Input::binary(), module(), state()) -> {state(), ok()} | {state(), error()} | {state(), cont()}.
+-spec decode(Input::binary(), module(), cont(), state()) -> {state(), ok()} | {state(), error()} | {state(), cont()}.
+
+
+-record(state,{todo}).
+
+%%
+%%---------------------------------------------------------------------
+%%
+encode(_X, _Mod, State) when is_record(State, state) ->
+ {State, <<"todo">>}.
+
+%%
+%%---------------------------------------------------------------------
+%%
+decode(_X, _Mod, State) ->
+ {State, {ok, todo, <<"">>}}.
+
+decode(_X, _Mod, _Cont, State) ->
+ {State, {ok, todo, <<"">>}}.
+
+decode_init() ->
+ {more, []}. %% todo
+
diff --git a/src/abf_driver.erl b/src/abf_driver.erl
new file mode 100644
index 0000000..12673d9
--- /dev/null
+++ b/src/abf_driver.erl
@@ -0,0 +1,39 @@
+%% @doc Protocol driver process for Avro protocol sessions.
+%%
+%% The process executing `loop()' in this module is represented in the
+%% diagram below by the "UBF Driver" circle.
+%%
+
+-module(abf_driver).
+
+-export([start/1, init/1, encode/2, decode/4]).
+
+-define(DIC_KEY, abf_avro_state).
+
+start(Contract) ->
+ proc_utils:spawn_link_debug(fun() -> contract_driver:start(?MODULE, Contract) end, abf_client_driver).
+
+init(_Contract) ->
+ erase(),
+ abf:decode_init().
+
+encode(Contract, Term) ->
+ State = get(?DIC_KEY),
+ {NewState, Res} = abf:encode(Term, Contract, State),
+ put(?DIC_KEY, NewState),
+ Res.
+
+decode(Contract, Cont, Binary, CallBack) ->
+ State = get(?DIC_KEY),
+ {NewState, Cont1} = abf:decode(Binary, Contract, Cont, State),
+ put(?DIC_KEY, NewState),
+ decode(Contract, Cont1, CallBack).
+
+decode(_Contract, {more, _}=Cont, _CallBack) ->
+ Cont;
+decode(Contract, {ok, Term, Binary}=_Cont, CallBack) ->
+ State = get(?DIC_KEY),
+ CallBack(Term),
+ {NewState, Cont1} = abf:decode(Binary, Contract, State),
+ put(?DIC_KEY, NewState),
+ decode(Contract, Cont1, CallBack).
diff --git a/src/contract_lex.xrl b/src/contract_lex.xrl
index 0ec6b90..5ab83c2 100644
--- a/src/contract_lex.xrl
+++ b/src/contract_lex.xrl
@@ -34,6 +34,7 @@ EVENT : {token,{eventkwd,TokenLine}}.
"[^"]*" : S = lists:sublist(TokenChars, 2, length(TokenChars) - 2),
{token,{string,TokenLine,S}}.
=> : {token,{'=>',TokenLine}}.
+<= : {token,{'<=',TokenLine}}.
\+\+ : {token,{'++',TokenLine}}.
\.\. : {token,{'..',TokenLine}}.
\#\# : {token,{'##',TokenLine}}.
diff --git a/src/contract_yecc.yrl b/src/contract_yecc.yrl
index bd20dce..e10ab1e 100644
--- a/src/contract_yecc.yrl
+++ b/src/contract_yecc.yrl
@@ -5,7 +5,7 @@ form type typeDef typeRef primType typeAttr typeSeq typeRec.
Terminals
namekwd vsnkwd typekwd statekwd anystatekwd eventkwd atom binary float integer string
- '+' '|' '=' '#' '{' '}' '&' ';' ',' '[]' '[' ']' '(' ')' ']?' ']{0}' ']{1}' ']+' ')?' '){0}' '){1}' '++' '..' '##' '=>' dot.
+ '+' '|' '=' '#' '{' '}' '&' ';' ',' '[]' '[' ']' '(' ')' ']?' ']{0}' ']{1}' ']+' ')?' '){0}' '){1}' '++' '..' '##' '=>' '<=' dot.
Rootsymbol form.
@@ -83,6 +83,7 @@ transitions -> transition : ['$1'].
transition -> typeRef '=>' outputs : {input, '$1', '$3'}.
transition -> eventkwd '=>' typeRef : {event_out, '$3'}.
+transition -> eventkwd '<=' typeRef : {event_in, '$3'}.
outputs -> responseAndState '|' outputs : ['$1'|'$3'].
outputs -> responseAndState : ['$1'].
@@ -93,6 +94,8 @@ anyrules -> anyrule ';' anyrules : ['$1'|'$3'].
anyrules -> anyrule : ['$1'].
anyrule -> typeRef '=>' typeRef : {'$1', '$3'}.
+anyrule -> eventkwd '=>' typeRef : {event_out, '$3'}.
+anyrule -> eventkwd '<=' typeRef : {event_in, '$3'}.
Erlang code.
diff --git a/src/pbf.erl b/src/pbf.erl
new file mode 100644
index 0000000..7aff96f
--- /dev/null
+++ b/src/pbf.erl
@@ -0,0 +1,145 @@
+%% @doc Functions for Google's Protocol Buffers<->Erlang data conversion.
+%%
+%% For most purposes, these functions are not called by code outside
+%% of this library: Erlang client & Erlang server application code
+%% usually have no need to use these functions.
+%%
+%% == Links ==
+%%
+%%
+%% http://code.google.com/p/protobuf/
+%%
+%%
+%% @TODO add types and mapping specification
+%%
+%%
+
+-module(pbf).
+
+-include("ubf.hrl").
+
+-export([encode_print/1, encode_print/2, encode/1, encode/2]).
+-export([decode_print/1, decode_print/2, decode_init/0, decode/1, decode/2, decode/3]).
+
+-export([atom_to_binary/1]).
+-export([binary_to_existing_atom/1]).
+
+%% Dummy hack/kludge.
+-export([contract_records/0]).
+
+contract_records() ->
+ [].
+
+
+%%
+%%---------------------------------------------------------------------
+%%
+-spec encode_print(Input::term()) -> ok | no_return().
+-spec encode_print(Input::term(), module()) -> ok | no_return().
+
+-spec encode(Input::term()) -> iolist() | no_return().
+-spec encode(Input::term(), module()) -> iolist() | no_return().
+
+-spec decode_print(Input::binary()) -> ok | no_return().
+-spec decode_print(Input::binary(), module()) -> ok | no_return().
+
+-type ok() :: {ok, Output::term(), Remainder::binary()}.
+-type error() :: {error, Reason::term()}.
+-type cont() :: {more, fun()}.
+
+-spec decode_init() -> cont().
+-spec decode(Input::binary()) -> ok() | error() | cont().
+-spec decode(Input::binary(), module()) -> ok() | error() | cont().
+-spec decode(Input::binary(), module(), cont()) -> ok() | error() | cont().
+
+
+-record(state,
+ {
+ x % current binary to be decoded
+ , stack % current stack
+ , type % current type (optional)
+ , size % current size (optional)
+ , mod % contract
+ }
+ ).
+
+
+%%
+%%---------------------------------------------------------------------
+%%
+encode_print(X) ->
+ encode_print(X, ?MODULE).
+
+encode_print(X, Mod) ->
+ io:format("~p~n", [encode(X, Mod)]).
+
+encode(X) ->
+ encode(X, ?MODULE).
+
+encode(_X, _Mod) ->
+ %% @TODO
+ exit(unimplemented).
+
+
+%%
+%%---------------------------------------------------------------------
+%%
+decode_print(X) ->
+ decode_print(X, ?MODULE).
+
+decode_print(X, Mod) ->
+ io:format("~p~n", [decode(X, Mod)]).
+
+decode(X) ->
+ decode(X, ?MODULE).
+
+decode(X, Mod) ->
+ decode(X, Mod, decode_init()).
+
+decode(X, Mod, {more, Fun}) ->
+ Fun(#state{x=X,mod=Mod}).
+
+decode_init() ->
+ {more, fun decode_start/1}.
+
+decode_start(_S) ->
+ %% @TODO
+ exit(unimplemented).
+
+decode_finish(#state{x=X,stack=Term}) ->
+ {ok, Term, X}.
+
+decode_pause(#state{x=X}=S, Cont, Resume) ->
+ {more, fun(#state{x=X1,mod=Mod1}) ->
+ Resume(S#state{x= <>,mod=Mod1}, Cont)
+ end}.
+
+decode_error(Type, SubType, Value, S) ->
+ {error, {Type, SubType, Value, S}}.
+
+
+%%
+%%---------------------------------------------------------------------
+%%
+
+%% @TODO encode implementation
+
+
+%%
+%%---------------------------------------------------------------------
+%%
+
+%% @TODO decode implementation
+
+
+%%
+%%---------------------------------------------------------------------
+%%
+atom_to_binary(X) ->
+ list_to_binary(atom_to_list(X)).
+
+binary_to_existing_atom(X) ->
+ list_to_existing_atom(binary_to_list(X)).
+
+push(X, [Top|Rest]) ->
+ [[X|Top]|Rest].
diff --git a/src/pbf_driver.erl b/src/pbf_driver.erl
new file mode 100644
index 0000000..8d48f14
--- /dev/null
+++ b/src/pbf_driver.erl
@@ -0,0 +1,30 @@
+%% @doc Protocol driver process for PBF (Google's Protocol Buffers
+%% Format) protocol sessions.
+%%
+%% The process executing `loop()' in this module is represented in the
+%% diagram below by the "UBF Driver" circle.
+%%
+
+-module(pbf_driver).
+
+-export([start/1, init/1, encode/2, decode/4]).
+
+start(Contract) ->
+ proc_utils:spawn_link_debug(fun() -> contract_driver:start(?MODULE, Contract) end, pbf_client_driver).
+
+init(_Contract) ->
+ pbf:decode_init().
+
+encode(Contract, Term) ->
+ pbf:encode(Term, Contract).
+
+decode(Contract, Cont, Binary, CallBack) ->
+ Cont1 = pbf:decode(Binary, Contract, Cont),
+ decode(Contract, Cont1, CallBack).
+
+decode(_Contract, {more, _}=Cont, _CallBack) ->
+ Cont;
+decode(Contract, {ok, Term, Binary}=_Cont, CallBack) ->
+ CallBack(Term),
+ Cont1 = pbf:decode(Binary, Contract),
+ decode(Contract, Cont1, CallBack).
diff --git a/src/tbf.erl b/src/tbf.erl
index 6e844ab..22052ab 100644
--- a/src/tbf.erl
+++ b/src/tbf.erl
@@ -110,21 +110,22 @@
%% | 'T-I08' | 'T-I16' | 'T-I32' | 'T-U64' | 'T-I64' | 'T-DOUBLE'
%% | 'T-BINARY' | 'T-STRUCT' | 'T-MAP' | 'T-SET' | 'T-LIST'.
%% tbf::field_id() = integer().
-%% tbf::field_data() = tbf::void() | tbf::boolean() | integer() | double() | binary() | binary() |
-%% | tbf::struct() | tbf::map() | tbf::set() | tbf::list().
+%% tbf::field_data() = tbf::void() | tbf::boolean() | integer()
+%% | integer() | float()
+%% | binary() | tbf::struct() | tbf::map() | tbf::set() | tbf::list().
%%
%% tbf::map() = {'map', tbf::map_type(), [tbf::map_data()]}.
%% tbf::map_type() = {tbf::field_type(), tbf::field_type()}.
%% tbf::map_data() = {tbf::field_data(), tbf::field_data()}.
%%
-%% tbf::list() = {'list', tbf::list_type(), [tbf::list_data()]}.
-%% tbf::list_type() = tbf::field_type().
-%% tbf::list_data() = tbf::field_data().
-%%
%% tbf::set() = {'set', tbf::set_type(), [tbf::set_data()]}.
%% tbf::set_type() = tbf::field_type().
%% tbf::set_data() = tbf::field_data().
%%
+%% tbf::list() = {'list', tbf::list_type(), [tbf::list_data()]}.
+%% tbf::list_type() = tbf::field_type().
+%% tbf::list_data() = tbf::field_data().
+%%
%% tbf::void() = 'undefined'.
%% tbf::boolean() = 'true' | 'false'.
%%
@@ -283,7 +284,7 @@ contract_records() ->
-type error() :: {error, Reason::term()}.
-type cont() :: {more, fun()}.
--spec decode_init() -> fun().
+-spec decode_init() -> cont().
-spec decode(Input::binary()) -> ok() | error() | cont().
-spec decode(Input::binary(), module()) -> ok() | error() | cont().
-spec decode(Input::binary(), module(), cont()) -> ok() | error() | cont().
@@ -350,11 +351,21 @@ encode(X, Mod) when is_tuple(X) ->
'list' ->
encode_list(X, Mod);
_ ->
- exit(badarg)
+ try_encode_ubf(X, Mod)
end;
-encode(_, _) ->
- exit(badarg).
+encode(X, Mod) ->
+ try_encode_ubf(X, Mod).
+try_encode_ubf(X, Mod) ->
+ %% automagically try to encode from native ubf
+ case get('ubf_info') of
+ tbf_client_driver ->
+ encode_message({'message', <<"$UBF">>, 'T-CALL', 0, X}, Mod);
+ tbf_driver ->
+ encode_message({'message', <<"$UBF">>, 'T-REPLY', 0, X}, Mod);
+ _ ->
+ exit(badarg)
+ end.
%%
%%---------------------------------------------------------------------
@@ -369,43 +380,39 @@ decode(X) ->
decode(X, ?MODULE).
decode(X, Mod) ->
- decode_message(#state{x=X,mod=Mod}, decode_init()).
+ decode(X, Mod, decode_init()).
decode(X, Mod, {more, Fun}) ->
Fun(#state{x=X,mod=Mod}).
decode_init() ->
- fun decode_done/1.
+ {more, fun decode_start/1}.
-decode_done(#state{x=X,stack=Term}) ->
- {ok, Term, X}.
+decode_start(S) ->
+ decode_message(S, fun decode_finish/1).
-decode_done('field', #state{stack=[[H,T,[T1|T2]|T3]|Stack]}=S, Cont) ->
- H1 = list_to_tuple(lists:reverse([H|T])),
- H2 = [[H1|T1]|T2],
- Cont(S#state{stack=[[H2|T3]|Stack]});
-decode_done('field-data', #state{stack=[[H,[T|T1]|T2]|Stack]}=S, Cont) ->
- H1 = [[H|T]|T1],
- Cont(S#state{stack=[[H1|T2]|Stack]});
-decode_done('field-datum', #state{stack=[[H,T,[T1|T2]|T3]|Stack]}=S, Cont) ->
- H1 = [[{T,H}|T1]|T2],
- Cont(S#state{stack=[[H1|T3]|Stack]});
-decode_done(Type, #state{stack=[[[H|T]|T1]|Stack]}=S, Cont)
- when Type =:= 'struct';
- Type =:= 'map';
- Type =:= 'set';
- Type =:= 'list' ->
- H1 = list_to_tuple(lists:reverse([lists:reverse(H)|T])),
- Cont(S#state{stack=[[H1|T1]|Stack]});
-decode_done('message', #state{stack=[H|[[]]],mod=Mod}=S, Cont) ->
- H1 = list_to_tuple(lists:reverse(H)),
- case H1 of
- {'message',<<"$UBF">>,_Type,_SeqId,Struct} ->
- %% special treatment for UBF-native messages
- UBF = decode_ubf(Struct, Mod),
- Cont(S#state{stack=setelement(5,H1,UBF)});
+decode_finish(#state{x=X,stack=Term}) ->
+ {ok, try_decode_ubf(Term), X}.
+
+try_decode_ubf(X) ->
+ %% automagically try to decode to native ubf
+ case get('ubf_info') of
+ tbf_client_driver ->
+ case X of
+ {'message', <<"$UBF">>, 'T-REPLY', 0, Y} ->
+ Y;
+ _ ->
+ exit(badarg)
+ end;
+ tbf_driver ->
+ case X of
+ {'message', <<"$UBF">>, 'T-CALL', 0, Y} ->
+ Y;
+ _ ->
+ exit(badarg)
+ end;
_ ->
- Cont(S#state{stack=H1})
+ X
end.
decode_pause(#state{x=X}=S, Cont, Resume) ->
@@ -575,6 +582,34 @@ encode_type(_, _, _) -> exit(badarg).
%%
%%---------------------------------------------------------------------
%%
+decode_finish('field', #state{stack=[[H,T,[T1|T2]|T3]|Stack]}=S, Cont) ->
+ H1 = list_to_tuple(lists:reverse([H|T])),
+ H2 = [[H1|T1]|T2],
+ Cont(S#state{stack=[[H2|T3]|Stack]});
+decode_finish('field-data', #state{stack=[[H,[T|T1]|T2]|Stack]}=S, Cont) ->
+ H1 = [[H|T]|T1],
+ Cont(S#state{stack=[[H1|T2]|Stack]});
+decode_finish('field-datum', #state{stack=[[H,T,[T1|T2]|T3]|Stack]}=S, Cont) ->
+ H1 = [[{T,H}|T1]|T2],
+ Cont(S#state{stack=[[H1|T3]|Stack]});
+decode_finish(Type, #state{stack=[[[H|T]|T1]|Stack]}=S, Cont)
+ when Type =:= 'struct';
+ Type =:= 'map';
+ Type =:= 'set';
+ Type =:= 'list' ->
+ H1 = list_to_tuple(lists:reverse([lists:reverse(H)|T])),
+ Cont(S#state{stack=[[H1|T1]|Stack]});
+decode_finish('message', #state{stack=[H|[[]]],mod=Mod}=S, Cont) ->
+ H1 = list_to_tuple(lists:reverse(H)),
+ case H1 of
+ {'message',<<"$UBF">>,_Type,_SeqId,Struct} ->
+ %% special treatment for UBF-native messages
+ UBF = decode_ubf(Struct, Mod),
+ Cont(S#state{stack=setelement(5,H1,UBF)});
+ _ ->
+ Cont(S#state{stack=H1})
+ end.
+
decode_message(#state{x=X,stack=undefined}=S, Cont) ->
case X of
<> when Len >= 0 ->
@@ -585,7 +620,7 @@ decode_message(#state{x=X,stack=undefined}=S, Cont) ->
decode_error('message', 'message-type', Type, S);
DecodedType ->
Stack1 = [[Id, DecodedType, Name, 'message'], []],
- Cont1 = fun(S1) -> decode_done('message', S1, Cont) end,
+ Cont1 = fun(S1) -> decode_finish('message', S1, Cont) end,
decode_struct(S#state{x=X2,stack=Stack1}, Cont1)
end;
_ ->
@@ -631,7 +666,7 @@ decode_map(#state{x=X,stack=Stack}=S, Cont) ->
DecodedValueType ->
Type = {KeyType,ValueType},
Stack1 = push([[], DecodedValueType, DecodedKeyType, 'map'], Stack),
- Cont1 = fun(S1) -> decode_done('map', S1, Cont) end,
+ Cont1 = fun(S1) -> decode_finish('map', S1, Cont) end,
decode_field_datum(S#state{x=X1,stack=Stack1,type=Type,size=Size}, Cont1)
end
end;
@@ -649,7 +684,7 @@ decode_set(#state{x=X,stack=Stack}=S, Cont) ->
decode_error('set', 'set-type', Type, S);
DecodedType ->
Stack1 = push([[], DecodedType, 'set'], Stack),
- Cont1 = fun(S1) -> decode_done('set', S1, Cont) end,
+ Cont1 = fun(S1) -> decode_finish('set', S1, Cont) end,
decode_field_data(S#state{x=X1,stack=Stack1,type=Type,size=Size}, Cont1)
end;
<<_Type:8/signed,Size:32/signed>> when Size < 0 ->
@@ -666,7 +701,7 @@ decode_list(#state{x=X,stack=Stack}=S, Cont) ->
decode_error('list', 'list-type', Type, S);
DecodedType ->
Stack1 = push([[], DecodedType, 'list'], Stack),
- Cont1 = fun(S1) -> decode_done('list', S1, Cont) end,
+ Cont1 = fun(S1) -> decode_finish('list', S1, Cont) end,
decode_field_data(S#state{x=X1,stack=Stack1,type=Type,size=Size}, Cont1)
end;
<<_Type:8/signed,Size:32/signed>> when Size < 0 ->
@@ -678,7 +713,7 @@ decode_list(#state{x=X,stack=Stack}=S, Cont) ->
decode_fields(#state{x=X,stack=Stack}=S, Cont) ->
case X of
<> ->
- decode_done('struct', S#state{x=X1}, Cont);
+ decode_finish('struct', S#state{x=X1}, Cont);
<> ->
case decode_field_type(Type) of
undefined ->
@@ -687,7 +722,7 @@ decode_fields(#state{x=X,stack=Stack}=S, Cont) ->
Name = <<>>,
Stack1 = push([Id, DecodedType, Name, 'field'], Stack),
Cont2 = fun(S2) -> decode_fields(S2, Cont) end,
- Cont1 = fun(S1) -> decode_done('field', S1, Cont2) end,
+ Cont1 = fun(S1) -> decode_finish('field', S1, Cont2) end,
decode_type(Type, S#state{x=X2,stack=Stack1}, Cont1)
end;
_ ->
@@ -698,14 +733,14 @@ decode_field_data(#state{size=0}=S, Cont) ->
Cont(S#state{type=undefined,size=undefined});
decode_field_data(#state{type=Type,size=Size}=S, Cont) ->
Cont2 = fun(S2) -> decode_field_data(S2#state{type=Type,size=Size-1}, Cont) end,
- Cont1 = fun(S1) -> decode_done('field-data', S1, Cont2) end,
+ Cont1 = fun(S1) -> decode_finish('field-data', S1, Cont2) end,
decode_type(Type, S, Cont1).
decode_field_datum(#state{size=0}=S, Cont) ->
Cont(S#state{type=undefined,size=undefined});
decode_field_datum(#state{type={KeyType,ValueType}=Type,size=Size}=S, Cont) ->
Cont3 = fun(S3) -> decode_field_datum(S3#state{type=Type,size=Size-1}, Cont) end,
- Cont2 = fun(S2) -> decode_done('field-datum', S2, Cont3) end,
+ Cont2 = fun(S2) -> decode_finish('field-datum', S2, Cont3) end,
Cont1 = fun(S1) -> decode_type(ValueType, S1, Cont2) end,
decode_type(KeyType, S, Cont1).
diff --git a/src/ubf_client.erl b/src/ubf_client.erl
index 37d093d..d4b0aed 100644
--- a/src/ubf_client.erl
+++ b/src/ubf_client.erl
@@ -12,8 +12,9 @@
%%% client process.
%%%
%%%
-%%% Note that this library can support UBF(A), EBF, and JSF transport.
-%%% See the `connect()' function arguments for details.
+%%% Note that this library can support UBF(A), EBF, JSF, TBF, PBF, and
+%%% ABF transport. See the `connect()' function arguments for
+%%% details.
%%%
%%% This module also provides an alternative client-side function for
%%% calling's UBF contract manager and a UBF contract's implementation
@@ -42,7 +43,7 @@
-import(contract_manager, [do_lpcIn/4, do_lpcOut/9, do_lpcOutError/6]).
%% @type address() = string() | ip_address(). A DNS hostname or IP address.
-%% @type connect_options() = list({proto, ubf | ebf | jsf}).
+%% @type connect_options() = list({proto, ubf | ebf | jsf | tbf | pbf | abf}).
%% An OTP-style property list, see 'proplists' module for details.
%% @type ip_address() = string() | tuple(). An IP address in string form,
%% e.g. "127.0.0.1" (IPv4) or "::1" (IPv6), or in tuple form (see
@@ -142,6 +143,7 @@ ubf_client(Parent, Host, Port, Options, Timeout)
process_flag(trap_exit, true),
DefaultConnectOptions =
[binary, {nodelay, true}, {active, false}],
+ ServerHello = proplists:get_value(serverhello, Options, defined),
{DriverModule, DriverContract, DriverVersion, ConnectOptions} =
case proplists:get_value(proto,Options,ubf) of
ubf ->
@@ -149,7 +151,13 @@ ubf_client(Parent, Host, Port, Options, Timeout)
ebf ->
{ebf_driver, undefined, 'ebf1.0', DefaultConnectOptions++[{packet,4}]};
jsf ->
- {jsf_driver, jsf, 'jsf1.0', DefaultConnectOptions}
+ {jsf_driver, jsf, 'jsf1.0', DefaultConnectOptions};
+ tbf ->
+ {tbf_driver, tbf, 'tbf1.0', DefaultConnectOptions};
+ pbf ->
+ {pbf_driver, pbf, 'pbf1.0', DefaultConnectOptions};
+ abf -> %% @TODO ubf_driver -> abf_driver
+ {ubf_driver, undefined, 'abf1.0', DefaultConnectOptions}
end,
case gen_tcp:connect(Host, Port, ConnectOptions) of
{ok, Socket} ->
@@ -163,18 +171,23 @@ ubf_client(Parent, Host, Port, Options, Timeout)
%%, {send_timeout, Timeout}
%%, {send_timeout_close, true}
]),
- %% wait for a startup message
- receive
- {Driver, {DriverVersion, Service, _}} ->
- Parent ! {self(), {ok, Service}},
- ubf_client_loop(Parent, Driver);
- {'EXIT', Driver, Reason} ->
- Parent ! {self(), {error, Reason}};
- {'EXIT', Parent, Reason} ->
- exit(Driver, Reason)
- after Timeout ->
- exit(Driver, timeout),
- Parent ! {self(), {error, timeout}}
+ if ServerHello =/= undefined ->
+ %% wait for a startup message
+ receive
+ {Driver, {DriverVersion, Service, _}} ->
+ Parent ! {self(), {ok, Service}},
+ ubf_client_loop(Parent, Driver);
+ {'EXIT', Driver, Reason} ->
+ Parent ! {self(), {error, Reason}};
+ {'EXIT', Parent, Reason} ->
+ exit(Driver, Reason)
+ after Timeout ->
+ exit(Driver, timeout),
+ Parent ! {self(), {error, timeout}}
+ end;
+ true ->
+ Parent ! {self(), {ok, undefined}},
+ ubf_client_loop(Parent, Driver)
end;
{error, _E} ->
Parent ! {self(), {error, socket}}
diff --git a/src/ubf_server.erl b/src/ubf_server.erl
index 80e070e..fc83757 100644
--- a/src/ubf_server.erl
+++ b/src/ubf_server.erl
@@ -24,13 +24,19 @@
%%% terms between a UBF(B) contract checking server and a client
%%% that does not support the UBF(A) wire format but does support
%%% Erlang's native wire formats.
-%%% JSF, a.k.a the JSON Server Format. Similar to EBF, except
+%%% JSF, a.k.a the JSon Format. Similar to EBF, except
%%% that JavaScript's JSON encoding is used for the wire protocol
-%%% instead of UBF(A) or Erlang's native wire formats.
-%%% NOTE: This server is currently incomplete. Source code from
-%%% Gemini Mobile Technologies, Inc. is not yet available to
-%%% help glue JSF-style encoding to a full HTTP/JSON-RPC service.
-%%%
+%%% instead of UBF(A) or Erlang's native wire formats.
+%%% TBF, a.k.a the Thrift Binary Format. Similar to EBF, except
+%%% that Thrift's binary encoding is used for the wire protocol
+%%% instead of UBF(A) or Erlang's native wire formats.
+%%% PBF, a.k.a the Google's Protocol Buffers Format. Similar to
+%%% EBF, except that Google's Protocol Buffers binary encoding is used
+%%% for the wire protocol instead of UBF(A) or Erlang's native wire
+%%% formats.
+%%% ABF, a.k.a the Avro Binary Format. Similar to EBF, except
+%%% that Avro's binary encoding is used for the wire protocol
+%%% instead of UBF(A) or Erlang's native wire formats.
%%%
%%%
%%% There is no "stop" function. To stop the server, instead stop the
@@ -88,15 +94,17 @@ start(Name, PluginModules, Port) ->
%% {maxconn, integer()} ... Maximum number of simultaneous TCP
%% connections allowed.
%% Default: 10,000.
-%% {proto, {ubf | ebf | jsf}} ... Enable the UBF, EBF, or JSF version
-%% of the protocol's wire format.
+%% {proto, {ubf | ebf | jsf | tbf | pbf | abf}} ... Enable the
+%% UBF, EBF, JSF, TBF, PBF or ABF version of the protocol's wire
+%% format.
%% Default: ubf.
%% {registeredname, atom()} ... Set the name to be registered for
%% the TCP listener. If undefined, a default name is automatically
%% registered.
%% Default: undefined.
-%% {serverhello, string()} ... Meta contract greeting string, sent
-%% when a client first connects to the server.
+%% {serverhello, string() | undefined} ... Meta contract greeting
+%% string, sent when a client first connects to the server. If
+%% undefined, server hello is not sent to the client.
%% Default: "meta_server"
%% {statelessrpc, true | false} ... run the stateless variety of
%% a UBF(B) contract. A stateless contract is an extension of
@@ -169,7 +177,13 @@ start_ubf_listener(MetaContract, Port, Server, Options) ->
ebf ->
{ebf_driver, 'ebf1.0', 4};
jsf ->
- {jsf_driver, 'jsf1.0', 0}
+ {jsf_driver, 'jsf1.0', 0};
+ tbf ->
+ {tbf_driver, 'tbf1.0', 0};
+ pbf ->
+ {pbf_driver, 'pbf1.0', 0};
+ abf -> %% @TODO ubf_driver -> abf_driver
+ {ubf_driver, 'abf1.0', 0}
end,
IdleTimer =
case proplists:get_value(idletimer,Options,16#ffffffff) of
@@ -200,9 +214,13 @@ start_ubf_listener(MetaContract, Port, Server, Options) ->
contract_manager:start(ProcessOptions)
end,
Handler = ubf_plugin_handler:start_handler(ProcessOptions),
- %% (The next three lines are pretty devious but they
- %% work !) send hello back to the opening program
- self() ! {self(), {DriverVersion, ?S(ServerHello), help()}},
+ %% Next few lines are pretty devious but they work!
+ if ServerHello =/= undefined ->
+ %% send hello back to the opening program
+ self() ! {self(), {DriverVersion, ?S(ServerHello), help()}};
+ true ->
+ noop
+ end,
%% swap the driver
contract_driver:relay(DriverModule, self(), ContractManager),
ContractManager !