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 @@ + + + +Module abf + + + + +
+ +

Module abf

+ + + +

Function Index

+ + + + +
decode/3
decode/4
decode_init/0
encode/3
+ +

Function Details

+ +

decode/3

+
+

decode(X, Mod, State) -> any()

+
+ +

decode/4

+
+

decode(X, Mod, Cont, State) -> any()

+
+ +

decode_init/0

+
+

decode_init() -> any()

+
+ +

encode/3

+
+

encode(X, Mod, State) -> any()

+
+
+ + +

Generated EDoc, $Id$

+ + diff --git a/edoc/abf_driver.html b/edoc/abf_driver.html new file mode 100644 index 0000000..45d43ae --- /dev/null +++ b/edoc/abf_driver.html @@ -0,0 +1,53 @@ + + + +Module abf_driver + + + + +
+ +

Module abf_driver

+Protocol driver process for Avro protocol sessions. + + +

Description

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. + +

Function Index

+ + + + +
decode/4
encode/2
init/1
start/1
+ +

Function Details

+ +

decode/4

+
+

decode(Contract, Cont, Binary, CallBack) -> any()

+
+ +

encode/2

+
+

encode(Contract, Term) -> any()

+
+ +

init/1

+
+

init(Contract) -> any()

+
+ +

start/1

+
+

start(Contract) -> any()

+
+
+ + +

Generated EDoc, $Id$

+ + diff --git a/edoc/edoc-info b/edoc/edoc-info index ac6932a..f089cd6 100644 --- a/edoc/edoc-info +++ b/edoc/edoc-info @@ -1,8 +1,8 @@ {application,ubf}. {packages,[]}. -{modules,[contract_driver,contract_lex,contract_manager,contract_manager_tlog, - contract_parser,contract_yecc,contracts,contracts_abnf,ebf_driver, - jsf,jsf_driver,jsf_utils,proc_socket_server,proc_utils,tbf, - tbf_driver,ubf,ubf_client,ubf_driver,ubf_plugin_handler, - ubf_plugin_meta_stateful,ubf_plugin_meta_stateless,ubf_server, - ubf_utils]}. +{modules,[abf,abf_driver,contract_driver,contract_lex,contract_manager, + contract_manager_tlog,contract_parser,contract_yecc,contracts, + contracts_abnf,ebf_driver,jsf,jsf_driver,jsf_utils,pbf,pbf_driver, + proc_socket_server,proc_utils,tbf,tbf_driver,ubf,ubf_client, + ubf_driver,ubf_plugin_handler,ubf_plugin_meta_stateful, + ubf_plugin_meta_stateless,ubf_server,ubf_utils]}. diff --git a/edoc/modules-frame.html b/edoc/modules-frame.html index 0130d19..49d9eca 100644 --- a/edoc/modules-frame.html +++ b/edoc/modules-frame.html @@ -7,6 +7,8 @@

Modules

+ + @@ -19,6 +21,8 @@ + + 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 + + + +
abf
abf_driver
contract_driver
contract_lex
contract_manager
jsf
jsf_driver
jsf_utils
pbf
pbf_driver
proc_socket_server
proc_utils
tbf
Overviewerlang logo
+
+ +

Module pbf

+Functions for Google's Protocol Buffers<->Erlang data conversion. + + +

Description

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

+ + + +

Function Index

+ + + + + + + + + + + + + +
atom_to_binary/1
binary_to_existing_atom/1
contract_records/0
decode/1
decode/2
decode/3
decode_init/0
decode_print/1
decode_print/2
encode/1
encode/2
encode_print/1
encode_print/2
+ +

Function Details

+ +

atom_to_binary/1

+
+

atom_to_binary(X) -> any()

+
+ +

binary_to_existing_atom/1

+
+

binary_to_existing_atom(X) -> any()

+
+ +

contract_records/0

+
+

contract_records() -> any()

+
+ +

decode/1

+
+

decode(X) -> any()

+
+ +

decode/2

+
+

decode(X, Mod) -> any()

+
+ +

decode/3

+
+

decode(X, Mod, X3) -> any()

+
+ +

decode_init/0

+
+

decode_init() -> any()

+
+ +

decode_print/1

+
+

decode_print(X) -> any()

+
+ +

decode_print/2

+
+

decode_print(X, Mod) -> any()

+
+ +

encode/1

+
+

encode(X) -> any()

+
+ +

encode/2

+
+

encode(X, Mod) -> any()

+
+ +

encode_print/1

+
+

encode_print(X) -> any()

+
+ +

encode_print/2

+
+

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. + + +

Description

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. + +

Function Index

+ + + + +
decode/4
encode/2
init/1
start/1
+ +

Function Details

+ +

decode/4

+
+

decode(Contract, Cont, Binary, CallBack) -> any()

+
+ +

encode/2

+
+

encode(Contract, Term) -> any()

+
+ +

init/1

+
+

init(Contract) -> any()

+
+ +

start/1

+
+

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()

-

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.

ip_address()

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 == +%% +%% +%% +%% == 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 == +%% +%% +%% +%% @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 !