Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed the issue where auto_observe was not working in LwM2M Gateway #10257

Merged
merged 3 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/emqx_gateway/src/lwm2m/emqx_lwm2m_cmd.erl
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"discover">>, <<"dat
[
{uri_path, FullPathList},
{uri_query, QueryList},
{'accept', ?LWM2M_FORMAT_LINK}
{accept, ?LWM2M_FORMAT_LINK}
]
),
InputCmd
Expand Down Expand Up @@ -241,6 +241,7 @@ empty_ack_to_mqtt(Ref) ->
coap_failure_to_mqtt(Ref, MsgType) ->
make_base_response(maps:put(<<"msgType">>, MsgType, Ref)).

%% TODO: application/link-format
content_to_mqtt(CoapPayload, <<"text/plain">>, Ref) ->
emqx_lwm2m_message:text_to_json(extract_path(Ref), CoapPayload);
content_to_mqtt(CoapPayload, <<"application/octet-stream">>, Ref) ->
Expand Down
32 changes: 23 additions & 9 deletions apps/emqx_gateway/src/lwm2m/emqx_lwm2m_session.erl
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@
%%--------------------------------------------------------------------
-module(emqx_lwm2m_session).

-include("src/coap/include/emqx_coap.hrl").
-include("src/lwm2m/include/emqx_lwm2m.hrl").
-include_lib("emqx/include/logger.hrl").
-include_lib("emqx/include/emqx.hrl").
-include_lib("emqx/include/emqx_mqtt.hrl").
-include("src/coap/include/emqx_coap.hrl").
-include("src/lwm2m/include/emqx_lwm2m.hrl").
-include_lib("snabbkaffe/include/snabbkaffe.hrl").

%% API
-export([
Expand Down Expand Up @@ -513,12 +514,20 @@ observe_object_list(AlternatePath, ObjectList, Session) ->
true ->
Acc;
false ->
try
emqx_lwm2m_xml_object_db:find_objectid(binary_to_integer(ObjId)),
observe_object(AlternatePath, ObjectPath, Acc)
catch
error:no_xml_definition ->
Acc
ObjId1 = binary_to_integer(ObjId),
case emqx_lwm2m_xml_object_db:find_objectid(ObjId1) of
{error, no_xml_definition} ->
?tp(
warning,
ignore_observer_resource,
#{
reason => no_xml_definition,
object_id => ObjId1
}
),
Acc;
_ ->
observe_object(AlternatePath, ObjectPath, Acc)
end
end
end,
Expand All @@ -538,15 +547,20 @@ deliver_auto_observe_to_coap(AlternatePath, TermData, Session) ->
path => AlternatePath,
data => TermData
}),
{Req, Ctx} = emqx_lwm2m_cmd:mqtt_to_coap(AlternatePath, TermData),
{Req0, Ctx} = emqx_lwm2m_cmd:mqtt_to_coap(AlternatePath, TermData),
Req = alloc_token(Req0),
maybe_do_deliver_to_coap(Ctx, Req, 0, false, Session).

is_auto_observe() ->
emqx:get_config([gateway, lwm2m, auto_observe]).

alloc_token(Req = #coap_message{}) ->
Req#coap_message{token = crypto:strong_rand_bytes(4)}.

%%--------------------------------------------------------------------
%% Response
%%--------------------------------------------------------------------

handle_coap_response(
{Ctx = #{<<"msgType">> := EventType}, #coap_message{
method = CoapMsgMethod,
Expand Down
7 changes: 5 additions & 2 deletions apps/emqx_gateway/src/lwm2m/emqx_lwm2m_xml_object_db.erl
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
start_link(XmlDir) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [XmlDir], []).

-spec find_objectid(integer()) -> {error, no_xml_definition} | xmerl:xmlElement().
find_objectid(ObjectId) ->
ObjectIdInt =
case is_list(ObjectId) of
Expand All @@ -65,9 +66,10 @@ find_objectid(ObjectId) ->
end,
case ets:lookup(?LWM2M_OBJECT_DEF_TAB, ObjectIdInt) of
[] -> {error, no_xml_definition};
[{ObjectId, Xml}] -> Xml
[{_ObjectId, Xml}] -> Xml
end.

-spec find_name(string()) -> {error, no_xml_definition} | xmerl:xmlElement().
find_name(Name) ->
NameBinary =
case is_list(Name) of
Expand All @@ -77,10 +79,11 @@ find_name(Name) ->
case ets:lookup(?LWM2M_OBJECT_NAME_TO_ID_TAB, NameBinary) of
[] ->
{error, no_xml_definition};
[{NameBinary, ObjectId}] ->
[{_NameBinary, ObjectId}] ->
find_objectid(ObjectId)
end.

-spec stop() -> ok.
stop() ->
gen_server:stop(?MODULE).

Expand Down
33 changes: 32 additions & 1 deletion apps/emqx_gateway/src/lwm2m/include/emqx_lwm2m.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,39 @@
-define(ERR_BAD_REQUEST, <<"Bad Request">>).
-define(REG_PREFIX, <<"rd">>).

%%--------------------------------------------------------------------
%% Data formats for transferring resource information, defined in
%% OMA-TS-LightweightM2M-V1_0_1-20170704-A

%% 0: Plain text. 0 is numeric value used in CoAP Content-Format option.
%% The plain text format is used for "Read" and "Write" operations on singular
%% Resources. i.e: /3/0/0
%%
%% This data format has a Media Type of "text/plain".
-define(LWM2M_FORMAT_PLAIN_TEXT, 0).

%% 40: Link format. 40 is numeric value used in CoAP Content-Format option.
%%
-define(LWM2M_FORMAT_LINK, 40).

%% 42: Opaque. 41 is numeric value used in CoAP Content-Format option.
%% The opaque format is used for "Read" and "Write" operations on singular
%% Resources where the value of the Resource is an opaque binary value.
%% i.e: firmware images or opaque value from top layer.
%%
%% This data format has a Media Type of "application/octet-stream".
-define(LWM2M_FORMAT_OPAQUE, 42).

%% 11542: TLV. 11542 is numeric value used in CoAP Content-Format option.
%% For "Read" and "Write" operation, the binary TLV format is used to represent
%% an array of values or a single value using a compact binary representation.
%%
%% This data format has a Media Type of "application/vnd.oma.lwm2m+tlv".
-define(LWM2M_FORMAT_TLV, 11542).
-define(LWMWM_FORMAT_JSON, 11543).

%% 11543: JSON. 11543 is numeric value used in CoAP Content-Format option.
%% The client may support the JSON format for "Read" and "Write" operations to
%% represent multiple resource or single resource values.
%%
%% This data format has a Media Type of "application/vnd.oma.lwm2m+json".
-define(LWM2M_FORMAT_OMA_JSON, 11543).
116 changes: 89 additions & 27 deletions apps/emqx_gateway/test/emqx_lwm2m_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -35,29 +35,7 @@
-include("src/coap/include/emqx_coap.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").

-define(CONF_DEFAULT, <<
"\n"
"gateway.lwm2m {\n"
" xml_dir = \"../../lib/emqx_gateway/src/lwm2m/lwm2m_xml\"\n"
" lifetime_min = 1s\n"
" lifetime_max = 86400s\n"
" qmode_time_window = 22\n"
" auto_observe = false\n"
" mountpoint = \"lwm2m/${username}\"\n"
" update_msg_publish_condition = contains_object_list\n"
" translators {\n"
" command = {topic = \"/dn/#\", qos = 0}\n"
" response = {topic = \"/up/resp\", qos = 0}\n"
" notify = {topic = \"/up/notify\", qos = 0}\n"
" register = {topic = \"/up/resp\", qos = 0}\n"
" update = {topic = \"/up/resp\", qos = 0}\n"
" }\n"
" listeners.udp.default {\n"
" bind = 5783\n"
" }\n"
"}\n"
>>).
-include_lib("snabbkaffe/include/snabbkaffe.hrl").

-record(coap_content, {content_format, payload = <<>>}).

Expand Down Expand Up @@ -99,7 +77,8 @@ groups() ->
%% case06_register_wrong_lifetime, %% now, will ignore wrong lifetime
case07_register_alternate_path_01,
case07_register_alternate_path_02,
case08_reregister
case08_reregister,
case09_auto_observe
]},
{test_grp_1_read, [RepeatOpt], [
case10_read,
Expand Down Expand Up @@ -164,8 +143,15 @@ end_per_suite(Config) ->
emqx_mgmt_api_test_util:end_suite([emqx_conf, emqx_authn]),
Config.

init_per_testcase(_AllTestCase, Config) ->
ok = emqx_common_test_helpers:load_config(emqx_gateway_schema, ?CONF_DEFAULT),
init_per_testcase(TestCase, Config) ->
GatewayConfig =
case TestCase of
case09_auto_observe ->
default_config(#{auto_observe => true});
_ ->
default_config()
end,
ok = emqx_common_test_helpers:load_config(emqx_gateway_schema, GatewayConfig),

{ok, _} = application:ensure_all_started(emqx_gateway),
{ok, ClientUdpSock} = gen_udp:open(0, [binary, {active, false}]),
Expand All @@ -187,7 +173,37 @@ end_per_testcase(_AllTestCase, Config) ->
ok = application:stop(emqx_gateway).

default_config() ->
?CONF_DEFAULT.
default_config(#{}).

default_config(Overrides) ->
iolist_to_binary(
io_lib:format(
"\n"
"gateway.lwm2m {\n"
" xml_dir = \"../../lib/emqx_gateway/src/lwm2m/lwm2m_xml\"\n"
" lifetime_min = 1s\n"
" lifetime_max = 86400s\n"
" qmode_time_window = 22\n"
" auto_observe = ~w\n"
" mountpoint = \"lwm2m/${username}\"\n"
" update_msg_publish_condition = contains_object_list\n"
" translators {\n"
" command = {topic = \"/dn/#\", qos = 0}\n"
" response = {topic = \"/up/resp\", qos = 0}\n"
" notify = {topic = \"/up/notify\", qos = 0}\n"
" register = {topic = \"/up/resp\", qos = 0}\n"
" update = {topic = \"/up/resp\", qos = 0}\n"
" }\n"
" listeners.udp.default {\n"
" bind = ~w\n"
" }\n"
"}\n",
[
maps:get(auto_observe, Overrides, false),
maps:get(bind, Overrides, ?PORT)
]
)
).

default_port() ->
?PORT.
Expand Down Expand Up @@ -762,6 +778,52 @@ case08_reregister(Config) ->
%% verify the lwm2m client is still online
?assertEqual(ReadResult, test_recv_mqtt_response(ReportTopic)).

case09_auto_observe(Config) ->
UdpSock = ?config(sock, Config),
Epn = "urn:oma:lwm2m:oma:3",
MsgId1 = 15,
RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
timer:sleep(200),

ok = snabbkaffe:start_trace(),

%% step 1, device register ...
test_send_coap_request(
UdpSock,
post,
sprintf("coap://127.0.0.1:~b/rd?ep=~ts&lt=345&lwm2m=1", [?PORT, Epn]),
#coap_content{
content_format = <<"text/plain">>,
payload = <<
"</lwm2m>;rt=\"oma.lwm2m\";ct=11543,"
"</lwm2m/1/0>,</lwm2m/2/0>,</lwm2m/3/0>,</lwm2m/59102/0>"
>>
},
[],
MsgId1
),
#coap_message{method = Method1} = test_recv_coap_response(UdpSock),
?assertEqual({ok, created}, Method1),

#coap_message{
method = Method2,
token = Token2,
options = Options2
} = test_recv_coap_request(UdpSock),
?assertEqual(get, Method2),
?assertNotEqual(<<>>, Token2),
?assertMatch(
#{
observe := 0,
uri_path := [<<"lwm2m">>, <<"3">>, <<"0">>]
},
Options2
),

{ok, _} = ?block_until(#{?snk_kind := ignore_observer_resource}, 1000),
ok.

case10_read(Config) ->
UdpSock = ?config(sock, Config),
Epn = "urn:oma:lwm2m:oma:3",
Expand Down
11 changes: 11 additions & 0 deletions changes/ce/fix-10257.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Fixed the issue where `auto_observe` was not working in LwM2M Gateway.

Before the fix, OBSERVE requests were sent without a token, causing failures
that LwM2M clients could not handle.

After the fix, LwM2M Gateway can correctly observe the resource list carried by
client, furthermore, unknown resources will be ignored and printing the following
warning log:
```
2023-03-28T18:50:27.771123+08:00 [warning] msg: ignore_observer_resource, mfa: emqx_lwm2m_session:observe_object_list/3, line: 522, peername: 127.0.0.1:56830, clientid: testlwm2mclient, object_id: 31024, reason: no_xml_definition
```
8 changes: 8 additions & 0 deletions changes/ce/fix-10257.zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
修复 LwM2M 网关 `auto_observe` 不工作的问题。

在修复之前,下发的 OBSERVE 请求没有 Token 从而导致 LwM2M 客户端无法处理的失败。

修复后,能正确监听 LwM2M 携带的资源列表、和会忽略未知的资源,并打印以下日志:
```
2023-03-28T18:50:27.771123+08:00 [warning] msg: ignore_observer_resource, mfa: emqx_lwm2m_session:observe_object_list/3, line: 522, peername: 127.0.0.1:56830, clientid: testlwm2mclient, object_id: 31024, reason: no_xml_definition
```