|
|
@@ -26,8 +26,6 @@
|
|
|
|
|
|
-import(lists, [reverse/1]).
|
|
|
|
|
|
--import(string, [rchr/2, substr/2, substr/3]).
|
|
|
-
|
|
|
%% ------------------------------------------------------------------------
|
|
|
%% Topic semantics and usage
|
|
|
%% ------------------------------------------------------------------------
|
|
|
@@ -50,107 +48,146 @@
|
|
|
|
|
|
-include("emqtt_topic.hrl").
|
|
|
|
|
|
--export([new/1,
|
|
|
- type/1,
|
|
|
- match/2,
|
|
|
- validate/1,
|
|
|
- triples/1,
|
|
|
- words/1]).
|
|
|
+-export([new/1, type/1, match/2, validate/1, triples/1, words/1]).
|
|
|
+
|
|
|
+%%----------------------------------------------------------------------------
|
|
|
+
|
|
|
+-ifdef(use_specs).
|
|
|
+
|
|
|
+-spec new( binary() ) -> topic().
|
|
|
+
|
|
|
+-spec type(topic() | binary()) -> direct | wildcard.
|
|
|
+
|
|
|
+-spec match(binary(), binary()) -> boolean().
|
|
|
|
|
|
--define(MAX_LEN, 1024).
|
|
|
+-spec validate({name | filter, binary()}) -> boolean().
|
|
|
|
|
|
--spec new(Name :: binary()) -> topic().
|
|
|
+-endif.
|
|
|
+
|
|
|
+%%----------------------------------------------------------------------------
|
|
|
+
|
|
|
+-define(MAX_TOPIC_LEN, 65535).
|
|
|
+
|
|
|
+%% ------------------------------------------------------------------------
|
|
|
+%% New Topic
|
|
|
+%% ------------------------------------------------------------------------
|
|
|
new(Name) when is_binary(Name) ->
|
|
|
- #topic{name=Name, node=node()}.
|
|
|
+ #topic{ name = Name, node = node() }.
|
|
|
|
|
|
%% ------------------------------------------------------------------------
|
|
|
-%% topic type: direct or wildcard
|
|
|
+%% Topic Type: direct or wildcard
|
|
|
%% ------------------------------------------------------------------------
|
|
|
--spec type(Topic :: topic()) -> direct | wildcard.
|
|
|
-type(#topic{name=Name}) when is_binary(Name) ->
|
|
|
- type(words(Name));
|
|
|
-type([]) ->
|
|
|
- direct;
|
|
|
-type([<<>>|T]) ->
|
|
|
- type(T);
|
|
|
-type([<<$#, _/binary>>|_]) ->
|
|
|
- wildcard;
|
|
|
-type([<<$+, _/binary>>|_]) ->
|
|
|
- wildcard;
|
|
|
-type([_|T]) ->
|
|
|
- type(T).
|
|
|
+type(#topic{ name = Name }) when is_binary(Name) ->
|
|
|
+ type(Name);
|
|
|
+type(Topic) when is_binary(Topic) ->
|
|
|
+ type2(words(Topic)).
|
|
|
+
|
|
|
+type2([]) ->
|
|
|
+ direct;
|
|
|
+type2(['#'|_]) ->
|
|
|
+ wildcard;
|
|
|
+type2(['+'|_]) ->
|
|
|
+ wildcard;
|
|
|
+type2([_H |T]) ->
|
|
|
+ type2(T).
|
|
|
|
|
|
%% ------------------------------------------------------------------------
|
|
|
-%% topic match
|
|
|
+%% Match Topic. B1 is Topic Name, B2 is Topic Filter.
|
|
|
%% ------------------------------------------------------------------------
|
|
|
--spec match(B1 :: binary(), B2 :: binary()) -> boolean().
|
|
|
-match(B1, B2) when is_binary(B1) and is_binary(B2) ->
|
|
|
- match(words(B1), words(B2));
|
|
|
+match(Name, Filter) when is_binary(Name) and is_binary(Filter) ->
|
|
|
+ match(words(Name), words(Filter));
|
|
|
match([], []) ->
|
|
|
true;
|
|
|
match([H|T1], [H|T2]) ->
|
|
|
match(T1, T2);
|
|
|
-match([_H|T1], [<<"+">>|T2]) ->
|
|
|
+match([<<$$, _/binary>>|_], ['+'|_]) ->
|
|
|
+ false;
|
|
|
+match([_H|T1], ['+'|T2]) ->
|
|
|
match(T1, T2);
|
|
|
-match(_, [<<"#">>]) ->
|
|
|
+match([<<$$, _/binary>>|_], ['#']) ->
|
|
|
+ false;
|
|
|
+match(_, ['#']) ->
|
|
|
true;
|
|
|
match([_H1|_], [_H2|_]) ->
|
|
|
false;
|
|
|
+match([_H1|_], []) ->
|
|
|
+ false;
|
|
|
match([], [_H|_T2]) ->
|
|
|
false.
|
|
|
|
|
|
%% ------------------------------------------------------------------------
|
|
|
-%% topic validate
|
|
|
+%% Validate Topic
|
|
|
%% ------------------------------------------------------------------------
|
|
|
--spec validate({Type :: subscribe | publish, Topic :: binary()}) -> boolean().
|
|
|
validate({_, <<>>}) ->
|
|
|
false;
|
|
|
-validate({_, Topic}) when is_binary(Topic) and (size(Topic) > ?MAX_LEN) ->
|
|
|
+validate({_, Topic}) when is_binary(Topic) and (size(Topic) > ?MAX_TOPIC_LEN) ->
|
|
|
false;
|
|
|
-validate({subscribe, Topic}) when is_binary(Topic) ->
|
|
|
- valid(words(Topic));
|
|
|
-validate({publish, Topic}) when is_binary(Topic) ->
|
|
|
+validate({filter, Topic}) when is_binary(Topic) ->
|
|
|
+ validate2(words(Topic));
|
|
|
+validate({name, Topic}) when is_binary(Topic) ->
|
|
|
Words = words(Topic),
|
|
|
- valid(Words) and (not include_wildcard(Topic)).
|
|
|
-
|
|
|
-triples(B) when is_binary(B) ->
|
|
|
- triples(binary_to_list(B), []).
|
|
|
-
|
|
|
-triples(S, Acc) ->
|
|
|
- triples(rchr(S, $/), S, Acc).
|
|
|
+ validate2(Words) and (not include_wildcard(Words)).
|
|
|
+
|
|
|
+validate2([]) ->
|
|
|
+ true;
|
|
|
+validate2(['#']) -> % end with '#'
|
|
|
+ true;
|
|
|
+validate2(['#'|Words]) when length(Words) > 0 ->
|
|
|
+ false;
|
|
|
+validate2([''|Words]) ->
|
|
|
+ validate2(Words);
|
|
|
+validate2(['+'|Words]) ->
|
|
|
+ validate2(Words);
|
|
|
+validate2([W|Words]) ->
|
|
|
+ case validate3(W) of
|
|
|
+ true -> validate2(Words);
|
|
|
+ false -> false
|
|
|
+ end.
|
|
|
+
|
|
|
+validate3(<<>>) ->
|
|
|
+ true;
|
|
|
+validate3(<<C/utf8, _Rest/binary>>) when C == $#; C == $+; C == 0 ->
|
|
|
+ false;
|
|
|
+validate3(<<_/utf8, Rest/binary>>) ->
|
|
|
+ validate3(Rest).
|
|
|
+
|
|
|
+include_wildcard([]) -> false;
|
|
|
+include_wildcard(['#'|_T]) -> true;
|
|
|
+include_wildcard(['+'|_T]) -> true;
|
|
|
+include_wildcard([ _ | T]) -> include_wildcard(T).
|
|
|
|
|
|
-triples(0, S, Acc) ->
|
|
|
- [{root, l2b(S), l2b(S)}|Acc];
|
|
|
-
|
|
|
-triples(I, S, Acc) ->
|
|
|
- S1 = substr(S, 1, I-1),
|
|
|
- S2 = substr(S, I+1),
|
|
|
- triples(S1, [{l2b(S1), l2b(S2), l2b(S)}|Acc]).
|
|
|
-
|
|
|
-words(Topic) when is_binary(Topic) ->
|
|
|
- words(binary_to_list(Topic), [], []).
|
|
|
+%% ------------------------------------------------------------------------
|
|
|
+%% Topic to Triples
|
|
|
+%% ------------------------------------------------------------------------
|
|
|
+triples(Topic) when is_binary(Topic) ->
|
|
|
+ triples(words(Topic), root, []).
|
|
|
|
|
|
-words([], Word, ResAcc) ->
|
|
|
- reverse([l2b(reverse(W)) || W <- [Word|ResAcc]]);
|
|
|
+triples([], _Parent, Acc) ->
|
|
|
+ reverse(Acc);
|
|
|
|
|
|
-words([$/|Topic], Word, ResAcc) ->
|
|
|
- words(Topic, [], [Word|ResAcc]);
|
|
|
+triples([W|Words], Parent, Acc) ->
|
|
|
+ Node = join(Parent, W),
|
|
|
+ triples(Words, Node, [{Parent, W, Node}|Acc]).
|
|
|
|
|
|
-words([C|Topic], Word, ResAcc) ->
|
|
|
- words(Topic, [C|Word], ResAcc).
|
|
|
+join(root, W) ->
|
|
|
+ W;
|
|
|
+join(Parent, W) ->
|
|
|
+ <<(bin(Parent))/binary, $/, (bin(W))/binary>>.
|
|
|
|
|
|
-valid([<<>>|Words]) -> valid2(Words);
|
|
|
-valid(Words) -> valid2(Words).
|
|
|
+bin('') -> <<>>;
|
|
|
+bin('+') -> <<"+">>;
|
|
|
+bin('#') -> <<"#">>;
|
|
|
+bin( B ) when is_binary(B) -> B.
|
|
|
|
|
|
-valid2([<<>>|_Words]) -> false;
|
|
|
-valid2([<<"#">>|Words]) when length(Words) > 0 -> false;
|
|
|
-valid2([_|Words]) -> valid2(Words);
|
|
|
-valid2([]) -> true.
|
|
|
+%% ------------------------------------------------------------------------
|
|
|
+%% Split Topic to Words
|
|
|
+%% ------------------------------------------------------------------------
|
|
|
+words(Topic) when is_binary(Topic) ->
|
|
|
+ [word(W) || W <- binary:split(Topic, <<"/">>, [global])].
|
|
|
|
|
|
-include_wildcard(<<>>) -> false;
|
|
|
-include_wildcard(<<$#, _T/binary>>) -> true;
|
|
|
-include_wildcard(<<$+, _T/binary>>) -> true;
|
|
|
-include_wildcard(<<_H, T/binary>>) -> include_wildcard(T).
|
|
|
+word(<<>>) -> '';
|
|
|
+word(<<"+">>) -> '+';
|
|
|
+word(<<"#">>) -> '#';
|
|
|
+word(Bin) -> Bin.
|
|
|
|
|
|
-l2b(L) -> list_to_binary(L).
|
|
|
|