Merge pull request #12285 from HJianBo/shorten-coap-params

feat(coap): support short param names
This commit is contained in:
JianBo He 2024-01-11 09:09:09 +08:00 committed by GitHub
commit b16920e796
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 212 additions and 47 deletions

View File

@ -14,6 +14,8 @@
%% limitations under the License.
%%--------------------------------------------------------------------
-ifndef(EMQX_COAP_HRL).
-define(APP, emqx_coap).
-define(DEFAULT_COAP_PORT, 5683).
-define(DEFAULT_COAPS_PORT, 5684).
@ -79,3 +81,14 @@
}).
-type coap_message() :: #coap_message{}.
-define(QUERY_PARAMS_MAPPING, [
{<<"c">>, <<"clientid">>},
{<<"t">>, <<"token">>},
{<<"u">>, <<"username">>},
{<<"p">>, <<"password">>},
{<<"q">>, <<"qos">>},
{<<"r">>, <<"retain">>}
]).
-endif.

View File

@ -386,7 +386,7 @@ check_auth_state(Msg, #channel{connection_required = true} = Channel) ->
true ->
call_session(handle_request, Msg, Channel);
false ->
URIQuery = emqx_coap_message:get_option(uri_query, Msg, #{}),
URIQuery = emqx_coap_message:extract_uri_query(Msg),
case maps:get(<<"token">>, URIQuery, undefined) of
undefined ->
?SLOG(debug, #{msg => "token_required_in_conn_mode", message => Msg});
@ -430,7 +430,7 @@ check_token(
) ->
IsDeleteConn = is_delete_connection_request(Msg),
#{clientid := ClientId} = ClientInfo,
case emqx_coap_message:get_option(uri_query, Msg) of
case emqx_coap_message:extract_uri_query(Msg) of
#{
<<"clientid">> := ClientId,
<<"token">> := Token
@ -742,7 +742,7 @@ process_connection(
Channel = #channel{conn_state = idle},
Iter
) ->
Queries = emqx_coap_message:get_option(uri_query, Req),
Queries = emqx_coap_message:extract_uri_query(Req),
case
emqx_utils:pipeline(
[
@ -775,7 +775,7 @@ process_connection(
) when
ConnState == connected
->
Queries = emqx_coap_message:get_option(uri_query, Req),
Queries = emqx_coap_message:extract_uri_query(Req),
ErrMsg0 =
case Queries of
#{<<"clientid">> := ClientId} ->
@ -793,7 +793,7 @@ process_connection(
Channel
);
process_connection({close, Msg}, _, Channel, _) ->
Queries = emqx_coap_message:get_option(uri_query, Msg),
Queries = emqx_coap_message:extract_uri_query(Msg),
case maps:get(<<"clientid">>, Queries, undefined) of
undefined ->
ok;

View File

@ -6,7 +6,7 @@
%% Copyright (c) 2015 Petr Gotthard <petr.gotthard@centrum.cz>
%%--------------------------------------------------------------------
%% Copyright (c) 2017-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
%% Copyright (c) 2017-2024 EMQ Technologies Co., Ltd. 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.
@ -43,6 +43,11 @@
set_payload_block/3, set_payload_block/4
]).
-export([
extract_uri_query/1,
query_params_mapping_table/0
]).
-include("emqx_coap.hrl").
request(Type, Method) ->
@ -111,6 +116,12 @@ get_option(Option, Msg) ->
get_option(Option, #coap_message{options = Options}, Def) ->
maps:get(Option, Options, Def).
extract_uri_query(Msg = #coap_message{}) ->
expand_short_param_name(get_option(uri_query, Msg, #{})).
query_params_mapping_table() ->
?QUERY_PARAMS_MAPPING.
set_payload(Payload, Msg) when is_binary(Payload) ->
Msg#coap_message{payload = Payload};
set_payload(Payload, Msg) when is_list(Payload) ->
@ -149,3 +160,17 @@ to_options(Opts) when is_map(Opts) ->
Opts;
to_options(Opts) ->
maps:from_list(Opts).
expand_short_param_name(Queries) when is_map(Queries) ->
lists:foldl(
fun({Short, Long}, Acc) ->
case maps:take(Short, Acc) of
error ->
Acc;
{Value, Acc1} ->
maps:put(Long, Value, Acc1)
end
end,
Queries,
?QUERY_PARAMS_MAPPING
).

View File

@ -76,7 +76,7 @@ check_topic(Path) ->
get_sub_opts(Msg) ->
SubOpts = maps:fold(
fun parse_sub_opts/3, #{}, emqx_coap_message:get_option(uri_query, Msg, #{})
fun parse_sub_opts/3, #{}, emqx_coap_message:extract_uri_query(Msg)
),
case SubOpts of
#{qos := _} ->
@ -110,28 +110,24 @@ type_to_qos(coap, #coap_message{type = Type}) ->
end.
get_publish_opts(Msg) ->
case emqx_coap_message:get_option(uri_query, Msg) of
undefined ->
#{};
Qs ->
maps:fold(
fun
(<<"retain">>, V, Acc) ->
Val = V =:= <<"true">>,
Acc#{retain => Val};
(<<"expiry">>, V, Acc) ->
Val = erlang:binary_to_integer(V),
Acc#{expiry_interval => Val};
(<<"qos">>, V, Acc) ->
Val = erlang:binary_to_integer(V),
Acc#{qos => Val};
(_, _, Acc) ->
Acc
end,
#{},
Qs
)
end.
Qs = emqx_coap_message:extract_uri_query(Msg),
maps:fold(
fun
(<<"retain">>, V, Acc) ->
Val = V =:= <<"true">>,
Acc#{retain => Val};
(<<"expiry">>, V, Acc) ->
Val = erlang:binary_to_integer(V),
Acc#{expiry_interval => Val};
(<<"qos">>, V, Acc) ->
Val = erlang:binary_to_integer(V),
Acc#{qos => Val};
(_, _, Acc) ->
Acc
end,
#{},
Qs
).
get_publish_qos(Msg, PublishOpts) ->
case PublishOpts of

View File

@ -1,7 +1,7 @@
%% -*- mode: erlang -*-
{application, emqx_gateway_coap, [
{description, "CoAP Gateway"},
{vsn, "0.1.6"},
{vsn, "0.1.7"},
{registered, []},
{applications, [kernel, stdlib, emqx, emqx_gateway]},
{env, []},

View File

@ -151,6 +151,30 @@ t_connection(_) ->
end,
do(Action).
t_connection_with_short_param_name(_) ->
Action = fun(Channel) ->
%% connection
Token = connection(Channel, true),
timer:sleep(100),
?assertNotEqual(
[],
emqx_gateway_cm_registry:lookup_channels(coap, <<"client1">>)
),
%% heartbeat
{ok, changed, _} = send_heartbeat(Token, true),
disconnection(Channel, Token, true),
timer:sleep(100),
?assertEqual(
[],
emqx_gateway_cm_registry:lookup_channels(coap, <<"client1">>)
)
end,
do(Action).
t_heartbeat(Config) ->
Heartbeat = ?config(new_heartbeat, Config),
Action = fun(Channel) ->
@ -583,36 +607,67 @@ t_connectionless_pubsub(_) ->
%% helpers
send_heartbeat(Token) ->
HeartURI =
?MQTT_PREFIX ++
"/connection?clientid=client1&token=" ++
Token,
send_heartbeat(Token, false).
?LOGT("send heartbeat request:~ts~n", [HeartURI]),
er_coap_client:request(put, HeartURI).
send_heartbeat(Token, ShortenParamName) ->
Prefix = ?MQTT_PREFIX ++ "/connection",
Queries = #{
"clientid" => <<"client1">>,
"token" => Token
},
URI = compose_uri(Prefix, Queries, ShortenParamName),
?LOGT("send heartbeat request:~ts~n", [URI]),
er_coap_client:request(put, URI).
connection(Channel) ->
URI =
?MQTT_PREFIX ++
"/connection?clientid=client1&username=admin&password=public",
connection(Channel, false).
connection(Channel, ShortenParamName) ->
Prefix = ?MQTT_PREFIX ++ "/connection",
Queries = #{
"clientid" => <<"client1">>,
"username" => <<"admin">>,
"password" => <<"public">>
},
URI = compose_uri(Prefix, Queries, ShortenParamName),
Req = make_req(post),
{ok, created, Data} = do_request(Channel, URI, Req),
#coap_content{payload = BinToken} = Data,
binary_to_list(BinToken).
disconnection(Channel, Token) ->
%% delete
URI = ?MQTT_PREFIX ++ "/connection?clientid=client1&token=" ++ Token,
disconnection(Channel, Token, false).
disconnection(Channel, Token, ShortenParamName) ->
Prefix = ?MQTT_PREFIX ++ "/connection",
Queries = #{
"clientid" => <<"client1">>,
"token" => Token
},
URI = compose_uri(Prefix, Queries, ShortenParamName),
Req = make_req(delete),
{ok, deleted, _} = do_request(Channel, URI, Req).
observe(Channel, Token, true) ->
URI = ?PS_PREFIX ++ "/coap/observe?clientid=client1&token=" ++ Token,
observe(Channel, Token, Observe) ->
observe(Channel, Token, Observe, false).
observe(Channel, Token, true, ShortenParamName) ->
Prefix = ?PS_PREFIX ++ "/coap/observe",
Queries = #{
"clientid" => <<"client1">>,
"token" => Token
},
URI = compose_uri(Prefix, Queries, ShortenParamName),
Req = make_req(get, <<>>, [{observe, 0}]),
{ok, content, _Data} = do_request(Channel, URI, Req),
ok;
observe(Channel, Token, false) ->
URI = ?PS_PREFIX ++ "/coap/observe?clientid=client1&token=" ++ Token,
observe(Channel, Token, false, ShortenParamName) ->
Prefix = ?PS_PREFIX ++ "/coap/observe",
Queries = #{
"clientid" => <<"client1">>,
"token" => Token
},
URI = compose_uri(Prefix, Queries, ShortenParamName),
Req = make_req(get, <<>>, [{observe, 1}]),
{ok, nocontent, _Data} = do_request(Channel, URI, Req),
ok.
@ -620,8 +675,16 @@ observe(Channel, Token, false) ->
pubsub_uri(Topic) when is_list(Topic) ->
?PS_PREFIX ++ "/" ++ Topic.
pubsub_uri(Topic, Token) when is_list(Topic), is_list(Token) ->
?PS_PREFIX ++ "/" ++ Topic ++ "?clientid=client1&token=" ++ Token.
pubsub_uri(Topic, Token) ->
pubsub_uri(Topic, Token, false).
pubsub_uri(Topic, Token, ShortenParamName) when is_list(Topic), is_list(Token) ->
Prefix = ?PS_PREFIX ++ "/" ++ Topic,
Queries = #{
"clientid" => <<"client1">>,
"token" => Token
},
compose_uri(Prefix, Queries, ShortenParamName).
make_req(Method) ->
make_req(Method, <<>>).
@ -701,3 +764,28 @@ get_field(type, #coap_message{type = Type}) ->
Type;
get_field(method, #coap_message{method = Method}) ->
Method.
compose_uri(URI, Queries, ShortenParamName) ->
Queries1 = shorten_param_name(ShortenParamName, Queries),
case maps:size(Queries1) of
0 ->
URI;
_ ->
URI ++ "?" ++ uri_string:compose_query(maps:to_list(Queries1))
end.
shorten_param_name(false, Queries) ->
Queries;
shorten_param_name(true, Queries) ->
lists:foldl(
fun({Short, Long}, Acc) ->
case maps:take(Long, Acc) of
error ->
Acc;
{Value, Acc1} ->
maps:put(Short, Value, Acc1)
end
end,
Queries,
emqx_coap_message:query_params_mapping_table()
).

View File

@ -0,0 +1,41 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2024 EMQ Technologies Co., Ltd. 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.
%%--------------------------------------------------------------------
-module(emqx_coap_message_tests).
-include_lib("eunit/include/eunit.hrl").
short_name_uri_query_test() ->
UriQueryMap = #{
<<"c">> => <<"clientid_value">>,
<<"t">> => <<"token_value">>,
<<"u">> => <<"username_value">>,
<<"p">> => <<"password_value">>,
<<"q">> => 1,
<<"r">> => false
},
ParsedQuery = #{
<<"clientid">> => <<"clientid_value">>,
<<"token">> => <<"token_value">>,
<<"username">> => <<"username_value">>,
<<"password">> => <<"password_value">>,
<<"qos">> => 1,
<<"retain">> => false
},
Msg = emqx_coap_message:request(
con, put, <<"payload contents...">>, #{uri_query => UriQueryMap}
),
?assertEqual(ParsedQuery, emqx_coap_message:extract_uri_query(Msg)).

View File

@ -0,0 +1,2 @@
The CoAP gateway supports short parameter names for slight savings in datagram size.
For example, `clientid=bar` can be written as `c=bar`.