This commit is contained in:
Nelson Vides 2020-03-01 22:17:18 +01:00
Родитель 0a18835ea5
Коммит ea06737655
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: F64FA10AE541415D
11 изменённых файлов: 1005 добавлений и 0 удалений

18
.travis.yml Normal file
Просмотреть файл

@ -0,0 +1,18 @@
language: erlang
branches:
only:
- master
otp_release:
- 21.3
- 22.0
install:
- make
- make test-compile
- travis_retry pip install --user codecov
script:
- make test
- make dialyzer
after_success:
- make coverage-report
- make codecov
- codecov

34
Makefile Normal file
Просмотреть файл

@ -0,0 +1,34 @@
.PHONY: rel deps test
all: deps compile
compile: rebar3
./rebar3 compile
deps: rebar3
./rebar3 get-deps
clean: rebar3
./rebar3 clean
test-deps: rebar3
./rebar3 get-deps
test-compile: rebar3 test-deps
./rebar3 compile
test: test-compile
./rebar3 eunit
coverage-report: _build/test/cover/eunit.coverdata
./rebar3 as test coveralls send
codecov: _build/test/cover/eunit.coverdata
./rebar3 as test codecov analyze
rebar3:
wget https://github.com/erlang/rebar3/releases/download/3.13.1/rebar3 &&\
chmod u+x rebar3
dialyzer: rebar3
./rebar3 dialyzer

9
README.md Normal file
Просмотреть файл

@ -0,0 +1,9 @@
mongoose_jid
=====
An OTP library
Build
-----
$ rebar3 compile

149
c_src/mongoose_jid.cpp Normal file
Просмотреть файл

@ -0,0 +1,149 @@
#include "erl_nif.h"
#include <cstring>
ERL_NIF_TERM
mk_error(ErlNifEnv* env)
{
ERL_NIF_TERM ret;
enif_make_existing_atom(env, "error", &ret, ERL_NIF_LATIN1);
return ret;
}
static ERL_NIF_TERM
from_binary_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
if(argc != 1) {
return enif_make_badarg(env);
}
ErlNifBinary bin;
if (!enif_inspect_binary(env, argv[0], &bin)) {
return enif_make_badarg(env);
}
const unsigned size = bin.size;
unsigned commercial_at = -1;
unsigned slash = size;
for (unsigned i = 0; i < size ; ++i) {
switch(bin.data[i]) {
case '/':
if (slash == size) {
slash = i;
goto end_loop;
// If we found a slash first, we don't care about commercial_ats anymore
// https://tools.ietf.org/html/rfc7622#section-3.2
}
break;
case '@':
if (commercial_at == -1) {
commercial_at = i;
} else
return mk_error(env);
break;
}
}
end_loop:
if (commercial_at == 0 || slash == 0) {
return mk_error(env);
}
unsigned host_size = slash - commercial_at - 1;
if (host_size == 0) return mk_error(env);
ERL_NIF_TERM host;
unsigned char *host_data = enif_make_new_binary(env, host_size, &host);
std::memcpy(host_data, &(bin.data[commercial_at + 1]), host_size);
ERL_NIF_TERM resource;
unsigned res_size = slash >= size - 1 ? 0 : size - slash - 1;
unsigned char *res_data = enif_make_new_binary(env, res_size, &resource);
std::memcpy(res_data, &(bin.data[slash + 1]), res_size);
ERL_NIF_TERM user;
unsigned user_size = commercial_at == -1 ? 0 : commercial_at;
unsigned char *user_data = enif_make_new_binary(env, user_size, &user);
std::memcpy(user_data, &(bin.data[0]), user_size);
return enif_make_tuple3(
env,
user,
host,
resource);
}
static ERL_NIF_TERM
to_binary(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
if(argc != 1)
return enif_make_badarg(env);
if (enif_is_binary(env, argv[0]))
return argv[0];
int arity;
const ERL_NIF_TERM *tuple;
if (!enif_get_tuple(env, argv[0], &arity, &tuple))
return enif_make_badarg(env);
ErlNifBinary user = {};
ErlNifBinary server = {};
ErlNifBinary resource = {};
switch(arity) {
case 7: // It is a #jid record, so from
// {jid, User, Server, Resource, LUser, LServer, LResource}
// extract fields 1, 2, and 3 (U, S, and R, it's 0-indexed)
if (!enif_inspect_binary(env, tuple[1], &user)
|| !enif_inspect_binary(env, tuple[2], &server)
|| !enif_inspect_binary(env, tuple[3], &resource))
return enif_make_badarg(env);
break;
case 2: // {User, Server}
if (!enif_inspect_binary(env, tuple[0], &user)
|| !enif_inspect_binary(env, tuple[1], &server))
return enif_make_badarg(env);
break;
case 3: // {User, Server, Resource}
if (!enif_inspect_binary(env, tuple[0], &user)
|| !enif_inspect_binary(env, tuple[1], &server)
|| !enif_inspect_binary(env, tuple[2], &resource))
return enif_make_badarg(env);
break;
default:
return enif_make_badarg(env);
}
// Process those three binaries
unsigned index = 0;
unsigned len =
server.size + // there's always a server
(user.size == 0 ? 0 : user.size + 1) + // user plus commercial_at, if any
(resource.size == 0 ? 0 : resource.size + 1); // res plus slash, if any
ERL_NIF_TERM final_jid;
unsigned char *final_jid_data = enif_make_new_binary(env, len, &final_jid);
std::memset(final_jid_data, 0, len);
// copy the user field and the commercial_at if there's a user
if (user.size != 0) {
std::memcpy(final_jid_data, user.data, user.size);
std::memcpy(final_jid_data + user.size, "@", 1);
index += user.size + 1;
}
// always copy the server field
std::memcpy(final_jid_data + index, server.data, server.size);
index += server.size;
// copy the slash and the resource field if there's a resource
if (resource.size != 0) {
std::memcpy(final_jid_data + index, "/", 1);
std::memcpy(final_jid_data + index + 1, resource.data, resource.size);
}
return final_jid;
}
static ErlNifFunc jid_nif_funcs[] = {
{"to_binary", 1, to_binary},
{"from_binary_nif", 1, from_binary_nif}
};
static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info) {
return 0;
};
ERL_NIF_INIT(jid, jid_nif_funcs, NULL, NULL, upgrade, NULL);

8
include/jid.hrl Normal file
Просмотреть файл

@ -0,0 +1,8 @@
-record(jid, {user = <<>> :: jid:user(),
server = <<>> :: jid:server(),
resource = <<>> :: jid:resource(),
luser = <<>> :: jid:luser(),
lserver = <<>> :: jid:lserver(),
lresource = <<>> :: jid:lresource()
}).

53
rebar.config Normal file
Просмотреть файл

@ -0,0 +1,53 @@
{erl_opts, [debug_info]}.
{deps,
[
{stringprep, {git, "https://github.com/processone/stringprep.git", {tag, "1.0.19"}}}
]}.
{plugins, [pc]}.
{artifacts, ["priv/jid.so"]}.
{port_specs,
[
{
% Any arch
".*",
% Create library
"priv/jid.so",
% From files
["c_src/*.cpp"],
% Using options
[ {env, [{ "CXXFLAGS", "$CXXFLAGS -O3 -std=c++11" }]} ]
}
]}.
{provider_hooks,
[
{post,
[
{compile, {pc, compile}},
{clean, {pc, clean}}
]}
]}.
{profiles,
[
{test,
[
{deps, [{proper, "1.3.0"}]},
{plugins,
[
pc,
coveralls,
{rebar3_codecov,
{git,
"https://github.com/zofpolkowska/rebar3_codecov.git",
{ref, "3d0af19"}}}
]}
]}
]}.
{cover_enabled, true}.
{cover_export_enabled, true}.

10
rebar.lock Normal file
Просмотреть файл

@ -0,0 +1,10 @@
{"1.1.0",
[{<<"p1_utils">>,{pkg,<<"p1_utils">>,<<"1.0.18">>},1},
{<<"stringprep">>,
{git,"https://github.com/processone/stringprep.git",
{ref,"0f0df1b10aac78cbe6088b142d172de43fc387a3"}},
0}]}.
[
{pkg_hash,[
{<<"p1_utils">>, <<"3FE224DE5B2E190D730A3C5DA9D6E8540C96484CF4B4692921D1E28F0C32B01C">>}]}
].

15
src/jid.app.src Normal file
Просмотреть файл

@ -0,0 +1,15 @@
{application, jid,
[{description, "An OTP library"},
{vsn, "1.0.0"},
{registered, []},
{applications,
[kernel,
stdlib,
stringprep
]},
{env,[]},
{modules, []},
{licenses, ["Apache 2.0"]},
{links, []}
]}.

243
src/jid.erl Normal file
Просмотреть файл

@ -0,0 +1,243 @@
%%==============================================================================
%% Copyright 2015 Erlang Solutions Ltd.
%%
%% 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.
%%==============================================================================
-module(jid).
-on_load(load/0).
-export([make/3]).
-export([make/1]).
-export([make_noprep/3]).
-export([make_noprep/1]).
-export([are_equal/2]).
-export([are_bare_equal/2]).
-export([from_binary/1]).
-export([to_binary/1]).
-export([is_nodename/1]).
-export([nodeprep/1]).
-export([nameprep/1]).
-export([resourceprep/1]).
-export([to_lower/1]).
-export([to_lus/1]).
-export([to_bare/1]).
-export([replace_resource/2]).
-export([binary_to_bare/1]).
-include("jid.hrl").
-type user() :: binary().
-type server() :: binary().
-type resource() :: binary().
-type luser() :: binary().
-type lserver() :: binary().
-type lresource() :: binary().
-type jid() :: #jid{}.
-type ljid() :: {luser(), lserver(), lresource()}.
%% A tuple-style JID
-type simple_jid() :: {user(), server(), resource()}.
-type simple_bare_jid() :: {LUser :: luser(), LServer :: lserver()}.
%% A tuple-style JID without resource part
-type literal_jid() :: binary().
-export_type([jid/0,
ljid/0,
simple_bare_jid/0,
simple_jid/0,
literal_jid/0,
user/0, server/0, resource/0,
luser/0, lserver/0, lresource/0
]).
-define(SANE_LIMIT, 1024).
-define(XMPP_JID_SIZE_LIMIT, 3071).
% Maximum JID size in octets (bytes) as defined in
% https://tools.ietf.org/html/rfc7622#section-3.1
-spec make(User :: user(), Server :: server(), Res :: resource()) -> jid() | error.
make(User, Server, Res) ->
case {nodeprep(User), nameprep(Server), resourceprep(Res)} of
{error, _, _} -> error;
{_, error, _} -> error;
{_, _, error} -> error;
{LUser, LServer, LRes} ->
#jid{user = User,
server = Server,
resource = Res,
luser = LUser,
lserver = LServer,
lresource = LRes}
end.
-spec make(simple_jid()) -> jid() | error.
make({User, Server, Resource}) ->
make(User, Server, Resource).
-spec make_noprep(User :: luser(),
Server :: lserver(),
Resource :: lresource()) -> jid().
make_noprep(LUser, LServer, LResource) ->
#jid{user = LUser,
server = LServer,
resource = LResource,
luser = LUser,
lserver = LServer,
lresource = LResource}.
-spec make_noprep(simple_jid()) -> jid().
make_noprep({LUser, LServer, LResource}) ->
make_noprep(LUser, LServer, LResource).
-spec are_equal(jid(), jid()) -> boolean().
are_equal(#jid{luser = LUser, lserver = LServer, lresource = LRes},
#jid{luser = LUser, lserver = LServer, lresource = LRes}) ->
true;
are_equal(_, _) ->
false.
%% @doc Returns true if `are_equal(to_bare(A), to_bare(B))'
-spec are_bare_equal(jid() | ljid(), jid() | ljid()) -> boolean().
are_bare_equal(#jid{luser = LUser, lserver = LServer},
#jid{luser = LUser, lserver = LServer}) ->
true;
are_bare_equal(#jid{luser = LUser, lserver = LServer}, {LUser, LServer, _}) ->
true;
are_bare_equal({LUser, LServer, _}, #jid{luser = LUser, lserver = LServer}) ->
true;
are_bare_equal({LUser, LServer, _}, {LUser, LServer, _}) ->
true;
are_bare_equal(_, _) ->
false.
-spec from_binary(binary()) -> jid() | error.
from_binary(J) when is_binary(J), byte_size(J) < ?XMPP_JID_SIZE_LIMIT ->
case from_binary_nif(J) of
{U, H, R} -> make(U, H, R);
error -> error
end;
from_binary(_) ->
error.
%% Original Erlang equivalent can be found in test/jid_SUITE.erl,
%% together with `proper` generators to check for equivalence
-spec from_binary_nif(binary()) -> simple_jid() | error.
from_binary_nif(_) ->
erlang:nif_error(not_loaded).
%% Original Erlang equivalent can be found in test/jid_SUITE.erl,
%% together with `proper` generators to check for equivalence
-spec to_binary(simple_jid() | simple_bare_jid() | jid()) -> binary().
to_binary(_) ->
erlang:nif_error(not_loaded).
-spec is_nodename(<<>> | binary()) -> boolean().
is_nodename(<<>>) ->
false;
is_nodename(J) ->
nodeprep(J) /= error.
-spec validate_binary_size(binary()) -> binary() | error.
validate_binary_size(R) when size(R) < ?SANE_LIMIT ->
R;
validate_binary_size(_) ->
error.
-spec nodeprep(user()) -> lserver() | error.
nodeprep(S) when is_binary(S), size(S) < ?SANE_LIMIT ->
R = stringprep:nodeprep(S),
validate_binary_size(R);
nodeprep(_) ->
error.
-spec nameprep(server()) -> luser() | error.
nameprep(S) when is_binary(S), size(S) < ?SANE_LIMIT ->
R = stringprep:nameprep(S),
validate_binary_size(R);
nameprep(_) ->
error.
-spec resourceprep(resource()) -> lresource() | error.
resourceprep(S) when size(S) < ?SANE_LIMIT ->
R = stringprep:resourceprep(S),
validate_binary_size(R);
resourceprep(_) ->
error.
-spec to_lower(simple_jid()) -> simple_jid() | error;
(jid()) -> jid().
to_lower(#jid{luser = U, lserver = S, lresource = R}) ->
{U, S, R};
to_lower({U, S, R}) ->
case {jid:nodeprep(U), jid:nameprep(S), jid:resourceprep(R)} of
{LUser, LServer, LResource} when LUser /= error, LServer /= error, LResource /= error ->
{LUser, LServer, LResource};
_Error ->
error
end.
-spec to_lus(jid() | ljid()) -> simple_bare_jid();
(error) -> error.
to_lus(#jid{luser = U, lserver = S}) ->
{U, S};
to_lus({U, S, _}) ->
{U, S};
to_lus(error) ->
error.
-spec to_bare(simple_jid()) -> simple_jid();
(jid()) -> jid();
(error) -> error.
to_bare(#jid{} = JID) ->
JID#jid{resource = <<>>, lresource = <<>>};
to_bare({U, S, _R}) ->
{U, S, <<>>};
to_bare(error) ->
error.
-spec replace_resource(jid(), resource()) -> jid() | error.
replace_resource(#jid{} = JID, Resource) ->
case resourceprep(Resource) of
error -> error;
LResource ->
JID#jid{resource = Resource, lresource = LResource}
end.
-spec binary_to_bare(binary()) -> jid() | error.
binary_to_bare(JID) when is_binary(JID) ->
case from_binary(JID) of
error ->
error;
#jid{} = Result ->
to_bare(Result)
end.
%%%===================================================================
%%% Load NIF
%%%===================================================================
-spec load() -> any().
load() ->
PrivDir = case code:priv_dir(?MODULE) of
{error, _} ->
EbinDir = filename:dirname(code:which(?MODULE)),
AppPath = filename:dirname(EbinDir),
filename:join(AppPath, "priv");
Path ->
Path
end,
erlang:load_nif(filename:join(PrivDir, ?MODULE_STRING), none),
stringprep:start().

350
test/jid_SUITE.erl Normal file
Просмотреть файл

@ -0,0 +1,350 @@
-module(jid_SUITE).
-include_lib("proper/include/proper.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
-include("jid.hrl").
-export([all/0, groups/0]).
-export([
binary_to_jid_succeeds_with_valid_binaries/1,
binary_to_jid_fails_with_invalid_binaries/1,
binary_to_jid_fails_with_empty_binary/1,
make_jid_fails_on_binaries_that_are_too_long/1,
make_is_independent_of_the_input_format/1,
make_noprep_and_make_have_equal_raw_jid/1,
make_noprep_is_independent_of_the_input_format/1,
jid_to_lower_fails_if_any_binary_is_invalid/1,
jid_replace_resource_failes_for_invalid_resource/1,
nodeprep_fails_with_too_long_username/1,
nameprep_fails_with_too_long_domain/1,
resourceprep_fails_with_too_long_resource/1,
from_binary_fails_with_too_long_input/1,
nodeprep_fails_with_incorrect_username/1,
resourceprep_fails_with_incorrect_resource/1,
nameprep_fails_with_incorrect_domain/1,
is_nodename_fails_for_empty_binary/1,
compare_bare_jids/1,
compare_bare_jids_doesnt_depend_on_the_order/1,
compare_bare_with_jids_structs_and_bare_jids/1,
binary_to_bare_equals_binary_and_then_bare/1,
to_lower_to_bare_equals_to_bare_to_lower/1,
make_to_lus_equals_to_lower_to_lus/1
]).
-export([
with_nif_to_binary/1,
with_nif_from_binary/1
]).
all() -> [
{group, common},
{group, old_comparison}
].
groups() ->
[
{common, [parallel], [
binary_to_jid_succeeds_with_valid_binaries,
binary_to_jid_fails_with_invalid_binaries,
binary_to_jid_fails_with_empty_binary,
make_jid_fails_on_binaries_that_are_too_long,
make_is_independent_of_the_input_format,
make_noprep_and_make_have_equal_raw_jid,
make_noprep_is_independent_of_the_input_format,
jid_to_lower_fails_if_any_binary_is_invalid,
jid_replace_resource_failes_for_invalid_resource,
nodeprep_fails_with_too_long_username,
nameprep_fails_with_too_long_domain,
resourceprep_fails_with_too_long_resource,
from_binary_fails_with_too_long_input,
nodeprep_fails_with_incorrect_username,
resourceprep_fails_with_incorrect_resource,
nameprep_fails_with_incorrect_domain,
is_nodename_fails_for_empty_binary,
compare_bare_jids,
compare_bare_jids_doesnt_depend_on_the_order,
compare_bare_with_jids_structs_and_bare_jids,
binary_to_bare_equals_binary_and_then_bare,
to_lower_to_bare_equals_to_bare_to_lower,
make_to_lus_equals_to_lower_to_lus
]},
{old_comparison, [parallel], [
with_nif_from_binary,
with_nif_to_binary
]}
].
binary_to_jid_succeeds_with_valid_binaries(_C) ->
Prop = ?FORALL(BinJid, (jid_gen:jid()),
(is_record(jid:from_binary(BinJid), jid))),
run_property(Prop, 200, 1, 100).
binary_to_jid_fails_with_invalid_binaries(_C) ->
Prop = ?FORALL(BinJid, jid_gen:invalid_jid(),
error == jid:from_binary(BinJid)),
run_property(Prop, 200, 1, 100).
binary_to_jid_fails_with_empty_binary(_) ->
error = jid:from_binary(<<>>).
make_jid_fails_on_binaries_that_are_too_long(_) ->
Prop = ?FORALL({U, S, R},
{jid_gen:username(), jid_gen:domain(), jid_gen:resource()},
case element_length_is_too_big([U,S,R]) of
true -> error == jid:make(U,S,R);
false -> is_record(jid:make(U,S,R), jid)
end),
run_property(Prop, 50, 500, 1500).
make_is_independent_of_the_input_format(_) ->
Prop = ?FORALL({U, S, R},
{jid_gen:username(), jid_gen:domain(), jid_gen:resource()},
jid:make(U,S,R) == jid:make({U,S,R})),
run_property(Prop, 100, 1, 500).
make_noprep_and_make_have_equal_raw_jid(_) ->
Prop = ?FORALL({U, S, R},
{jid_gen:username(), jid_gen:domain(), jid_gen:resource()},
begin
#jid{user = U, server = S, resource = R} = jid:make(U, S, R),
#jid{user = U, server = S, resource = R} = jid:make_noprep(U, S, R),
true
end
),
run_property(Prop, 10, 1, 500).
make_noprep_is_independent_of_the_input_format(_) ->
Prop = ?FORALL({U, S, R},
{jid_gen:username(), jid_gen:domain(), jid_gen:resource()},
jid:make_noprep(U,S,R) == jid:make_noprep({U,S,R})),
run_property(Prop, 10, 1, 500).
element_length_is_too_big(Els) ->
lists:any(fun(El) -> size(El) >= 1024 end, Els).
jid_to_lower_fails_if_any_binary_is_invalid(_) ->
Prop = ?FORALL({U, S, R},
{jid_gen:maybe_valid_username(), jid_gen:maybe_valid_domain(), jid_gen:maybe_valid_resource()},
case jid:to_lower({U, S, R}) of
{LU, LS, LR} ->
jid:nodeprep(U) == LU andalso
jid:nameprep(S) == LS andalso
jid:resourceprep(R) == LR;
error ->
jid:nodeprep(U) == error orelse
jid:nameprep(S) == error orelse
jid:resourceprep(R) == error
end),
run_property(Prop, 150, 1, 42).
nodeprep_fails_with_too_long_username(_C) ->
Prop = ?FORALL(Bin, jid_gen:username(),
error == jid:nodeprep(Bin)),
run_property(Prop, 10, 1024, 2048).
nameprep_fails_with_too_long_domain(_C) ->
Prop = ?FORALL(Bin, jid_gen:domain(),
error == jid:nameprep(Bin)),
run_property(Prop, 10, 1024, 2048).
resourceprep_fails_with_too_long_resource(_C) ->
Prop = ?FORALL(Bin, jid_gen:resource(),
error == jid:resourceprep(Bin)),
run_property(Prop, 10, 1024, 2048).
from_binary_fails_with_too_long_input(_C) ->
Prop = ?FORALL(Bin, jid_gen:jid(),
error == jid:from_binary(Bin)),
run_property(Prop, 5, 3071, 5048).
jid_replace_resource_failes_for_invalid_resource(_) ->
Prop = ?FORALL({BinJid, MaybeCorrectRes},
{jid_gen:bare_jid(), jid_gen:maybe_valid_resource()},
jid_replace_resource(BinJid, MaybeCorrectRes)),
run_property(Prop, 100, 1, 42).
jid_replace_resource(BinJid, Res) ->
Jid = jid:from_binary(BinJid),
Jid2 = jid:replace_resource(Jid, Res),
check_jid_replace_resource_output(Res, Jid2).
check_jid_replace_resource_output(Resource, error) ->
jid:resourceprep(Resource) == error;
check_jid_replace_resource_output(Resource, #jid{}) ->
jid:resourceprep(Resource) =/= error.
nodeprep_fails_with_incorrect_username(_) ->
Prop = ?FORALL(Bin, jid_gen:invalid_username(),
error == jid:nodeprep(Bin)),
run_property(Prop, 100, 1, 500).
resourceprep_fails_with_incorrect_resource(_) ->
Prop = ?FORALL(Bin, jid_gen:invalid_resource(),
error == jid:resourceprep(Bin)),
run_property(Prop, 100, 1, 500).
nameprep_fails_with_incorrect_domain(_) ->
Prop = ?FORALL(Bin, jid_gen:invalid_domain(),
error == jid:nameprep(Bin)),
run_property(Prop, 100, 1, 500).
is_nodename_fails_for_empty_binary(_) ->
false = jid:is_nodename(<<>>).
compare_bare_jids_doesnt_depend_on_the_order(_) ->
Prop = ?FORALL(Val,
oneof([
{jid_gen:username(), jid_gen:domain(), jid_gen:resource()},
jid_gen:maybe_valid_jid()
]),
begin
{AA, BB} = case Val of
A when is_tuple(A) -> {A, A};
X when is_binary(X) -> {jid:from_binary(X), jid:from_binary(X)}
end,
equals(jid:are_bare_equal(BB, AA),
jid:are_bare_equal(AA, BB))
end),
run_property(Prop, 200, 1, 100).
compare_bare_with_jids_structs_and_bare_jids(_) ->
Prop = ?FORALL({U, S, R}, {jid_gen:username(), jid_gen:domain(), jid_gen:resource()},
begin
AA = {U, S, R},
BB = jid:make_noprep(U, S, R),
equals(jid:are_bare_equal(BB, AA),
jid:are_bare_equal(AA, BB))
end),
run_property(Prop, 200, 1, 100).
compare_bare_jids(_) ->
Prop = ?FORALL(Val, oneof([
{jid_gen:maybe_valid_jid(), jid_gen:maybe_valid_jid()},
jid_gen:maybe_valid_jid()
]),
begin
{A, B} = case Val of {X, Y} -> {X, Y}; X -> {X, X} end,
AA = jid:from_binary(A),
BB = jid:from_binary(B),
equals(jid:are_equal(jid:to_bare(AA), jid:to_bare(BB)),
jid:are_bare_equal(AA, BB))
end),
run_property(Prop, 200, 1, 100).
binary_to_bare_equals_binary_and_then_bare(_) ->
Prop = ?FORALL(A, jid_gen:maybe_valid_jid(),
equals(jid:to_bare(jid:from_binary(A)), jid:binary_to_bare(A))),
run_property(Prop, 200, 1, 100).
to_lower_to_bare_equals_to_bare_to_lower(_) ->
Prop = ?FORALL(JID, oneof([jid_gen:jid_struct(),
{jid_gen:maybe_valid_username(),
jid_gen:maybe_valid_domain(),
jid_gen:resource()}]),
equals(jid:to_bare(jid:to_lower(JID)),
jid:to_lower(jid:to_bare(JID)))),
run_property(Prop, 200, 1, 100).
make_to_lus_equals_to_lower_to_lus(_) ->
Prop = ?FORALL({U, S, R}, {jid_gen:maybe_valid_username(),
jid_gen:maybe_valid_domain(),
jid_gen:maybe_valid_resource()},
equals(jid:to_lus(jid:make(U, S, R)),
jid:to_lus(jid:to_lower({U, S, R})))),
run_property(Prop, 200, 1, 100).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Original code kept for documentation purposes
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
with_nif_to_binary(_C) ->
Prop = ?FORALL(L, jid_gen:jid(), from_binary(L) =:= jid:from_binary(L)),
run_property(Prop, 100, 1, 42).
with_nif_from_binary(_C) ->
Prop = ?FORALL(L, jid_gen:from_jid(), to_binary(L) =:= jid:to_binary(L)),
run_property(Prop, 100, 1, 42).
% Some property based testing to check for equivalence
% Original code
-spec from_binary(binary()) -> error | jid:jid().
from_binary(J) ->
binary_to_jid1(J, []).
-spec binary_to_jid1(binary(), [byte()]) -> 'error' | jid:jid().
binary_to_jid1(<<$@, _J/binary>>, []) ->
error;
binary_to_jid1(<<$@, J/binary>>, N) ->
binary_to_jid2(J, lists:reverse(N), []);
binary_to_jid1(<<$/, _J/binary>>, []) ->
error;
binary_to_jid1(<<$/, J/binary>>, N) ->
binary_to_jid3(J, [], lists:reverse(N), []);
binary_to_jid1(<<C, J/binary>>, N) ->
binary_to_jid1(J, [C | N]);
binary_to_jid1(<<>>, []) ->
error;
binary_to_jid1(<<>>, N) ->
jid:make(<<>>, list_to_binary(lists:reverse(N)), <<>>).
%% @doc Only one "@" is admitted per JID
-spec binary_to_jid2(binary(), [byte()], [byte()]) -> 'error' | jid:jid().
binary_to_jid2(<<$@, _J/binary>>, _N, _S) ->
error;
binary_to_jid2(<<$/, _J/binary>>, _N, []) ->
error;
binary_to_jid2(<<$/, J/binary>>, N, S) ->
binary_to_jid3(J, N, lists:reverse(S), []);
binary_to_jid2(<<C, J/binary>>, N, S) ->
binary_to_jid2(J, N, [C | S]);
binary_to_jid2(<<>>, _N, []) ->
error;
binary_to_jid2(<<>>, N, S) ->
jid:make(list_to_binary(N), list_to_binary(lists:reverse(S)), <<>>).
-spec binary_to_jid3(binary(), [byte()], [byte()], [byte()]) -> 'error' | jid:jid().
binary_to_jid3(<<C, J/binary>>, N, S, R) ->
binary_to_jid3(J, N, S, [C | R]);
binary_to_jid3(<<>>, N, S, R) ->
jid:make(list_to_binary(N), list_to_binary(S), list_to_binary(lists:reverse(R))).
-spec to_binary(jid:simple_jid() | jid:simple_bare_jid() | jid:jid()) -> binary().
to_binary(Jid) when is_binary(Jid) ->
% sometimes it is used to format error messages
Jid;
to_binary(#jid{user = User, server = Server, resource = Resource}) ->
to_binary({User, Server, Resource});
to_binary({User, Server}) ->
to_binary({User, Server, <<>>});
to_binary({Node, Server, Resource}) ->
S1 = case Node of
<<>> ->
<<>>;
_ ->
<<Node/binary, "@">>
end,
S2 = <<S1/binary, Server/binary>>,
S3 = case Resource of
<<>> ->
S2;
_ ->
<<S2/binary, "/", Resource/binary>>
end,
S3.
%% HELPERS
run_property(Prop, NumTest, StartSize, StopSize) ->
?assert(proper:quickcheck(Prop, [verbose, long_result,
{numtests, NumTest},
{start_size, StartSize},
{max_size, StopSize}])).

116
test/jid_gen.erl Normal file
Просмотреть файл

@ -0,0 +1,116 @@
-module(jid_gen).
-export([jid_struct/0]).
-export([from_jid/0]).
-export([jid/0]).
-export([bare_jid/0]).
-export([full_jid/0]).
-export([username/0]).
-export([domain/0]).
-export([resource/0]).
-export([maybe_valid_jid/0]).
-export([invalid_jid/0]).
-export([invalid_bare_jid/0]).
-export([invalid_full_jid/0]).
-export([maybe_valid_username/0]).
-export([invalid_username/0]).
-export([maybe_valid_domain/0]).
-export([invalid_domain/0]).
-export([maybe_valid_resource/0]).
-export([invalid_resource/0]).
-include_lib("proper/include/proper.hrl").
jid_struct() ->
?LET({U, S, R}, {username(), domain(), resource()},
{jid, U, S, R, U, S, R}).
from_jid() ->
oneof([
{jid_gen:username(), jid_gen:domain(), jid_gen:resource()},
{<<>>, jid_gen:domain(), jid_gen:resource()},
{jid_gen:username(), jid_gen:domain()},
{jid, jid_gen:username(), jid_gen:domain(), jid_gen:resource(),
jid_gen:username(), jid_gen:domain(), jid_gen:resource()}
]).
jid() ->
oneof([full_jid(), bare_jid(), domain()]).
bare_jid() ->
?LET({Username, Domain}, {username(), domain()},
<<Username/binary, $@, Domain/binary>>).
full_jid() ->
?LET({BareJid, Resource}, {bare_jid(), resource()},
<<BareJid/binary, $/, Resource/binary>>).
username() ->
?SIZED(S, always_correct_xmpp_binary(S)).
domain() ->
?SIZED(S, always_correct_xmpp_binary(round(S*1.5))).
resource() ->
?SIZED(S, always_correct_xmpp_binary(round(S*1.7))).
maybe_valid_jid() ->
oneof([jid(), invalid_jid()]).
invalid_jid() ->
oneof([invalid_full_jid(), invalid_bare_jid()]).
invalid_bare_jid() ->
%%Oh yes, jids like domain/resource are allowed in both ejabberd and MongooseIM
?LET({U, S}, {?SUCHTHAT(E, invalid_username(), size(E) == 1 orelse binary:matches(E, <<"/">>) == []),
maybe_valid_domain()},
<<U/binary, $@, S/binary>>).
invalid_full_jid() ->
?LET({BareJid, R}, {invalid_bare_jid(), resource()},
<<BareJid/binary, $/, R/binary>>).
maybe_valid_username() ->
oneof([username(), <<>>, invalid_username()]).
invalid_username() ->
invalid_xmpp_binary(prohibited_output_node()).
maybe_valid_resource() ->
oneof([resource(), <<>>, invalid_resource()]).
invalid_resource() ->
invalid_xmpp_binary([<<238,190,187>>]). %<<"\x{EFBB}"/utf8>>
maybe_valid_domain() ->
oneof([domain(), <<>>, invalid_domain()]).
invalid_domain() ->
invalid_resource().
always_correct_xmpp_binary(S) ->
?LET(Str, always_correct_xmpp_string(S), list_to_binary(Str)).
allowed_output() ->
oneof([choose($a, $z),
choose($A, $Z),
oneof([$., $-, $_]),
choose($0, $9)]).
always_correct_xmpp_string(S) ->
[allowed_output() || _ <- lists:seq(1, S)].
invalid_xmpp_binary(ProhibitedOutput) ->
?LET({NotAllowed, Str},
{oneof(ProhibitedOutput),
frequency([{1, []}, {5, maybe_invalid_xmpp_string(ProhibitedOutput)}])},
erlang:iolist_to_binary([NotAllowed | Str])).
maybe_invalid_xmpp_string(ProhibitedOutput) ->
list(
oneof([allowed_output(),
oneof(ProhibitedOutput)])).
prohibited_output_node() ->
[$", $&, $', $/, $:, $<, $>, $@, " "].