Add property-based tests for codecs

This commit is contained in:
Сергей Прохоров 2019-02-24 22:14:22 +01:00
parent 2f015e2f3f
commit 229ca2b6b8
No known key found for this signature in database
GPG key ID: 1C570244E4EF3337
9 changed files with 441 additions and 41 deletions

56
test/mtp_prop_gen.erl Normal file
View file

@ -0,0 +1,56 @@
%% Common data generators for property-based tests
-module(mtp_prop_gen).
-include_lib("proper/include/proper.hrl").
-export([stream_4b/0,
packet_4b/0,
stream_16b/0,
packet_16b/0,
key/0,
iv/0,
secret/0,
dc_id/0,
codec/0
]).
%% 4-byte aligned packet: `binary()`
packet_4b() ->
?LET(IoList, proper_types:non_empty(proper_types:list(proper_types:binary(4))),
iolist_to_binary(IoList)).
%% List of 4-byte aligned packets: `[binary()]`
stream_4b() ->
proper_types:list(packet_4b()).
%% 16-byte aligned packet: `binary()`
packet_16b() ->
?LET(IoList, proper_types:non_empty(proper_types:list(proper_types:binary(16))),
iolist_to_binary(IoList)).
%% List of 16-byte aligned packets: `[binary()]`
stream_16b() ->
proper_types:list(packet_16b()).
%% 32-byte encryption key: `binary()`
key() ->
proper_types:binary(32).
%% 16-byte encryption initialization vector: `binary()`
iv() ->
proper_types:binary(16).
%% 16-byte secret: `binary()`
secret() ->
proper_types:binary(16).
%% Datacenter ID: `[-9..9]`
dc_id() ->
proper_types:integer(-9, 9).
codec() ->
Protocols = [mtp_abridged, mtp_intermediate, mtp_secure],
proper_types:oneof(
[proper_types:exactly(P)
|| P <- Protocols]).

View file

@ -0,0 +1,43 @@
-module(prop_mtp_abridged).
-include_lib("proper/include/proper.hrl").
-export([prop_codec/1, prop_stream/1]).
prop_codec(doc) ->
"Tests that any 4-byte aligned binary can be encoded and decoded back".
prop_codec() ->
?FORALL(Bin, mtp_prop_gen:packet_4b(), codec(Bin)).
codec(Bin) ->
Codec = mtp_abridged:new(),
{Data, Codec1} = mtp_abridged:encode_packet(Bin, Codec),
{ok, Decoded, _} = mtp_abridged:try_decode_packet(iolist_to_binary(Data), Codec1),
Decoded == Bin.
prop_stream(doc) ->
"Tests that any number of packets can be encoded, concatenated and decoded".
prop_stream() ->
?FORALL(Stream, mtp_prop_gen:stream_4b(), stream_codec(Stream)).
stream_codec(Stream) ->
Codec = mtp_abridged:new(),
{BinStream, Codec1} =
lists:foldl(
fun(Bin, {Acc, Codec1}) ->
{Data, Codec2} = mtp_abridged:encode_packet(Bin, Codec1),
{<<Acc/binary, (iolist_to_binary(Data))/binary>>,
Codec2}
end, {<<>>, Codec}, Stream),
DecodedStream = decode_stream(BinStream, Codec1, []),
Stream == DecodedStream.
decode_stream(BinStream, Codec, Acc) ->
case mtp_abridged:try_decode_packet(BinStream, Codec) of
{incomplete, _} ->
lists:reverse(Acc);
{ok, DecPacket, Codec1} ->
decode_stream(<<>>, Codec1, [DecPacket | Acc])
end.

32
test/prop_mtp_aes_cbc.erl Normal file
View file

@ -0,0 +1,32 @@
-module(prop_mtp_aes_cbc).
-include_lib("proper/include/proper.hrl").
-export([prop_stream/1]).
prop_stream(doc) ->
"Tests that any number of packets can be encoded, concatenated and decoded"
" as a stream using the same key for encoding and decoding".
prop_stream() ->
?FORALL({Key, Iv, Stream}, arg_set(), stream_codec(Key, Iv, Stream)).
arg_set() ->
proper_types:tuple(
[mtp_prop_gen:key(),
mtp_prop_gen:iv(),
mtp_prop_gen:stream_16b()
]).
stream_codec(Key, Iv, Stream) ->
Codec = mtp_aes_cbc:new(Key, Iv, Key, Iv, 16),
{BinStream, Codec2} =
lists:foldl(
fun(Bin, {Acc, Codec1}) ->
{Data, Codec2} = mtp_aes_cbc:encrypt(Bin, Codec1),
{<<Acc/binary, (iolist_to_binary(Data))/binary>>,
Codec2}
end, {<<>>, Codec}, Stream),
{Decrypted, _Codec3} = mtp_aes_cbc:decrypt(BinStream, Codec2),
%% io:format("Dec: ~p~nOrig: ~p~nCodec: ~p~n", [Decrypted, Stream, _Codec3]),
Decrypted == iolist_to_binary(Stream).

44
test/prop_mtp_full.erl Normal file
View file

@ -0,0 +1,44 @@
-module(prop_mtp_full).
-include_lib("proper/include/proper.hrl").
-export([prop_codec/1, prop_stream/1]).
prop_codec(doc) ->
"Tests that any 4-byte aligned binary can be encoded and decoded back".
prop_codec() ->
?FORALL(Bin, mtp_prop_gen:packet_4b(), codec(Bin)).
codec(Bin) ->
Codec = mtp_full:new(),
{Data, Codec1} = mtp_full:encode_packet(Bin, Codec),
{ok, Decoded, _} = mtp_full:try_decode_packet(iolist_to_binary(Data), Codec1),
Decoded == Bin.
prop_stream(doc) ->
"Tests that any number of packets can be encoded, concatenated and decoded".
prop_stream() ->
?FORALL(Stream, mtp_prop_gen:stream_4b(), stream_codec(Stream)).
stream_codec(Stream) ->
Codec = mtp_full:new(),
{BinStream, Codec1} =
lists:foldl(
fun(Bin, {Acc, Codec1}) ->
{Data, Codec2} = mtp_full:encode_packet(Bin, Codec1),
{<<Acc/binary, (iolist_to_binary(Data))/binary>>,
Codec2}
end, {<<>>, Codec}, Stream),
DecodedStream = decode_stream(BinStream, Codec1, []),
Stream == DecodedStream.
decode_stream(BinStream, Codec, Acc) ->
case mtp_full:try_decode_packet(BinStream, Codec) of
{incomplete, _} ->
lists:reverse(Acc);
{ok, DecPacket, Codec1} ->
decode_stream(<<>>, Codec1, [DecPacket | Acc])
end.

View file

@ -0,0 +1,52 @@
-module(prop_mtp_intermediate).
-include_lib("proper/include/proper.hrl").
-export([prop_codec/1, prop_stream/1, prop_stream_padding/1]).
prop_codec(doc) ->
"Tests that any 4-byte aligned binary can be encoded and decoded back".
prop_codec() ->
?FORALL(Bin, mtp_prop_gen:packet_4b(), codec(Bin)).
codec(Bin) ->
Codec = mtp_intermediate:new(),
{Data, Codec1} = mtp_intermediate:encode_packet(Bin, Codec),
{ok, Decoded, _} = mtp_intermediate:try_decode_packet(iolist_to_binary(Data), Codec1),
Decoded == Bin.
prop_stream(doc) ->
"Tests that any number of packets can be encoded, concatenated and decoded".
prop_stream() ->
?FORALL(Stream, mtp_prop_gen:stream_4b(), stream_codec(Stream, false)).
stream_codec(Stream, Padding) ->
Codec = mtp_intermediate:new(#{padding => Padding}),
{BinStream, Codec1} =
lists:foldl(
fun(Bin, {Acc, Codec1}) ->
{Data, Codec2} = mtp_intermediate:encode_packet(Bin, Codec1),
{<<Acc/binary, (iolist_to_binary(Data))/binary>>,
Codec2}
end, {<<>>, Codec}, Stream),
DecodedStream = decode_stream(BinStream, Codec1, []),
Stream == DecodedStream.
decode_stream(BinStream, Codec, Acc) ->
case mtp_intermediate:try_decode_packet(BinStream, Codec) of
{incomplete, _} ->
lists:reverse(Acc);
{ok, DecPacket, Codec1} ->
decode_stream(<<>>, Codec1, [DecPacket | Acc])
end.
prop_stream_padding(doc) ->
"Tests that any number of packets can be encoded, concatenated and decoded"
" using encoder with random padding enabled".
prop_stream_padding() ->
?FORALL(Stream, mtp_prop_gen:stream_4b(), stream_codec(Stream, true)).

View file

@ -0,0 +1,104 @@
-module(prop_mtp_obfuscated).
-include_lib("proper/include/proper.hrl").
-export([prop_stream/1,
prop_client_server_handshake/1,
prop_client_server_stream/1]).
prop_stream(doc) ->
"Tests that any number of packets can be encrypted with mtp_obfuscatedcoded,"
" concatenated and decoded as a stream using the same key for encoding and decoding".
prop_stream() ->
?FORALL({Key, Iv, Stream}, stream_arg_set(), stream_codec(Key, Iv, Stream)).
stream_arg_set() ->
proper_types:tuple(
[mtp_prop_gen:key(),
mtp_prop_gen:iv(),
mtp_prop_gen:stream_4b()
]).
stream_codec(Key, Iv, Stream) ->
Codec = mtp_obfuscated:new(Key, Iv, Key, Iv),
{BinStream, Codec2} =
lists:foldl(
fun(Bin, {Acc, Codec1}) ->
{Data, Codec2} = mtp_obfuscated:encrypt(Bin, Codec1),
{<<Acc/binary, (iolist_to_binary(Data))/binary>>,
Codec2}
end, {<<>>, Codec}, Stream),
{Decrypted, _Codec3} = mtp_obfuscated:decrypt(BinStream, Codec2),
%% io:format("Dec: ~p~nOrig: ~p~nCodec: ~p~n", [Decrypted, Stream, _Codec3]),
Decrypted == iolist_to_binary(Stream).
prop_client_server_handshake(doc) ->
"Tests that for any secret, protocol and dc_id, it's possible to perform handshake".
prop_client_server_handshake() ->
?FORALL({Secret, DcId, Protocol}, cs_hs_arg_set(),
cs_hs_exchange(Secret, DcId, Protocol)).
cs_hs_arg_set() ->
proper_types:tuple(
[mtp_prop_gen:secret(),
mtp_prop_gen:dc_id(),
mtp_prop_gen:codec()]).
cs_hs_exchange(Secret, DcId, Protocol) ->
%% io:format("Secret: ~p; DcId: ~p, Protocol: ~p~n",
%% [Secret, DcId, Protocol]),
{Packet, _, _, _CliCodec} = mtp_obfuscated:client_create(Secret, Protocol, DcId),
case mtp_obfuscated:from_header(Packet, Secret, [Protocol]) of
{ok, DcId, Protocol, _SrvCodec} ->
true;
_ ->
false
end.
prop_client_server_stream(doc) ->
"Tests that for any secret, protocol and dc_id, it's possible to perform"
" handshake/key exchange and then do bi-directional encode/decode stream of data".
prop_client_server_stream() ->
?FORALL({Secret, DcId, Protocol, Stream}, cs_stream_arg_set(),
cs_stream_exchange(Secret, DcId, Protocol, Stream)).
cs_stream_arg_set() ->
proper_types:tuple(
[mtp_prop_gen:secret(),
mtp_prop_gen:dc_id(),
mtp_prop_gen:codec(),
mtp_prop_gen:stream_4b()]).
cs_stream_exchange(Secret, DcId, Protocol, Stream) ->
%% io:format("Secret: ~p; DcId: ~p, Protocol: ~p~n",
%% [Secret, DcId, Protocol]),
{Header, _, _, CliCodec} = mtp_obfuscated:client_create(Secret, Protocol, DcId),
{ok, DcId, Protocol, SrvCodec} = mtp_obfuscated:from_header(Header, Secret, [Protocol]),
%% Client to server
{CliCodec1,
SrvCodec1,
Cli2SrvTransmitted} = transmit_stream(CliCodec, SrvCodec, Stream),
{_CliCodec2,
_SrvCodec2,
Srv2CliTransmitted} = transmit_stream(SrvCodec1, CliCodec1, Stream),
BinStream = iolist_to_binary(Stream),
(Cli2SrvTransmitted == BinStream)
andalso (Srv2CliTransmitted == BinStream).
transmit_stream(EncCodec, DecCodec, Stream) ->
{EncStream, EncCodec3} =
lists:foldl(
fun(Packet, {Acc, CliCodec1}) ->
{Data, CliCodec2} = mtp_obfuscated:encrypt(Packet, CliCodec1),
{<<Acc/binary, (iolist_to_binary(Data))/binary>>,
CliCodec2}
end, {<<>>, EncCodec}, Stream),
{Decrypted, DecCodec2} = mtp_obfuscated:decrypt(EncStream, DecCodec),
{EncCodec3,
DecCodec2,
Decrypted}.