Explorar o código

Improve emqx_mqtt_props module and add test cases

Feng Lee %!s(int64=6) %!d(string=hai) anos
pai
achega
444972968f
Modificáronse 2 ficheiros con 110 adicións e 47 borrados
  1. 64 39
      src/emqx_mqtt_props.erl
  2. 46 8
      test/emqx_mqtt_props_SUITE.erl

+ 64 - 39
src/emqx_mqtt_props.erl

@@ -25,6 +25,12 @@
         , validate/1
         ]).
 
+%% For tests
+-export([all/0]).
+
+-type(prop_name() :: atom()).
+-type(prop_id() :: pos_integer()).
+
 -define(PROPS_TABLE,
         #{16#01 => {'Payload-Format-Indicator', 'Byte', [?PUBLISH]},
           16#02 => {'Message-Expiry-Interval', 'Four-Byte-Integer', [?PUBLISH]},
@@ -52,36 +58,10 @@
           16#27 => {'Maximum-Packet-Size', 'Four-Byte-Integer', [?CONNECT, ?CONNACK]},
           16#28 => {'Wildcard-Subscription-Available', 'Byte', [?CONNACK]},
           16#29 => {'Subscription-Identifier-Available', 'Byte', [?CONNACK]},
-          16#2A => {'Shared-Subscription-Available', 'Byte', [?CONNACK]}}).
-
-name(16#01) -> 'Payload-Format-Indicator';
-name(16#02) -> 'Message-Expiry-Interval';
-name(16#03) -> 'Content-Type';
-name(16#08) -> 'Response-Topic';
-name(16#09) -> 'Correlation-Data';
-name(16#0B) -> 'Subscription-Identifier';
-name(16#11) -> 'Session-Expiry-Interval';
-name(16#12) -> 'Assigned-Client-Identifier';
-name(16#13) -> 'Server-Keep-Alive';
-name(16#15) -> 'Authentication-Method';
-name(16#16) -> 'Authentication-Data';
-name(16#17) -> 'Request-Problem-Information';
-name(16#18) -> 'Will-Delay-Interval';
-name(16#19) -> 'Request-Response-Information';
-name(16#1A) -> 'Response-Information';
-name(16#1C) -> 'Server-Reference';
-name(16#1F) -> 'Reason-String';
-name(16#21) -> 'Receive-Maximum';
-name(16#22) -> 'Topic-Alias-Maximum';
-name(16#23) -> 'Topic-Alias';
-name(16#24) -> 'Maximum-QoS';
-name(16#25) -> 'Retain-Available';
-name(16#26) -> 'User-Property';
-name(16#27) -> 'Maximum-Packet-Size';
-name(16#28) -> 'Wildcard-Subscription-Available';
-name(16#29) -> 'Subscription-Identifier-Available';
-name(16#2A) -> 'Shared-Subscription-Available'.
+          16#2A => {'Shared-Subscription-Available', 'Byte', [?CONNACK]}
+         }).
 
+-spec(id(prop_name()) -> prop_id()).
 id('Payload-Format-Indicator')          -> 16#01;
 id('Message-Expiry-Interval')           -> 16#02;
 id('Content-Type')                      -> 16#03;
@@ -108,12 +88,47 @@ id('User-Property')                     -> 16#26;
 id('Maximum-Packet-Size')               -> 16#27;
 id('Wildcard-Subscription-Available')   -> 16#28;
 id('Subscription-Identifier-Available') -> 16#29;
-id('Shared-Subscription-Available')     -> 16#2A.
+id('Shared-Subscription-Available')     -> 16#2A;
+id(Name)                                -> error({bad_property, Name}).
 
+-spec(name(prop_id()) -> prop_name()).
+name(16#01) -> 'Payload-Format-Indicator';
+name(16#02) -> 'Message-Expiry-Interval';
+name(16#03) -> 'Content-Type';
+name(16#08) -> 'Response-Topic';
+name(16#09) -> 'Correlation-Data';
+name(16#0B) -> 'Subscription-Identifier';
+name(16#11) -> 'Session-Expiry-Interval';
+name(16#12) -> 'Assigned-Client-Identifier';
+name(16#13) -> 'Server-Keep-Alive';
+name(16#15) -> 'Authentication-Method';
+name(16#16) -> 'Authentication-Data';
+name(16#17) -> 'Request-Problem-Information';
+name(16#18) -> 'Will-Delay-Interval';
+name(16#19) -> 'Request-Response-Information';
+name(16#1A) -> 'Response-Information';
+name(16#1C) -> 'Server-Reference';
+name(16#1F) -> 'Reason-String';
+name(16#21) -> 'Receive-Maximum';
+name(16#22) -> 'Topic-Alias-Maximum';
+name(16#23) -> 'Topic-Alias';
+name(16#24) -> 'Maximum-QoS';
+name(16#25) -> 'Retain-Available';
+name(16#26) -> 'User-Property';
+name(16#27) -> 'Maximum-Packet-Size';
+name(16#28) -> 'Wildcard-Subscription-Available';
+name(16#29) -> 'Subscription-Identifier-Available';
+name(16#2A) -> 'Shared-Subscription-Available';
+name(Id)    -> error({unsupported_property, Id}).
+
+-spec(filter(emqx_types:packet_type(), emqx_types:properties()|list())
+      -> emqx_types:properties()).
 filter(PacketType, Props) when is_map(Props) ->
     maps:from_list(filter(PacketType, maps:to_list(Props)));
 
-filter(PacketType, Props) when ?CONNECT =< PacketType, PacketType =< ?AUTH, is_list(Props) ->
+filter(PacketType, Props) when ?CONNECT =< PacketType,
+                               PacketType =< ?AUTH,
+                               is_list(Props) ->
     Filter = fun(Name) ->
                  case maps:find(id(Name), ?PROPS_TABLE) of
                      {ok, {Name, _Type, 'ALL'}} ->
@@ -125,6 +140,7 @@ filter(PacketType, Props) when ?CONNECT =< PacketType, PacketType =< ?AUTH, is_l
              end,
     [Prop || Prop = {Name, _} <- Props, Filter(Name)].
 
+-spec(validate(emqx_types:properties()) -> ok).
 validate(Props) when is_map(Props) ->
     lists:foreach(fun validate_prop/1, maps:to_list(Props)).
 
@@ -132,23 +148,32 @@ validate_prop(Prop = {Name, Val}) ->
     case maps:find(id(Name), ?PROPS_TABLE) of
         {ok, {Name, Type, _}} ->
             validate_value(Type, Val)
-              orelse error(bad_property, Prop);
+                orelse error({bad_property_value, Prop});
         error ->
-            error({bad_property, Prop})
+            error({bad_property, Name})
     end.
 
 validate_value('Byte', Val) ->
-    is_integer(Val);
+    is_integer(Val) andalso Val =< 16#FF;
 validate_value('Two-Byte-Integer', Val) ->
-    is_integer(Val);
+    is_integer(Val) andalso 0 =< Val andalso Val =< 16#FFFF;
 validate_value('Four-Byte-Integer', Val) ->
-    is_integer(Val);
+    is_integer(Val) andalso 0 =< Val andalso Val =< 16#FFFFFFFF;
 validate_value('Variable-Byte-Integer', Val) ->
-    is_integer(Val);
+    is_integer(Val) andalso 0 =< Val andalso Val =< 16#7FFFFFFF;
+validate_value('UTF8-String-Pair', {Name, Val}) ->
+    validate_value('UTF8-Encoded-String', Name)
+        andalso validate_value('UTF8-Encoded-String', Val);
+validate_value('UTF8-String-Pair', Pairs) when is_list(Pairs) ->
+    lists:foldl(fun(Pair, OK) ->
+                        OK andalso validate_value('UTF8-String-Pair', Pair)
+                end, true, Pairs);
 validate_value('UTF8-Encoded-String', Val)  ->
     is_binary(Val);
 validate_value('Binary-Data', Val) ->
     is_binary(Val);
-validate_value('UTF8-String-Pair', Val) ->
-    is_tuple(Val) orelse is_list(Val).
+validate_value(_Type, _Val) -> false.
+
+-spec(all() -> map()).
+all() -> ?PROPS_TABLE.
 

+ 46 - 8
test/emqx_mqtt_props_SUITE.erl

@@ -20,23 +20,61 @@
 -compile(nowarn_export_all).
 
 -include("emqx_mqtt.hrl").
+-include_lib("eunit/include/eunit.hrl").
+-include_lib("emqx_ct_helpers/include/emqx_ct.hrl").
 
 all() -> emqx_ct:all(?MODULE).
 
 t_id(_) ->
-    'TODO'.
+    foreach_prop(
+      fun({Id, Prop}) ->
+              ?assertEqual(Id, emqx_mqtt_props:id(element(1, Prop)))
+      end),
+    ?catch_error({bad_property, 'Bad-Property'}, emqx_mqtt_props:id('Bad-Property')).
 
 t_name(_) ->
-    'TODO'.
+    foreach_prop(
+      fun({Id, Prop}) ->
+              ?assertEqual(emqx_mqtt_props:name(Id), element(1, Prop))
+      end),
+    ?catch_error({unsupported_property, 16#FF}, emqx_mqtt_props:name(16#FF)).
 
 t_filter(_) ->
-    'TODO'.
+    ConnProps = #{'Session-Expiry-Interval' => 1,
+                  'Maximum-Packet-Size' => 255
+                 },
+    ?assertEqual(ConnProps,
+                 emqx_mqtt_props:filter(?CONNECT, ConnProps)),
+    PubProps = #{'Payload-Format-Indicator' => 6,
+                 'Message-Expiry-Interval' => 300,
+                 'Session-Expiry-Interval' => 300
+                },
+    ?assertEqual(#{'Payload-Format-Indicator' => 6,
+                   'Message-Expiry-Interval' => 300
+                  },
+                 emqx_mqtt_props:filter(?PUBLISH, PubProps)).
 
 t_validate(_) ->
-    'TODO'.
+    ConnProps = #{'Session-Expiry-Interval' => 1,
+                  'Maximum-Packet-Size' => 255
+                 },
+    ok = emqx_mqtt_props:validate(ConnProps),
+    BadProps = #{'Unknown-Property' => 10},
+    ?catch_error({bad_property,'Unknown-Property'},
+                 emqx_mqtt_props:validate(BadProps)).
 
-deprecated_mqtt_properties_all(_) ->
-    Props = emqx_mqtt_props:filter(?CONNECT, #{'Session-Expiry-Interval' => 1, 'Maximum-Packet-Size' => 255}),
-    ok = emqx_mqtt_props:validate(Props),
-    #{} = emqx_mqtt_props:filter(?CONNECT, #{'Maximum-QoS' => ?QOS_2}).
+t_validate_value(_) ->
+    ok = emqx_mqtt_props:validate(#{'Correlation-Data' => <<"correlation-id">>}),
+    ok = emqx_mqtt_props:validate(#{'Reason-String' => <<"Unknown Reason">>}),
+    ok = emqx_mqtt_props:validate(#{'User-Property' => {<<"Prop">>, <<"Val">>}}),
+    ok = emqx_mqtt_props:validate(#{'User-Property' => [{<<"Prop">>, <<"Val">>}]}),
+    ?catch_error({bad_property_value, {'Payload-Format-Indicator', 16#FFFF}},
+                 emqx_mqtt_props:validate(#{'Payload-Format-Indicator' => 16#FFFF})),
+    ?catch_error({bad_property_value, {'Server-Keep-Alive', 16#FFFFFF}},
+                 emqx_mqtt_props:validate(#{'Server-Keep-Alive' => 16#FFFFFF})),
+    ?catch_error({bad_property_value, {'Will-Delay-Interval', -16#FF}},
+                 emqx_mqtt_props:validate(#{'Will-Delay-Interval' => -16#FF})).
+
+foreach_prop(Fun) ->
+    lists:foreach(Fun, maps:to_list(emqx_mqtt_props:all())).