Move protocol encoding into a separate modules

This commit is contained in:
Michael Santos
2014-12-02 07:36:43 -05:00
parent af4a17e5f1
commit bab762ef2e
7 changed files with 254 additions and 139 deletions

1
.gitignore vendored
View File

@@ -10,3 +10,4 @@ c_src/alcove_call.h
c_src/alcove_calls.h
c_src/alcove_version.h
src/alcove.erl
src/alcove_proto.erl

View File

@@ -32,16 +32,6 @@ license() ->
erl_syntax:comment(License).
api(Proto) ->
Calls = calls(Proto),
% Generate the function
Pattern = [],
Body = erl_syntax:tuple([ erl_syntax:atom(N) || {N,_} <- Calls ]),
Clause = erl_syntax:clause(Pattern, [], [Body]),
[erl_syntax:function(erl_syntax:atom("api"), [Clause])].
mkerl(File, Proto) ->
Module = erl_syntax:attribute(
erl_syntax:atom(module),
@@ -120,7 +110,6 @@ mkerl(File, Proto) ->
Exports_gen1,
Static,
api(Proto),
Functions
]))),
@@ -163,8 +152,6 @@ static_exports() ->
{stderr,1}, {stderr,2}, {stderr,3},
{eof,2}, {eof,3},
{event,1}, {event,2}, {event,3},
{encode,3},
{command,1},
{call,2}, {call,3}, {call,4}, {call,5}].
static() ->
@@ -315,27 +302,6 @@ event(Drv, Pids, Timeout) ->
alcove_drv:event(Drv, Pids, Timeout).
";
static({encode,3}) ->
"
encode(Call, Pids, Arg) when is_atom(Call), is_list(Pids), is_list(Arg) ->
Bin = alcove_drv:encode(command(Call), Arg),
alcove_drv:msg(Pids, Bin).
";
static({command,1}) ->
"
command(Cmd) when is_atom(Cmd) ->
lookup(Cmd, api()).
lookup(Cmd, Cmds) ->
lookup(Cmd, 1, Cmds, tuple_size(Cmds)).
lookup(Cmd, N, Cmds, _Max) when Cmd =:= element(N, Cmds) ->
% Convert to 0 offset
N-1;
lookup(Cmd, N, Cmds, Max) when N =< Max ->
lookup(Cmd, N+1, Cmds, Max).
";
static({call,2}) ->
"
call(Drv, Command) ->
@@ -354,8 +320,7 @@ call(Drv, Pids, Command, Argv) ->
static({call,5}) ->
"
call(Drv, Pids, Command, Argv, Timeout) when is_pid(Drv), is_list(Argv) ->
case alcove_drv:call(Drv, Pids, encode(Command, Pids, Argv),
call_returns(Command), Timeout) of
case alcove_drv:call(Drv, Pids, Command, Argv, Timeout) of
badarg ->
erlang:error(badarg, [Drv, Command, Argv]);
undef ->
@@ -363,11 +328,6 @@ call(Drv, Pids, Command, Argv, Timeout) when is_pid(Drv), is_list(Argv) ->
Reply ->
Reply
end.
call_returns(execve) -> false;
call_returns(execvp) -> false;
call_returns(exit) -> false;
call_returns(_) -> true.
".
includes(Header) ->

140
bin/alcove_proto.escript Executable file
View File

@@ -0,0 +1,140 @@
#!/usr/bin/env escript
%%%
%%% Generate the alcove.erl file
%%%
main([]) ->
File = "alcove_proto.erl",
Proto = "c_src/alcove_call.proto",
main([File, Proto]);
main([File, Proto]) ->
mkerl(File, Proto).
license() ->
{{Year,_,_},{_,_,_}} = calendar:universal_time(),
Date = integer_to_list(Year),
License = [
" Copyright (c) " ++ Date ++ ", Michael Santos <michael.santos@gmail.com>",
" Permission to use, copy, modify, and/or distribute this software for any",
" purpose with or without fee is hereby granted, provided that the above",
" copyright notice and this permission notice appear in all copies.",
"",
" THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES",
" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF",
" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR",
" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES",
" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN",
" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF",
" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE."],
erl_syntax:comment(License).
mkerl(File, Proto) ->
Module = erl_syntax:attribute(
erl_syntax:atom(module),
[erl_syntax:atom(filename:basename(File, ".erl"))]
),
Includes = includes(["alcove.hrl"]),
% Type specs
Specs = erl_syntax:comment(["%__SPECS__%%"]),
% Any hardcoded functions will be included here
Static = erl_syntax:comment(["%__STATIC__%%"]),
% Generate the list of exports
Comment_static = erl_syntax:comment([" Static functions"]),
Exports_static = erl_syntax:attribute(erl_syntax:atom(export), [
erl_syntax:list([
erl_syntax:arity_qualifier(erl_syntax:atom(Fun), erl_syntax:integer(Arity))
|| {Fun, Arity} <- static_exports() ])
]),
Comment_gen = erl_syntax:comment([" Generated functions"]),
Exports_gen = erl_syntax:attribute(erl_syntax:atom(export), [
erl_syntax:list([
erl_syntax:arity_qualifier(erl_syntax:atom(calls), erl_syntax:integer(0))
])]),
Body = erl_syntax:list([ erl_syntax:atom(N) || {N,_} <- proto(Proto) ]),
Clause = erl_syntax:clause([], [], [Body]),
Fun = [erl_syntax:function(erl_syntax:atom("calls"), [Clause])],
Code0 = erl_prettypr:format(erl_syntax:form_list(lists:flatten([
license(),
Module,
Includes,
Specs,
Comment_static,
Exports_static,
Comment_gen,
Exports_gen,
Static,
Fun
]))),
Code = lists:foldl(fun({Marker, Generated}, Text) ->
re:replace(Text, Marker, Generated)
end,
Code0,
[
{"%%__STATIC__%%", static()},
{"%%__SPECS__%%", specs()}
]),
% io:format("~s~n", [Code]).
file:write_file(File, [Code]).
% List the supported alcove API functions
proto(Proto) ->
{ok, Bin} = file:read_file(Proto),
Fun = binary:split(Bin, <<"\n">>, [trim,global]),
call_to_fun(Fun, []).
call_to_fun([], Acc) ->
lists:reverse(Acc);
call_to_fun([H|T], Acc) ->
[Fun, Arity] = binary:split(H, <<"/">>),
call_to_fun(T, [{binary_to_list(Fun), b2i(Arity)}|Acc]).
b2i(N) when is_binary(N) ->
list_to_integer(binary_to_list(N)).
static_exports() ->
[{call,1},{returns,1}].
static() ->
[ static({Fun, Arity}) || {Fun, Arity} <- static_exports() ].
static({call,1}) ->
"
call(Call) when is_atom(Call) ->
lookup(Call, calls(), 0).
lookup(Call, [Call|_], N) ->
N;
lookup(Call, [_|Calls], N) ->
lookup(Call, Calls, N+1).
";
static({returns,1}) ->
"
returns(execve) -> false;
returns(execvp) -> false;
returns(exit) -> false;
returns(_) -> true.
".
includes(Header) ->
[ erl_syntax:attribute(erl_syntax:atom(include), [erl_syntax:string(N)]) || N <- Header ].
% FIXME hack for hard coding typespecs
specs() ->
"".

View File

@@ -33,6 +33,7 @@
{compile, "bin/alcove_version.escript c_src/alcove_version.h"},
{compile, "bin/alcove_calls.sh c_src/alcove_call.proto > c_src/alcove_calls.h"},
{compile, "bin/alcove_call.sh c_src/alcove_call.proto > c_src/alcove_call.h"},
{compile, "bin/alcove_proto.escript src/alcove_proto.erl c_src/alcove_call.proto"},
{compile, "bin/alcove.escript src/alcove.erl c_src/alcove_call.proto"}
]}.

97
src/alcove_codec.erl Normal file
View File

@@ -0,0 +1,97 @@
%%% Copyright (c) 2014, Michael Santos <michael.santos@gmail.com>
%%%
%%% Permission to use, copy, modify, and/or distribute this software for any
%%% purpose with or without fee is hereby granted, provided that the above
%%% copyright notice and this permission notice appear in all copies.
%%%
%%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(alcove_codec).
-include_lib("alcove/include/alcove.hrl").
-export([call/3, stdin/2]).
-export([encode/2, encode/3, decode/1]).
call(Call, Pids, Arg) ->
Bin = encode(alcove_proto:call(Call), Arg),
Payload = case Pids of
[] ->
Bin;
_ ->
Size = iolist_size(Bin),
[<<?UINT16(Size)>>, Bin]
end,
stdin(Pids, Payload).
stdin([], Data) ->
Data;
stdin(Pids, Data) ->
hdr(Pids, Data).
hdr(Pids, Data) ->
hdr_1(lists:reverse(Pids), Data).
hdr_1([], [_Length|Acc]) ->
Acc;
hdr_1([Pid|Pids], Acc) ->
Size = iolist_size(Acc) + 2 + 4,
hdr_1(Pids, [<<?UINT16(Size)>>, <<?UINT16(?ALCOVE_MSG_STDIN)>>, <<?UINT32(Pid)>>|Acc]).
encode(Command, Arg) when is_integer(Command), is_list(Arg) ->
encode(?ALCOVE_MSG_CALL, Command, Arg).
encode(Type, Command, Arg) when is_integer(Type), is_integer(Command), is_list(Arg) ->
<<
?UINT16(Type),
?UINT16(Command),
(term_to_binary(list_to_tuple(Arg)))/binary
>>.
decode(Msg) ->
% Re-add the message length stripped off by open_port
Len = byte_size(Msg),
decode(<<?UINT16(Len), Msg/binary>>, [], [], []).
decode(<<>>, _Pids, Msg, Acc) ->
lists:flatten(lists:reverse([Msg|Acc]));
decode(<<?UINT16(Len), ?UINT16(?ALCOVE_MSG_PROXY), ?UINT32(Pid),
Rest/binary>>, Pids, [], Acc) when Len =:= 2 + 4 + byte_size(Rest) ->
decode(Rest, [Pid|Pids], [], Acc);
decode(<<?UINT16(Len), ?UINT16(?ALCOVE_MSG_PROXY), ?UINT32(Pid),
Rest/binary>>, _Pids, Msg, Acc) when Len =:= 2 + 4 + byte_size(Rest) ->
decode(Rest, [Pid], [], [lists:reverse(Msg)|Acc]);
decode(<<?UINT16(Len), ?UINT16(?ALCOVE_MSG_STDOUT), ?UINT32(Pid),
Bin/binary>>, Pids, Msg, Acc) ->
Bytes = Len - (2 + 4),
<<Reply:Bytes/bytes, Rest/binary>> = Bin,
decode(Rest, Pids,
[{alcove_stdout, lists:reverse([Pid|Pids]), Reply}|Msg], Acc);
decode(<<?UINT16(Len), ?UINT16(?ALCOVE_MSG_STDERR), ?UINT32(Pid),
Bin/binary>>, Pids, Msg, Acc) ->
Bytes = Len - (2 + 4),
<<Reply:Bytes/bytes, Rest/binary>> = Bin,
decode(Rest, Pids,
[{alcove_stderr, lists:reverse([Pid|Pids]), Reply}|Msg], Acc);
decode(<<?UINT16(Len), ?UINT16(?ALCOVE_MSG_CALL),
Bin/binary>>, Pids, Msg, Acc) ->
Bytes = Len - 2,
<<Reply:Bytes/bytes, Rest/binary>> = Bin,
decode(Rest, Pids,
[{alcove_call, lists:reverse(Pids), binary_to_term(Reply)}|Msg],
Acc);
decode(<<?UINT16(Len), ?UINT16(?ALCOVE_MSG_EVENT),
Bin/binary>>, Pids, Msg, Acc) ->
Bytes = Len - 2,
<<Reply:Bytes/bytes, Rest/binary>> = Bin,
decode(Rest, Pids,
[{alcove_event, lists:reverse(Pids), binary_to_term(Reply)}|Msg],
Acc).

View File

@@ -18,10 +18,8 @@
%% API
-export([start/0, start/1, stop/1]).
-export([start_link/2]).
-export([call/5, encode/2, encode/3]).
-export([call/5]).
-export([stdin/3, stdout/3, stderr/3, event/3, send/2]).
-export([atom_to_type/1, type_to_atom/1]).
-export([msg/2, decode/1]).
-export([getopts/1]).
%% gen_server callbacks
@@ -52,11 +50,12 @@ start_link(Owner, Options) ->
stop(Drv) ->
gen_server:call(Drv, stop).
-spec call(ref(),[integer()],iodata(),boolean(),timeout()) -> term().
call(Drv, Pids, Data, Returns, Timeout) ->
-spec call(ref(),[integer()],atom(),list(),timeout()) -> term().
call(Drv, Pids, Command, Argv, Timeout) ->
Data = alcove_codec:call(Command, Pids, Argv),
case send(Drv, Data) of
true ->
call_reply(Drv, Pids, Returns, Timeout);
call_reply(Drv, Pids, alcove_proto:returns(Command), Timeout);
Error ->
Error
end.
@@ -74,88 +73,20 @@ send(Drv, Data) ->
stdin(Drv, [], Data) ->
send(Drv, Data);
stdin(Drv, Pids, Data) ->
Stdin = hdr(lists:reverse(Pids), [Data]),
Stdin = alcove_codec:stdin(Pids, Data),
send(Drv, Stdin).
-spec stdout(ref(),[integer()],timeout()) ->
'false' | binary().
-spec stdout(ref(),[integer()],timeout()) -> 'false' | binary().
stdout(Drv, Pids, Timeout) ->
reply(Drv, Pids, ?ALCOVE_MSG_STDOUT, Timeout).
reply(Drv, Pids, alcove_stdout, Timeout).
-spec stderr(ref(),[integer()],timeout()) ->
'false' | binary().
-spec stderr(ref(),[integer()],timeout()) -> 'false' | binary().
stderr(Drv, Pids, Timeout) ->
reply(Drv, Pids, ?ALCOVE_MSG_STDERR, Timeout).
reply(Drv, Pids, alcove_stderr, Timeout).
-spec event(ref(),[integer()],timeout()) -> term().
event(Drv, Pids, Timeout) ->
reply(Drv, Pids, ?ALCOVE_MSG_EVENT, Timeout).
msg([], Data) ->
Data;
msg(Pids, Data) ->
Size = iolist_size(Data),
hdr(lists:reverse(Pids), [<<?UINT16(Size)>>, Data]).
hdr([], [_Length|Acc]) ->
Acc;
hdr([Pid|Pids], Acc) ->
Size = iolist_size(Acc) + 2 + 4,
hdr(Pids, [<<?UINT16(Size)>>, <<?UINT16(?ALCOVE_MSG_STDIN)>>, <<?UINT32(Pid)>>|Acc]).
encode(Command, Arg) when is_integer(Command), is_list(Arg) ->
encode(?ALCOVE_MSG_CALL, Command, Arg).
encode(Type, Command, Arg) when is_integer(Type), is_integer(Command), is_list(Arg) ->
<<
?UINT16(Type),
?UINT16(Command),
(term_to_binary(list_to_tuple(Arg)))/binary
>>.
decode(Msg) ->
% Re-add the message length stripped off by open_port
Len = byte_size(Msg),
decode(<<?UINT16(Len), Msg/binary>>, [], [], []).
decode(<<>>, _Pids, Msg, Acc) ->
lists:flatten(lists:reverse([Msg|Acc]));
decode(<<?UINT16(Len), ?UINT16(?ALCOVE_MSG_PROXY), ?UINT32(Pid),
Rest/binary>>, Pids, [], Acc) when Len =:= 2 + 4 + byte_size(Rest) ->
decode(Rest, [Pid|Pids], [], Acc);
decode(<<?UINT16(Len), ?UINT16(?ALCOVE_MSG_PROXY), ?UINT32(Pid),
Rest/binary>>, _Pids, Msg, Acc) when Len =:= 2 + 4 + byte_size(Rest) ->
decode(Rest, [Pid], [], [lists:reverse(Msg)|Acc]);
decode(<<?UINT16(Len), ?UINT16(?ALCOVE_MSG_STDOUT), ?UINT32(Pid),
Bin/binary>>, Pids, Msg, Acc) ->
Bytes = Len - (2 + 4),
<<Reply:Bytes/bytes, Rest/binary>> = Bin,
decode(Rest, Pids,
[{alcove_stdout, lists:reverse([Pid|Pids]), Reply}|Msg], Acc);
decode(<<?UINT16(Len), ?UINT16(?ALCOVE_MSG_STDERR), ?UINT32(Pid),
Bin/binary>>, Pids, Msg, Acc) ->
Bytes = Len - (2 + 4),
<<Reply:Bytes/bytes, Rest/binary>> = Bin,
decode(Rest, Pids,
[{alcove_stderr, lists:reverse([Pid|Pids]), Reply}|Msg], Acc);
decode(<<?UINT16(Len), ?UINT16(?ALCOVE_MSG_CALL),
Bin/binary>>, Pids, Msg, Acc) ->
Bytes = Len - 2,
<<Reply:Bytes/bytes, Rest/binary>> = Bin,
decode(Rest, Pids,
[{alcove_call, lists:reverse(Pids), binary_to_term(Reply)}|Msg],
Acc);
decode(<<?UINT16(Len), ?UINT16(?ALCOVE_MSG_EVENT),
Bin/binary>>, Pids, Msg, Acc) ->
Bytes = Len - 2,
<<Reply:Bytes/bytes, Rest/binary>> = Bin,
decode(Rest, Pids,
[{alcove_event, lists:reverse(Pids), binary_to_term(Reply)}|Msg],
Acc).
reply(Drv, Pids, alcove_event, Timeout).
%%--------------------------------------------------------------------
%%% Callbacks
@@ -211,7 +142,7 @@ code_change(_OldVsn, State, _Extra) ->
% the parent.
handle_info({Port, {data, Data}}, #state{port = Port, pid = Pid} = State) ->
[ Pid ! {Tag, self(), Pids, Term} ||
{Tag, Pids, Term} <- decode(Data) ],
{Tag, Pids, Term} <- alcove_codec:decode(Data) ],
{noreply, State};
handle_info({'EXIT', Port, Reason}, #state{port = Port} = State) ->
@@ -252,9 +183,8 @@ call_reply(Drv, Pids, true, Timeout) ->
end.
reply(Drv, Pids, Type, Timeout) ->
Tag = type_to_atom(Type),
receive
{Tag, Drv, Pids, Event} ->
{Type, Drv, Pids, Event} ->
Event
after
Timeout ->
@@ -300,20 +230,6 @@ find_executable(Exe) ->
N
end.
atom_to_type(alcove_call) -> ?ALCOVE_MSG_CALL;
atom_to_type(alcove_event) -> ?ALCOVE_MSG_EVENT;
atom_to_type(alcove_stdin) -> ?ALCOVE_MSG_STDIN;
atom_to_type(alcove_stdout) -> ?ALCOVE_MSG_STDOUT;
atom_to_type(alcove_stderr) -> ?ALCOVE_MSG_STDERR;
atom_to_type(alcove_proxy) -> ?ALCOVE_MSG_PROXY.
type_to_atom(?ALCOVE_MSG_CALL) -> alcove_call;
type_to_atom(?ALCOVE_MSG_EVENT) -> alcove_event;
type_to_atom(?ALCOVE_MSG_STDIN) -> alcove_stdin;
type_to_atom(?ALCOVE_MSG_STDOUT) -> alcove_stdout;
type_to_atom(?ALCOVE_MSG_STDERR) -> alcove_stderr;
type_to_atom(?ALCOVE_MSG_PROXY) -> alcove_proxy.
basedir(Module) ->
case code:priv_dir(Module) of
{error, bad_name} ->

View File

@@ -122,7 +122,7 @@ msg(_) ->
0,8, 0,4, 131,100,0,2,111,107
>>,
Reply = alcove_drv:decode(Msg),
Reply = alcove_codec:decode(Msg),
?_assertEqual(
[{alcove_call,[295,551,807],<<"0.2.0">>},