%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
%%
-module(ssh_eqc_encode_decode).
-compile(export_all).
-proptest(eqc).
-proptest([triq,proper]).
-ifndef(EQC).
-ifndef(PROPER).
-ifndef(TRIQ).
-define(EQC,true).
%%-define(PROPER,true).
%%-define(TRIQ,true).
-endif.
-endif.
-endif.
-ifdef(EQC).
-include_lib("eqc/include/eqc.hrl").
-define(MOD_eqc,eqc).
-else.
-ifdef(PROPER).
-include_lib("proper/include/proper.hrl").
-define(MOD_eqc,proper).
-else.
-ifdef(TRIQ).
-define(MOD_eqc,triq).
-include_lib("triq/include/triq.hrl").
-endif.
-endif.
-endif.
%%% Properties:
prop_ssh_decode() ->
?FORALL(Msg, ssh_msg(),
try ssh_message:decode(Msg)
of
_ -> true
catch
C:E -> io:format('~p:~p~n',[C,E]),
false
end
).
%%% This fails because ssh_message is not symmetric in encode and decode regarding data types
prop_ssh_decode_encode() ->
?FORALL(Msg, ssh_msg(),
Msg == ssh_message:encode(ssh_message:decode(Msg))
).
%%%================================================================
%%%
%%% Scripts to generate message generators
%%%
%% awk '/^( |\t)+byte( |\t)+SSH/,/^( |\t)*$/{print}' rfc425?.txt | sed 's/^\( \|\\t\)*//' > msgs.txt
%% awk '/^byte( |\t)+SSH/{print $2","}' < msgs.txt
%% awk 'BEGIN{print "%%%---- BEGIN GENERATED";prev=0} END{print " >>.\n%%%---- END GENERATED"} /^byte( |\t)+SSH/{if (prev==1) print " >>.\n"; prev=1; printf "%c%s%c",39,$2,39; print "()->\n <<?"$2;next} /^string( |\t)+\"/{print " ,"$2;next} /^string( |\t)+.*address/{print " ,(ssh_string_address())/binary %%",$2,$3,$4,$5,$6;next}/^string( |\t)+.*US-ASCII/{print " ,(ssh_string_US_ASCII())/binary %%",$2,$3,$4,$5,$6;next} /^string( |\t)+.*UTF-8/{print " ,(ssh_string_UTF_8())/binary %% ",$2,$3,$4,$5,$6;next} /^[a-z0-9]+( |\t)/{print " ,(ssh_"$1"())/binary %%",$2,$3,$4,$5,$6;next} /^byte\[16\]( |\t)+/{print" ,(ssh_byte_16())/binary %%",$2,$3,$4,$5,$6;next} /^name-list( |\t)+/{print" ,(ssh_name_list())/binary %%",$2,$3,$4,$5,$6;next} /./{print "?? %%",$0}' < msgs.txt > gen.txt
%%%================================================================
%%%
%%% Generators
%%%
ssh_msg() -> ?LET(M,oneof(
[[msg_code('SSH_MSG_CHANNEL_CLOSE'),gen_uint32()],
[msg_code('SSH_MSG_CHANNEL_DATA'),gen_uint32(),gen_string( )],
[msg_code('SSH_MSG_CHANNEL_EOF'),gen_uint32()],
[msg_code('SSH_MSG_CHANNEL_EXTENDED_DATA'),gen_uint32(),gen_uint32(),gen_string( )],
[msg_code('SSH_MSG_CHANNEL_FAILURE'),gen_uint32()],
[msg_code('SSH_MSG_CHANNEL_OPEN'),gen_string("direct-tcpip"),gen_uint32(),gen_uint32(),gen_uint32(),gen_string( ),gen_uint32(),gen_string( ),gen_uint32()],
[msg_code('SSH_MSG_CHANNEL_OPEN'),gen_string("forwarded-tcpip"),gen_uint32(),gen_uint32(),gen_uint32(),gen_string( ),gen_uint32(),gen_string( ),gen_uint32()],
[msg_code('SSH_MSG_CHANNEL_OPEN'),gen_string("session"),gen_uint32(),gen_uint32(),gen_uint32()],
[msg_code('SSH_MSG_CHANNEL_OPEN'),gen_string("x11"),gen_uint32(),gen_uint32(),gen_uint32(),gen_string( ),gen_uint32()],
[msg_code('SSH_MSG_CHANNEL_OPEN'),gen_string( ),gen_uint32(),gen_uint32(),gen_uint32()],
[msg_code('SSH_MSG_CHANNEL_OPEN_CONFIRMATION'),gen_uint32(),gen_uint32(),gen_uint32(),gen_uint32()],
[msg_code('SSH_MSG_CHANNEL_OPEN_FAILURE'),gen_uint32(),gen_uint32(),gen_string( ),gen_string( )],
[msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("env"),gen_boolean(),gen_string( ),gen_string( )],
[msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("exec"),gen_boolean(),gen_string( )],
[msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("exit-signal"),0,gen_string( ),gen_boolean(),gen_string( ),gen_string( )],
[msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("exit-status"),0,gen_uint32()],
[msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("pty-req"),gen_boolean(),gen_string( ),gen_uint32(),gen_uint32(),gen_uint32(),gen_uint32(),gen_string( )],
[msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("shell"),gen_boolean()],
[msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("signal"),0,gen_string( )],
[msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("subsystem"),gen_boolean(),gen_string( )],
[msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("window-change"),0,gen_uint32(),gen_uint32(),gen_uint32(),gen_uint32()],
[msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("x11-req"),gen_boolean(),gen_boolean(),gen_string( ),gen_string( ),gen_uint32()],
[msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("xon-xoff"),0,gen_boolean()],
[msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string( ),gen_boolean()],
[msg_code('SSH_MSG_CHANNEL_SUCCESS'),gen_uint32()],
[msg_code('SSH_MSG_CHANNEL_WINDOW_ADJUST'),gen_uint32(),gen_uint32()],
%%Assym [msg_code('SSH_MSG_DEBUG'),gen_boolean(),gen_string( ),gen_string( )],
[msg_code('SSH_MSG_DISCONNECT'),gen_uint32(),gen_string( ),gen_string( )],
%%Assym [msg_code('SSH_MSG_GLOBAL_REQUEST'),gen_string("cancel-tcpip-forward"),gen_boolean(),gen_string( ),gen_uint32()],
%%Assym [msg_code('SSH_MSG_GLOBAL_REQUEST'),gen_string("tcpip-forward"),gen_boolean(),gen_string( ),gen_uint32()],
%%Assym [msg_code('SSH_MSG_GLOBAL_REQUEST'),gen_string( ),gen_boolean()],
[msg_code('SSH_MSG_IGNORE'),gen_string( )],
%% [msg_code('SSH_MSG_KEXDH_INIT'),gen_mpint()],
%% [msg_code('SSH_MSG_KEXDH_REPLY'),gen_string( ),gen_mpint(),gen_string( )],
%% [msg_code('SSH_MSG_KEXINIT'),gen_byte(16),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_boolean(),gen_uint32()],
[msg_code('SSH_MSG_KEX_DH_GEX_GROUP'),gen_mpint(),gen_mpint()],
[msg_code('SSH_MSG_NEWKEYS')],
[msg_code('SSH_MSG_REQUEST_FAILURE')],
[msg_code('SSH_MSG_REQUEST_SUCCESS')],
[msg_code('SSH_MSG_REQUEST_SUCCESS'),gen_uint32()],
[msg_code('SSH_MSG_SERVICE_ACCEPT'),gen_string( )],
[msg_code('SSH_MSG_SERVICE_REQUEST'),gen_string( )],
[msg_code('SSH_MSG_UNIMPLEMENTED'),gen_uint32()],
[msg_code('SSH_MSG_USERAUTH_BANNER'),gen_string( ),gen_string( )],
[msg_code('SSH_MSG_USERAUTH_FAILURE'),gen_name_list(),gen_boolean()],
[msg_code('SSH_MSG_USERAUTH_PASSWD_CHANGEREQ'),gen_string( ),gen_string( )],
[msg_code('SSH_MSG_USERAUTH_PK_OK'),gen_string( ),gen_string( )],
[msg_code('SSH_MSG_USERAUTH_SUCCESS')]
]
), list_to_binary(M)).
%%%================================================================
%%%
%%% Generator
%%%
do() ->
io_lib:format('[~s~n]',
[write_gen(
files(["rfc4254.txt",
"rfc4253.txt",
"rfc4419.txt",
"rfc4252.txt",
"rfc4256.txt"]))]).
write_gen(L) when is_list(L) ->
string:join(lists:map(fun write_gen/1, L), ",\n ");
write_gen({MsgName,Args}) ->
lists:flatten(["[",generate_args([MsgName|Args]),"]"]).
generate_args(As) -> string:join([generate_arg(A) || A <- As], ",").
generate_arg({<<"string">>, <<"\"",B/binary>>}) ->
S = get_string($",B),
["gen_string(\"",S,"\")"];
generate_arg({<<"string">>, _}) -> "gen_string( )";
generate_arg({<<"byte[",B/binary>>, _}) ->
io_lib:format("gen_byte(~p)",[list_to_integer(get_string($],B))]);
generate_arg({<<"byte">> ,_}) -> "gen_byte()";
generate_arg({<<"uint16">>,_}) -> "gen_uint16()";
generate_arg({<<"uint32">>,_}) -> "gen_uint32()";
generate_arg({<<"uint64">>,_}) -> "gen_uint64()";
generate_arg({<<"mpint">>,_}) -> "gen_mpint()";
generate_arg({<<"name-list">>,_}) -> "gen_name_list()";
generate_arg({<<"boolean">>,<<"FALSE">>}) -> "0";
generate_arg({<<"boolean">>,<<"TRUE">>}) -> "1";
generate_arg({<<"boolean">>,_}) -> "gen_boolean()";
generate_arg({<<"....">>,_}) -> ""; %% FIXME
generate_arg(Name) when is_binary(Name) ->
lists:flatten(["msg_code('",binary_to_list(Name),"')"]).
gen_boolean() -> choose(0,1).
gen_byte() -> choose(0,255).
gen_uint16() -> gen_byte(2).
gen_uint32() -> gen_byte(4).
gen_uint64() -> gen_byte(8).
gen_byte(N) when N>0 -> [gen_byte() || _ <- lists:seq(1,N)].
gen_char() -> choose($a,$z).
gen_mpint() -> ?LET(Size, choose(1,20),
?LET(Str, vector(Size, gen_byte()),
gen_string( strip_0s(Str) )
)).
strip_0s([0|T]) -> strip_0s(T);
strip_0s(X) -> X.
gen_string() ->
?LET(Size, choose(0,10),
?LET(Vector,vector(Size, gen_char()),
gen_string(Vector)
)).
gen_string(S) when is_binary(S) -> gen_string(binary_to_list(S));
gen_string(S) when is_list(S) -> uint32_to_list(length(S)) ++ S.
gen_name_list() ->
?LET(NumNames, choose(0,10),
?LET(L, [gen_name() || _ <- lists:seq(1,NumNames)],
gen_string( string:join(L,"," ) )
)).
gen_name() -> gen_string().
uint32_to_list(I) -> binary_to_list(<<I:32/unsigned-big-integer>>).
%%%----
get_string(Delim, B) ->
binary_to_list( element(1, split_binary(B, count_string_chars(Delim,B,0))) ).
count_string_chars(Delim, <<Delim,_/binary>>, Acc) -> Acc;
count_string_chars(Delim, <<_,B/binary>>, Acc) -> count_string_chars(Delim, B, Acc+1).
-define(MSG_CODE(Name,Num),
msg_code(Name) -> Num;
msg_code(Num) -> Name
).
?MSG_CODE('SSH_MSG_USERAUTH_REQUEST', 50);
?MSG_CODE('SSH_MSG_USERAUTH_FAILURE', 51);
?MSG_CODE('SSH_MSG_USERAUTH_SUCCESS', 52);
?MSG_CODE('SSH_MSG_USERAUTH_BANNER', 53);
?MSG_CODE('SSH_MSG_USERAUTH_PK_OK', 60);
?MSG_CODE('SSH_MSG_USERAUTH_PASSWD_CHANGEREQ', 60);
?MSG_CODE('SSH_MSG_DISCONNECT', 1);
?MSG_CODE('SSH_MSG_IGNORE', 2);
?MSG_CODE('SSH_MSG_UNIMPLEMENTED', 3);
?MSG_CODE('SSH_MSG_DEBUG', 4);
?MSG_CODE('SSH_MSG_SERVICE_REQUEST', 5);
?MSG_CODE('SSH_MSG_SERVICE_ACCEPT', 6);
?MSG_CODE('SSH_MSG_KEXINIT', 20);
?MSG_CODE('SSH_MSG_NEWKEYS', 21);
?MSG_CODE('SSH_MSG_GLOBAL_REQUEST', 80);
?MSG_CODE('SSH_MSG_REQUEST_SUCCESS', 81);
?MSG_CODE('SSH_MSG_REQUEST_FAILURE', 82);
?MSG_CODE('SSH_MSG_CHANNEL_OPEN', 90);
?MSG_CODE('SSH_MSG_CHANNEL_OPEN_CONFIRMATION', 91);
?MSG_CODE('SSH_MSG_CHANNEL_OPEN_FAILURE', 92);
?MSG_CODE('SSH_MSG_CHANNEL_WINDOW_ADJUST', 93);
?MSG_CODE('SSH_MSG_CHANNEL_DATA', 94);
?MSG_CODE('SSH_MSG_CHANNEL_EXTENDED_DATA', 95);
?MSG_CODE('SSH_MSG_CHANNEL_EOF', 96);
?MSG_CODE('SSH_MSG_CHANNEL_CLOSE', 97);
?MSG_CODE('SSH_MSG_CHANNEL_REQUEST', 98);
?MSG_CODE('SSH_MSG_CHANNEL_SUCCESS', 99);
?MSG_CODE('SSH_MSG_CHANNEL_FAILURE', 100);
?MSG_CODE('SSH_MSG_USERAUTH_INFO_REQUEST', 60);
?MSG_CODE('SSH_MSG_USERAUTH_INFO_RESPONSE', 61);
?MSG_CODE('SSH_MSG_KEX_DH_GEX_REQUEST_OLD', 30);
?MSG_CODE('SSH_MSG_KEX_DH_GEX_REQUEST', 34);
?MSG_CODE('SSH_MSG_KEX_DH_GEX_GROUP', 31);
?MSG_CODE('SSH_MSG_KEX_DH_GEX_INIT', 32);
?MSG_CODE('SSH_MSG_KEX_DH_GEX_REPLY', 33).
%%%=============================================================================
%%%=============================================================================
%%%=============================================================================
files(Fs) ->
Defs = lists:usort(lists:flatten(lists:map(fun file/1, Fs))),
DefinedIDs = lists:usort([binary_to_list(element(1,D)) || D <- Defs]),
WantedIDs = lists:usort(wanted_messages()),
Missing = WantedIDs -- DefinedIDs,
case Missing of
[] -> ok;
_ -> io:format('%% Warning: missing ~p~n', [Missing])
end,
Defs.
file(F) ->
{ok,B} = file:read_file(F),
hunt_msg_def(B).
hunt_msg_def(<<"\n",B/binary>>) -> some_hope(skip_blanks(B));
hunt_msg_def(<<_, B/binary>>) -> hunt_msg_def(B);
hunt_msg_def(<<>>) -> [].
some_hope(<<"byte ", B/binary>>) -> try_message(skip_blanks(B));
some_hope(B) -> hunt_msg_def(B).
try_message(B = <<"SSH_MSG_",_/binary>>) ->
{ID,Rest} = get_id(B),
case lists:member(binary_to_list(ID), wanted_messages()) of
true ->
{Lines,More} = get_def_lines(skip_blanks(Rest), []),
[{ID,lists:reverse(Lines)} | hunt_msg_def(More)];
false ->
hunt_msg_def(Rest)
end;
try_message(B) -> hunt_msg_def(B).
skip_blanks(<<32, B/binary>>) -> skip_blanks(B);
skip_blanks(<< 9, B/binary>>) -> skip_blanks(B);
skip_blanks(B) -> B.
get_def_lines(B0 = <<"\n",B/binary>>, Acc) ->
{ID,Rest} = get_id(skip_blanks(B)),
case {size(ID), skip_blanks(Rest)} of
{0,<<"....",More/binary>>} ->
{Text,LineEnd} = get_to_eol(skip_blanks(More)),
get_def_lines(LineEnd, [{<<"....">>,Text}|Acc]);
{0,_} ->
{Acc,B0};
{_,Rest1} ->
{Text,LineEnd} = get_to_eol(Rest1),
get_def_lines(LineEnd, [{ID,Text}|Acc])
end;
get_def_lines(B, Acc) ->
{Acc,B}.
get_to_eol(B) -> split_binary(B, count_to_eol(B,0)).
count_to_eol(<<"\n",_/binary>>, Acc) -> Acc;
count_to_eol(<<>>, Acc) -> Acc;
count_to_eol(<<_,B/binary>>, Acc) -> count_to_eol(B,Acc+1).
get_id(B) -> split_binary(B, count_id_chars(B,0)).
count_id_chars(<<C,B/binary>>, Acc) when $A=<C,C=<$Z -> count_id_chars(B,Acc+1);
count_id_chars(<<C,B/binary>>, Acc) when $a=<C,C=<$z -> count_id_chars(B,Acc+1);
count_id_chars(<<C,B/binary>>, Acc) when $0=<C,C=<$9 -> count_id_chars(B,Acc+1);
count_id_chars(<<"_",B/binary>>, Acc) -> count_id_chars(B,Acc+1);
count_id_chars(<<"-",B/binary>>, Acc) -> count_id_chars(B,Acc+1); %% e.g name-list
count_id_chars(<<"[",B/binary>>, Acc) -> count_id_chars(B,Acc+1); %% e.g byte[16]
count_id_chars(<<"]",B/binary>>, Acc) -> count_id_chars(B,Acc+1); %% e.g byte[16]
count_id_chars(_, Acc) -> Acc.
wanted_messages() ->
["SSH_MSG_CHANNEL_CLOSE",
"SSH_MSG_CHANNEL_DATA",
"SSH_MSG_CHANNEL_EOF",
"SSH_MSG_CHANNEL_EXTENDED_DATA",
"SSH_MSG_CHANNEL_FAILURE",
"SSH_MSG_CHANNEL_OPEN",
"SSH_MSG_CHANNEL_OPEN_CONFIRMATION",
"SSH_MSG_CHANNEL_OPEN_FAILURE",
"SSH_MSG_CHANNEL_REQUEST",
"SSH_MSG_CHANNEL_SUCCESS",
"SSH_MSG_CHANNEL_WINDOW_ADJUST",
"SSH_MSG_DEBUG",
"SSH_MSG_DISCONNECT",
"SSH_MSG_GLOBAL_REQUEST",
"SSH_MSG_IGNORE",
"SSH_MSG_KEXDH_INIT",
"SSH_MSG_KEXDH_REPLY",
"SSH_MSG_KEXINIT",
"SSH_MSG_KEX_DH_GEX_GROUP",
"SSH_MSG_KEX_DH_GEX_REQUEST",
"SSH_MSG_KEX_DH_GEX_REQUEST_OLD",
"SSH_MSG_NEWKEYS",
"SSH_MSG_REQUEST_FAILURE",
"SSH_MSG_REQUEST_SUCCESS",
"SSH_MSG_SERVICE_ACCEPT",
"SSH_MSG_SERVICE_REQUEST",
"SSH_MSG_UNIMPLEMENTED",
"SSH_MSG_USERAUTH_BANNER",
"SSH_MSG_USERAUTH_FAILURE",
%% hard args "SSH_MSG_USERAUTH_INFO_REQUEST",
%% "SSH_MSG_USERAUTH_INFO_RESPONSE",
"SSH_MSG_USERAUTH_PASSWD_CHANGEREQ",
"SSH_MSG_USERAUTH_PK_OK",
%%rfc4252 p12 error "SSH_MSG_USERAUTH_REQUEST",
"SSH_MSG_USERAUTH_SUCCESS"].