From dc64068e41adc6ce200b79775ed97ca9797b99cd Mon Sep 17 00:00:00 2001 From: JimMoen Date: Wed, 8 Nov 2023 19:25:59 +0800 Subject: [PATCH] test(gw_jt808): frame parser and channel SUITE --- .../test/emqx_jt808_SUITE.erl | 2651 +++++++++++++++++ .../test/emqx_jt808_auth_http_test_server.erl | 100 + .../test/emqx_jt808_parser_SUITE.erl | 721 +++++ 3 files changed, 3472 insertions(+) create mode 100644 apps/emqx_gateway_jt808/test/emqx_jt808_SUITE.erl create mode 100644 apps/emqx_gateway_jt808/test/emqx_jt808_auth_http_test_server.erl create mode 100644 apps/emqx_gateway_jt808/test/emqx_jt808_parser_SUITE.erl diff --git a/apps/emqx_gateway_jt808/test/emqx_jt808_SUITE.erl b/apps/emqx_gateway_jt808/test/emqx_jt808_SUITE.erl new file mode 100644 index 000000000..449caff27 --- /dev/null +++ b/apps/emqx_gateway_jt808/test/emqx_jt808_SUITE.erl @@ -0,0 +1,2651 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_jt808_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include("emqx_jt808.hrl"). +-include_lib("emqx/include/emqx.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). + +-define(FRM_FLAG, 16#7e:8). +-define(RESERVE, 0). +-define(NO_FRAGMENT, 0). +-define(WITH_FRAGMENT, 1). +-define(NO_ENCRYPT, 0). +-define(MSG_SIZE(X), X:10 / big - integer). + +-define(WORD, 16 / big - integer). +-define(DWORD, 32 / big - integer). + +-define(PORT, 6207). +-define(PORT_STR, "6207"). +-define(LOGT(Format, Args), ct:pal("TEST_SUITE: " ++ Format, Args)). + +-define(PROTO_REG_SERVER_HOST, "http://127.0.0.1:8991"). +-define(PROTO_REG_AUTH_PATH, "/jt808/auth"). +-define(PROTO_REG_REGISTRY_PATH, "/jt808/registry"). + +-define(CONF_DEFAULT, << + "\n" + "gateway.jt808 {\n" + " listeners.tcp.default {\n" + " bind = " + ?PORT_STR + "\n" + " }\n" + " proto {\n" + " allow_anonymous = false\n" + " registry = " + "\"" + ?PROTO_REG_SERVER_HOST + ?PROTO_REG_REGISTRY_PATH + "\"\n" + " authentication = " + "\"" + ?PROTO_REG_SERVER_HOST + ?PROTO_REG_AUTH_PATH + "\"\n" + " }\n" + "}\n" +>>). + +all() -> + []. +%% emqx_common_test_helpers:all(?MODULE). + +init_per_suite(Config) -> + application:load(emqx_gateway_jt808), + Apps = emqx_cth_suite:start( + [ + cowboy, + {emqx_conf, ?CONF_DEFAULT}, + emqx_gateway, + emqx_management, + {emqx_dashboard, "dashboard.listeners.http { enable = true, bind = 18083 }"} + ], + #{work_dir => emqx_cth_suite:work_dir(Config)} + ), + emqx_jt808_auth_http_test_server:start_link(), + emqx_common_test_http:create_default_app(), + [{suite_apps, Apps} | Config]. + +end_per_suite(Config) -> + emqx_jt808_auth_http_test_server:stop(), + emqx_common_test_http:delete_default_app(), + emqx_cth_suite:stop(?config(suite_apps, Config)), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% helper functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +gen_packet(Header, Body) -> + S1 = <
>, + Crc = make_crc(S1, undefined), + S2 = do_escape(<>), + Stream = <<16#7e:8, S2/binary, 16#7e:8>>, + ?LOGT("encode a packet=~p", [binary_to_hex_string(Stream)]), + Stream. + +make_crc(<<>>, Xor) -> + ?LOGT("crc is ~p", [Xor]), + Xor; +make_crc(<>, undefined) -> + make_crc(Rest, C); +make_crc(<>, Xor) -> + make_crc(Rest, C bxor Xor). + +do_escape(Binary) -> + do_escape(Binary, <<>>). + +do_escape(<<>>, Acc) -> + Acc; +do_escape(<<16#7e, Rest/binary>>, Acc) -> + do_escape(Rest, <>); +do_escape(<<16#7d, Rest/binary>>, Acc) -> + do_escape(Rest, <>); +do_escape(<>, Acc) -> + do_escape(Rest, <>). + +client_regi_procedure(Socket) -> + client_regi_procedure(Socket, <<"123456">>). + +client_regi_procedure(Socket, ExpectedCode) -> + % + % send REGISTER + % + Manuf = <<"examp">>, + Model = <<"33333333333333333333">>, + DevId = <<"1234567">>, + + Color = 3, + Plate = <<"ujvl239">>, + RegisterPacket = + <<58:?WORD, 59:?WORD, Manuf/binary, Model/binary, DevId/binary, Color, Plate/binary>>, + MsgId = ?MC_REGISTER, + PhoneBCD = <<16#00, 16#61, 16#23, 16#45, 16#67, 16#89>>, + MsgSn = 78, + Size = size(RegisterPacket), + Header = + <>, + S1 = gen_packet(Header, RegisterPacket), + + ok = gen_tcp:send(Socket, S1), + {ok, Packet} = gen_tcp:recv(Socket, 0, 500), + + AckPacket = <>, + Size2 = size(AckPacket), + MsgId2 = ?MS_REGISTER_ACK, + MsgSn2 = 0, + Header2 = + <>, + S2 = gen_packet(Header2, AckPacket), + ?LOGT("S2=~p", [binary_to_hex_string(S2)]), + ?LOGT("Packet=~p", [binary_to_hex_string(Packet)]), + ?assertEqual(S2, Packet), + {ok, ExpectedCode}. + +client_auth_procedure(Socket, AuthCode) -> + ?LOGT("start auth procedure", []), + % + % send AUTH + % + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + MsgId = ?MC_AUTH, + MsgSn = 78, + Size = size(AuthCode), + Header = + <>, + S1 = gen_packet(Header, AuthCode), + ?LOGT("auth S1=~p", [S1]), + + ok = gen_tcp:send(Socket, S1), + %% timer:sleep(200), + {ok, Packet} = gen_tcp:recv(Socket, 0, 500), + + % receive general response + GenAckPacket = <>, + Size2 = size(GenAckPacket), + MsgId2 = ?MS_GENERAL_RESPONSE, + MsgSn2 = 1, + Header2 = + <>, + S2 = gen_packet(Header2, GenAckPacket), + ?assertEqual(S2, Packet), + ?assert(lists:member(<<"jt808/000123456789/dn">>, emqx:topics())), + + ?LOGT("============= auth procedure success ===============", []). + +client_send_general_response(Socket, MsgId3, MsgSn3, PhoneBCD) -> + GenAckPacket4 = <>, + Size4 = size(GenAckPacket4), + MsgId4 = ?MC_GENERAL_RESPONSE, + MsgSn4 = 1, + Header4 = + <>, + S4 = gen_packet(Header4, GenAckPacket4), + ?LOGT("S4 = ~p", [S4]), + + ok = gen_tcp:send(Socket, S4), + ok. + +location_report() -> + Alarm = 2, + Status = 3, + Latitude = 1000, + Longitude = 1001, + Altitude = 2000, + Speed = 135, + Direction = 32, + TimeBCD = <<16#17, 16#10, 16#22, 16#11, 16#15, 16#53>>, + Time = <<"171022111553">>, + MileAge = 12379, + FuelMeter = 972, + Speed2 = 29, + AlarmID = 18, + OA_Type = 2, + OA_Id = 29, + IOA_Type = 8, + IOA_Id = 23, + IOA_Direction = 89, + PTA_ID = 45, + PTA_Time = 21, + PTA_Result = 1, + Binary = + <>, + Json = #{ + <<"alarm">> => Alarm, + <<"status">> => Status, + <<"latitude">> => Latitude, + <<"longitude">> => Longitude, + <<"altitude">> => Altitude, + <<"speed">> => Speed, + <<"direction">> => Direction, + <<"time">> => Time, + <<"extra">> => #{ + <<"mileage">> => MileAge, + <<"fuel_meter">> => FuelMeter, + <<"speed">> => Speed2, + <<"alarm_id">> => AlarmID, + <<"overspeed_alarm">> => + #{ + <<"type">> => OA_Type, + <<"id">> => OA_Id + }, + <<"in_out_alarm">> => + #{ + <<"type">> => IOA_Type, + <<"id">> => IOA_Id, + <<"direction">> => IOA_Direction + }, + <<"path_time_alarm">> => + #{ + <<"id">> => PTA_ID, + <<"time">> => PTA_Time, + <<"result">> => PTA_Result + } + } + }, + {Binary, Json}. + +location_report_28bytes() -> + Alarm = 2, + Status = 3, + Latitude = 1000, + Longitude = 1001, + Altitude = 2000, + Speed = 135, + Direction = 32, + TimeBCD = <<16#17, 16#10, 16#22, 16#11, 16#15, 16#53>>, + Time = <<"171022111553">>, + Binary = + <>, + Json = #{ + <<"alarm">> => Alarm, + <<"status">> => Status, + <<"latitude">> => Latitude, + <<"longitude">> => Longitude, + <<"altitude">> => Altitude, + <<"speed">> => Speed, + <<"direction">> => Direction, + <<"time">> => Time + }, + {Binary, Json}. + +binary_to_hex_string(Data) -> + lists:flatten([io_lib:format("~2.16.0B ", [X]) || <> <= Data]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%% test cases %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +t_register(_) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + + {ok, AuthCode} = client_regi_procedure(Socket), + ?assertEqual(AuthCode, <<"123456">>), + + ok = gen_tcp:close(Socket). + +t_auth(_) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}, {nodelay, true}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + + ok = gen_tcp:close(Socket). + +t_anonymous_register_and_auth(_) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + + {ok, AuthCode} = client_regi_procedure(Socket, <<>>), + ?assertEqual(AuthCode, <<>>), + + ok = client_auth_procedure(Socket, AuthCode), + + ok = gen_tcp:close(Socket). + +t_heartbeat(_) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + % + % send heartbeat + % + MsgId = ?MC_HEARTBEAT, + MsgSn = 78, + Size = 0, + Header = + <>, + S1 = gen_packet(Header, <<>>), + + ok = gen_tcp:send(Socket, S1), + %% timer:sleep(200), + {ok, Packet} = gen_tcp:recv(Socket, 0, 500), + + GenAckPacket = <>, + Size2 = size(GenAckPacket), + MsgId2 = ?MS_GENERAL_RESPONSE, + MsgSn2 = 2, + Header2 = + <>, + S2 = gen_packet(Header2, GenAckPacket), + ?assertEqual(S2, Packet), + + ok = gen_tcp:close(Socket). + +t_case04(_) -> + ok = emqx:subscribe(<<"jt808/000123456789/up">>), + + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + %% send event report + EventReportId = 98, + MsgBody3 = <>, + MsgId3 = ?MC_EVENT_REPORT, + MsgSn3 = 79, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + ok = gen_tcp:send(Socket, S3), + {ok, Packet4} = gen_tcp:recv(Socket, 0, 500), + + % receive general response + GenAckPacket4 = <>, + Size4 = size(GenAckPacket4), + MsgId4 = ?MS_GENERAL_RESPONSE, + MsgSn4 = 2, + Header4 = + <>, + S4 = gen_packet(Header4, GenAckPacket4), + ?assertEqual(S4, Packet4), + timer:sleep(100), + {<<"jt808/000123456789/up">>, Payload} = receive_msg(), + ?assertEqual( + #{ + <<"header">> => #{ + <<"msg_id">> => MsgId3, + <<"encrypt">> => ?NO_ENCRYPT, + <<"phone">> => <<"000123456789">>, + <<"len">> => Size3, + <<"msg_sn">> => MsgSn3 + }, + <<"body">> => #{<<"id">> => EventReportId} + }, + emqx_utils_json:decode(Payload, [return_maps]) + ), + + ok = gen_tcp:close(Socket). + +t_case05(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + Flag = 15, + Text = <<"who are you">>, + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_SEND_TEXT}, + <<"body">> => #{<<"flag">> => Flag, <<"text">> => Text} + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + % + % client get downlink "send text" + % + + MsgBody3 = <>, + MsgId3 = ?MS_SEND_TEXT, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + ?LOGT("S3=~p", [S3]), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?assertEqual(S3, Packet3), + + % client send "client general response" + GenAckPacket4 = <>, + Size4 = size(GenAckPacket4), + MsgId4 = ?MC_GENERAL_RESPONSE, + MsgSn4 = 2, + Header4 = + <>, + S4 = gen_packet(Header4, GenAckPacket4), + ?LOGT("S4 = ~p", [S4]), + + ok = gen_tcp:send(Socket, S4), + %% timer:sleep(300), + + ok = gen_tcp:close(Socket). + +t_case06_downlink_retx(_) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + Flag = 15, + Text = <<"who are you">>, + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_SEND_TEXT}, + <<"body">> => #{<<"flag">> => Flag, <<"text">> => Text} + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + % + % client get downlink "send text" + % + + MsgBody3 = <>, + MsgId3 = ?MS_SEND_TEXT, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + ?LOGT("S3=~p", [S3]), + + %% wait emqx-jt808 to retx "send text" + timer:sleep(100), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?assertEqual(S3, Packet3), + + % client send "client general response" + GenAckPacket4 = <>, + Size4 = size(GenAckPacket4), + MsgId4 = ?MC_GENERAL_RESPONSE, + MsgSn4 = 2, + Header4 = + <>, + S4 = gen_packet(Header4, GenAckPacket4), + ?LOGT("S4 = ~p", [S4]), + + ok = gen_tcp:send(Socket, S4), + %% timer:sleep(300), + + % wait again, there should be no retx packet + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case07_dl_0x8302_send_question(_) -> + ok = emqx:subscribe(<<"jt808/000123456789/up">>), + + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + Flag = 16#10, + Question = <<"who are you">>, + Length = size(Question), + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_SEND_QUESTION}, + <<"body">> => #{ + <<"flag">> => Flag, + <<"length">> => Length, + <<"question">> => Question, + <<"answers">> => [ + #{<<"id">> => 1, <<"len">> => 3, <<"answer">> => <<"Tom">>}, + #{<<"id">> => 2, <<"len">> => 4, <<"answer">> => <<"Mike">>} + ] + } + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + %% client get downlink "send text" + MsgBody3 = + <>/binary, 2:8, 4:?WORD, + <<"Mike">>/binary>>, + MsgId3 = ?MS_SEND_QUESTION, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?assertEqual(S3, Packet3), + + % client send "client answer" + Answer = <>, + Size4 = size(Answer), + MsgId4 = ?MC_QUESTION_ACK, + MsgSn4 = 2, + Header4 = + <>, + S4 = gen_packet(Header4, Answer), + + ok = gen_tcp:send(Socket, S4), + timer:sleep(100), + + {<<"jt808/000123456789/up">>, Payload} = receive_msg(), + ?assertEqual( + #{ + <<"header">> => #{ + <<"msg_id">> => MsgId4, + <<"encrypt">> => ?NO_ENCRYPT, + <<"phone">> => <<"000123456789">>, + <<"len">> => Size4, + <<"msg_sn">> => MsgSn4 + }, + <<"body">> => #{<<"seq">> => MsgSn3, <<"id">> => 2} + }, + emqx_utils_json:decode(Payload, [return_maps]) + ), + + ok = gen_tcp:close(Socket). + +t_case08_dl_0x8500_vehicle_ctrl(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + ok = emqx:subscribe(<<"jt808/000123456789/up">>), + + Flag = 16#0, + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_VEHICLE_CONTROL}, + <<"body">> => #{<<"flag">> => Flag} + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + % + % client get downlink "vehicle ctrl" + % + MsgBody3 = <>, + MsgId3 = ?MS_VEHICLE_CONTROL, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?assertEqual(S3, Packet3), + + % client send "client answer" + CtrlAck = + <<126, 5, 0, 0, 30, 1, 136, 118, 99, 137, 114, 1, 244, 0, 7, 0, 0, 0, 0, 0, 4, 0, 0, 1, 49, + 122, 103, 6, 147, 104, 81, 0, 24, 0, 0, 0, 121, 23, 16, 32, 18, 3, 25, 69, 126>>, + ?LOGT("S4 = ~p", [CtrlAck]), + + ok = gen_tcp:send(Socket, CtrlAck), + timer:sleep(300), + + {<<"jt808/000123456789/up">>, Payload} = receive_msg(), + ?assertEqual( + #{ + <<"header">> => + #{ + <<"encrypt">> => 0, + <<"len">> => 30, + <<"msg_id">> => 1280, + <<"msg_sn">> => 500, + <<"phone">> => <<"018876638972">> + }, + <<"body">> => + #{ + <<"seq">> => 7, + <<"location">> => #{ + <<"alarm">> => 0, + <<"altitude">> => 24, + <<"direction">> => 121, + <<"latitude">> => 20019815, + <<"longitude">> => 110323793, + <<"speed">> => 0, + <<"status">> => 262144, + <<"time">> => <<"171020120319">> + } + } + }, + emqx_utils_json:decode(Payload, [return_maps]) + ), + + ok = gen_tcp:close(Socket). + +t_case09_dl_0x8103_set_client_param(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + Length = 2, + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_SET_CLIENT_PARAM}, + <<"body">> => #{ + <<"length">> => Length, + <<"params">> => [ + #{<<"id">> => 16#0013, <<"value">> => <<"www.example.com">>}, + #{<<"id">> => 16#0059, <<"value">> => 1200} + ] + } + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + % + % client get downlink "vehicle ctrl" + % + MsgBody3 = + <>/binary, 16#0059:?DWORD, 4:8, + 1200:?DWORD>>, + MsgId3 = ?MS_SET_CLIENT_PARAM, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + % client send "general response" + GenAckPacket4 = <>, + Size4 = size(GenAckPacket4), + MsgId4 = ?MC_GENERAL_RESPONSE, + MsgSn4 = 1, + Header4 = + <>, + S4 = gen_packet(Header4, GenAckPacket4), + ?LOGT("S4 = ~p", [S4]), + + ok = gen_tcp:send(Socket, S4), + + % no retrasmition of 0x8103 + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case10_dl_0x8104_query_client_all_param(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_CLIENT_CONTROL}, + <<"body">> => #{<<"command">> => 200, <<"param">> => <<"ABCD">>} + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + % + % client get downlink command + % + MsgBody3 = <<200:8, <<"ABCD">>/binary>>, + MsgId3 = ?MS_CLIENT_CONTROL, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + % client send ack + client_send_general_response(Socket, MsgId3, MsgSn3, PhoneBCD), + + %% timer:sleep(200), + + % no retrasmition of downlink message + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case11_dl_0x8106_query_client_param(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + ok = emqx:subscribe(<<"jt808/000123456789/up">>), + + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_QUERY_CLIENT_PARAM}, + <<"body">> => #{<<"length">> => 2, <<"ids">> => [16#0092, 16#0031]} + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + % + % client get downlink command + % + MsgBody3 = <<2:8, 16#0092:?DWORD, 16#0031:?DWORD>>, + MsgId3 = ?MS_QUERY_CLIENT_PARAM, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + UlPacket4 = <>, + Size4 = size(UlPacket4), + MsgId4 = ?MC_QUERY_PARAM_ACK, + MsgSn4 = 2, + Header4 = + <>, + S4 = gen_packet(Header4, UlPacket4), + ?LOGT("S4 = ~p", [S4]), + + ok = gen_tcp:send(Socket, S4), + + {<<"jt808/000123456789/up">>, Payload} = receive_msg(), + ?assertEqual( + #{ + <<"header">> => #{ + <<"encrypt">> => 0, + <<"len">> => Size4, + <<"msg_id">> => ?MC_QUERY_PARAM_ACK, + <<"msg_sn">> => 2, + <<"phone">> => <<"000123456789">> + }, + <<"body">> => #{ + <<"seq">> => MsgSn3, + <<"length">> => 2, + <<"params">> => [ + #{<<"id">> => 16#0031, <<"value">> => 379}, + #{<<"id">> => 16#0092, <<"value">> => 2} + ] + } + }, + emqx_utils_json:decode(Payload, [return_maps]) + ), + + %% timer:sleep(200), + + % no retrasmition of downlink message + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case11_dl_0x8107_query_client_attrib(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + ok = emqx:subscribe(<<"jt808/000123456789/up">>), + + DlCommand = #{<<"header">> => #{<<"msg_id">> => ?MS_QUERY_CLIENT_ATTRIB}}, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + % + % client get downlink command + % + MsgBody3 = <<>>, + MsgId3 = ?MS_QUERY_CLIENT_ATTRIB, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + timer:sleep(100), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + UlPacket4 = + <<12:?WORD, <<"manu3">>/binary, <<"A1B2C3D4E5F6G7H8I9J0">>:20/binary, + <<"dev1234">>:7/binary, + <<16#33, 16#33, 16#33, 16#33, 16#33, 16#44, 16#44, 16#44, 16#44, 16#44>>:10/binary, 6:8, + <<"v2.3.7">>:6/binary, 5:8, <<"v1.26">>:5/binary, 101:8, 102:8>>, + Size4 = size(UlPacket4), + MsgId4 = ?MC_QUERY_ATTRIB_ACK, + MsgSn4 = 2, + Header4 = + <>, + S4 = gen_packet(Header4, UlPacket4), + ?LOGT("S4 = ~p", [S4]), + + ok = gen_tcp:send(Socket, S4), + timer:sleep(100), + + {<<"jt808/000123456789/up">>, Payload} = receive_msg(), + ?assertEqual( + #{ + <<"header">> => #{ + <<"encrypt">> => 0, + <<"len">> => Size4, + <<"msg_id">> => ?MC_QUERY_ATTRIB_ACK, + <<"msg_sn">> => 2, + <<"phone">> => <<"000123456789">> + }, + <<"body">> => #{ + <<"type">> => 12, + <<"manufacturer">> => <<"manu3">>, + <<"model">> => <<"A1B2C3D4E5F6G7H8I9J0">>, + <<"id">> => <<"dev1234">>, + <<"iccid">> => <<"33333333334444444444">>, + <<"hardware_version">> => <<"v2.3.7">>, + <<"firmware_version">> => <<"v1.26">>, + <<"gnss_prop">> => 101, + <<"comm_prop">> => 102 + } + }, + emqx_utils_json:decode(Payload, [return_maps]) + ), + + % no retrasmition of downlink message + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case15_dl_0x8201_query_location(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + ok = emqx:subscribe(<<"jt808/000123456789/up">>), + + DlCommand = #{<<"header">> => #{<<"msg_id">> => ?MS_QUERY_LOCATION}}, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + % + % client get downlink command + % + MsgBody3 = <<>>, + MsgId3 = ?MS_QUERY_LOCATION, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + {LocationReportBinary, LocationReportJson} = location_report(), + UlPacket4 = <>, + Size4 = size(UlPacket4), + MsgId4 = ?MC_QUERY_LOCATION_ACK, + MsgSn4 = 2, + Header4 = + <>, + S4 = gen_packet(Header4, UlPacket4), + ?LOGT("S4 = ~p", [S4]), + + ok = gen_tcp:send(Socket, S4), + timer:sleep(100), + + {<<"jt808/000123456789/up">>, Payload} = receive_msg(), + ?assertEqual( + #{ + <<"header">> => #{ + <<"encrypt">> => 0, + <<"len">> => Size4, + <<"msg_id">> => ?MC_QUERY_LOCATION_ACK, + <<"msg_sn">> => 2, + <<"phone">> => <<"000123456789">> + }, + <<"body">> => #{ + <<"seq">> => MsgSn3, + <<"params">> => LocationReportJson + } + }, + emqx_utils_json:decode(Payload, [return_maps]) + ), + + %% timer:sleep(200), + + % no retrasmition of downlink message + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_location_report(_) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + ok = emqx:subscribe(<<"jt808/000123456789/up">>), + + {LocationReportBinary, LocationReportJson} = location_report(), + UlPacket = <>, + Size = size(UlPacket), + MsgId = ?MC_LOCATION_REPORT, + MsgSn = 1, + Header = + <>, + S = gen_packet(Header, UlPacket), + ?LOGT("S = ~p", [S]), + + ok = gen_tcp:send(Socket, S), + timer:sleep(100), + + {<<"jt808/000123456789/up">>, Payload} = receive_msg(), + ?assertEqual( + #{ + <<"header">> => #{ + <<"encrypt">> => 0, + <<"len">> => Size, + <<"msg_id">> => ?MC_LOCATION_REPORT, + <<"msg_sn">> => MsgSn, + <<"phone">> => <<"000123456789">> + }, + <<"body">> => LocationReportJson + }, + emqx_utils_json:decode(Payload, [return_maps]) + ), + + % receive general response + {ok, Packet} = gen_tcp:recv(Socket, 0, 500), + GenAckPacket = <>, + Size2 = size(GenAckPacket), + MsgId2 = ?MS_GENERAL_RESPONSE, + MsgSn2 = 2, + Header2 = + <>, + S2 = gen_packet(Header2, GenAckPacket), + ?assertEqual(S2, Packet), + + % no retrasmition of downlink message + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case15_dl_0x8202_trace_location(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_TRACE_LOCATION}, + <<"body">> => #{<<"period">> => 23, <<"expiry">> => 183} + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + % + % client get downlink command + % + MsgBody3 = <<23:?WORD, 183:?DWORD>>, + MsgId3 = ?MS_TRACE_LOCATION, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + client_send_general_response(Socket, MsgId3, MsgSn3, PhoneBCD), + + % no retrasmition of downlink message + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case50_ul_0x0303_info_request_cancel(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + ok = emqx:subscribe(<<"jt808/000123456789/up">>), + + % + % send event report + % + MsgBody3 = <<1:8, 6:8>>, + MsgId3 = ?MC_INFO_REQ_CANCEL, + MsgSn3 = 79, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + ok = gen_tcp:send(Socket, S3), + %%timer:sleep(600), + {ok, Packet4} = gen_tcp:recv(Socket, 0, 500), + + % receive general response + GenAckPacket4 = <>, + Size4 = size(GenAckPacket4), + MsgId4 = ?MS_GENERAL_RESPONSE, + MsgSn4 = 2, + Header4 = + <>, + S4 = gen_packet(Header4, GenAckPacket4), + ?assertEqual(S4, Packet4), + + {<<"jt808/000123456789/up">>, Payload} = receive_msg(), + ?assertEqual( + #{ + <<"header">> => #{ + <<"msg_id">> => MsgId3, + <<"encrypt">> => ?NO_ENCRYPT, + <<"phone">> => <<"000123456789">>, + <<"len">> => Size3, + <<"msg_sn">> => MsgSn3 + }, + <<"body">> => #{<<"id">> => 1, <<"flag">> => 6} + }, + emqx_utils_json:decode(Payload, [return_maps]) + ), + + ok = gen_tcp:close(Socket). + +t_case51_ul_0x0701_waybill_report(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + ok = emqx:subscribe(<<"jt808/000123456789/up">>), + + % + % send event report + % + MsgBody3 = <<7:?DWORD, <<"ABCDEFG">>/binary>>, + MsgId3 = ?MC_WAYBILL_REPORT, + MsgSn3 = 79, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + ok = gen_tcp:send(Socket, S3), + %% timer:sleep(600), + {ok, Packet4} = gen_tcp:recv(Socket, 0, 500), + + % receive general response + GenAckPacket4 = <>, + Size4 = size(GenAckPacket4), + MsgId4 = ?MS_GENERAL_RESPONSE, + MsgSn4 = 2, + Header4 = + <>, + S4 = gen_packet(Header4, GenAckPacket4), + ?assertEqual(S4, Packet4), + + {<<"jt808/000123456789/up">>, Payload} = receive_msg(), + ?assertEqual( + #{ + <<"header">> => #{ + <<"msg_id">> => MsgId3, + <<"encrypt">> => ?NO_ENCRYPT, + <<"phone">> => <<"000123456789">>, + <<"len">> => Size3, + <<"msg_sn">> => MsgSn3 + }, + <<"body">> => #{<<"length">> => 7, <<"data">> => base64:encode(<<"ABCDEFG">>)} + }, + emqx_utils_json:decode(Payload, [return_maps]) + ), + + ok = gen_tcp:close(Socket). + +t_case52_ul_0x0705_can_bus_report(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + ok = emqx:subscribe(<<"jt808/000123456789/up">>), + + % + % send event report + % + MsgBody3 = + <<2:?WORD, <<16#09, 16#23, 16#46, 16#07, 16#25>>/binary, 0:1, 1:1, 0:1, 35:29, + <<"11111111">>/binary, 1:1, 0:1, 1:1, 36:29, <<"22222222">>/binary>>, + MsgId3 = ?MC_CAN_BUS_REPORT, + MsgSn3 = 79, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + ok = gen_tcp:send(Socket, S3), + %% timer:sleep(600), + {ok, Packet4} = gen_tcp:recv(Socket, 0, 500), + + % receive general response + GenAckPacket4 = <>, + Size4 = size(GenAckPacket4), + MsgId4 = ?MS_GENERAL_RESPONSE, + MsgSn4 = 2, + Header4 = + <>, + S4 = gen_packet(Header4, GenAckPacket4), + ?assertEqual(S4, Packet4), + + {<<"jt808/000123456789/up">>, Payload} = receive_msg(), + ?assertEqual( + #{ + <<"header">> => #{ + <<"msg_id">> => MsgId3, + <<"encrypt">> => ?NO_ENCRYPT, + <<"phone">> => <<"000123456789">>, + <<"len">> => Size3, + <<"msg_sn">> => MsgSn3 + }, + <<"body">> => #{ + <<"length">> => 2, + <<"time">> => <<"0923460725">>, + <<"can_data">> => [ + #{ + <<"channel">> => 0, + <<"frame_type">> => 1, + <<"data_method">> => 0, + <<"id">> => 35, + <<"data">> => base64:encode(<<"11111111">>) + }, + #{ + <<"channel">> => 1, + <<"frame_type">> => 0, + <<"data_method">> => 1, + <<"id">> => 36, + <<"data">> => base64:encode(<<"22222222">>) + } + ] + } + }, + emqx_utils_json:decode(Payload, [return_maps]) + ), + + ok = gen_tcp:close(Socket). + +t_case53_ul_0x0800_multimedia_event_report(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + ok = emqx:subscribe(<<"jt808/000123456789/up">>), + + % + % send event report + % + MsgBody3 = <<65:?DWORD, 2:8, 1:8, 4:8, 103:8>>, + MsgId3 = ?MC_MULTIMEDIA_EVENT_REPORT, + MsgSn3 = 79, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + ok = gen_tcp:send(Socket, S3), + %% timer:sleep(600), + {ok, Packet4} = gen_tcp:recv(Socket, 0, 500), + + % receive general response + GenAckPacket4 = <>, + Size4 = size(GenAckPacket4), + MsgId4 = ?MS_GENERAL_RESPONSE, + MsgSn4 = 2, + Header4 = + <>, + S4 = gen_packet(Header4, GenAckPacket4), + ?assertEqual(S4, Packet4), + + {<<"jt808/000123456789/up">>, Payload} = receive_msg(), + ?assertEqual( + #{ + <<"header">> => #{ + <<"msg_id">> => MsgId3, + <<"encrypt">> => ?NO_ENCRYPT, + <<"phone">> => <<"000123456789">>, + <<"len">> => Size3, + <<"msg_sn">> => MsgSn3 + }, + <<"body">> => #{ + <<"id">> => 65, + <<"type">> => 2, + <<"format">> => 1, + <<"event">> => 4, + <<"channel">> => 103 + } + }, + emqx_utils_json:decode(Payload, [return_maps]) + ), + + ok = gen_tcp:close(Socket). + +t_case54_ul_0x0900_send_transparent_data(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + ok = emqx:subscribe(<<"jt808/000123456789/up">>), + + % + % send event report + % + MsgBody3 = <<39:8, <<"oufwei">>/binary>>, + MsgId3 = ?MC_SEND_TRANSPARENT_DATA, + MsgSn3 = 79, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + ok = gen_tcp:send(Socket, S3), + %% timer:sleep(600), + {ok, Packet4} = gen_tcp:recv(Socket, 0, 500), + + % Receive general response + GenAckPacket4 = <>, + Size4 = size(GenAckPacket4), + MsgId4 = ?MS_GENERAL_RESPONSE, + MsgSn4 = 2, + Header4 = + <>, + S4 = gen_packet(Header4, GenAckPacket4), + ?assertEqual(S4, Packet4), + + {<<"jt808/000123456789/up">>, Payload} = receive_msg(), + ?assertEqual( + #{ + <<"header">> => #{ + <<"msg_id">> => MsgId3, + <<"encrypt">> => ?NO_ENCRYPT, + <<"phone">> => <<"000123456789">>, + <<"len">> => Size3, + <<"msg_sn">> => MsgSn3 + }, + <<"body">> => #{<<"type">> => 39, <<"data">> => base64:encode(<<"oufwei">>)} + }, + emqx_utils_json:decode(Payload, [return_maps]) + ), + ok = gen_tcp:close(Socket). + +t_case55_ul_0x0901_send_zip_data(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + ok = emqx:subscribe(<<"jt808/000123456789/up">>), + + % + % send event report + % + MsgBody3 = <<4:?DWORD, <<"1234">>/binary>>, + MsgId3 = ?MC_SEND_ZIP_DATA, + MsgSn3 = 79, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + ok = gen_tcp:send(Socket, S3), + %% timer:sleep(600), + {ok, Packet4} = gen_tcp:recv(Socket, 0, 500), + + % receive general response + GenAckPacket4 = <>, + Size4 = size(GenAckPacket4), + MsgId4 = ?MS_GENERAL_RESPONSE, + MsgSn4 = 2, + Header4 = + <>, + S4 = gen_packet(Header4, GenAckPacket4), + ?assertEqual(S4, Packet4), + + {<<"jt808/000123456789/up">>, Payload} = receive_msg(), + ?assertEqual( + #{ + <<"header">> => #{ + <<"msg_id">> => MsgId3, + <<"encrypt">> => ?NO_ENCRYPT, + <<"phone">> => <<"000123456789">>, + <<"len">> => Size3, + <<"msg_sn">> => MsgSn3 + }, + <<"body">> => #{ + <<"length">> => 4, + <<"data">> => base64:encode(<<"1234">>) + } + }, + emqx_utils_json:decode(Payload, [return_maps]) + ), + + ok = gen_tcp:close(Socket). + +t_case16_dl_0x8301_set_event(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_SET_EVENT}, + <<"body">> => #{ + <<"type">> => 3, + <<"length">> => 2, + <<"events">> => + [ + #{<<"id">> => 56, <<"length">> => 3, <<"content">> => <<"111">>}, + #{<<"id">> => 7, <<"length">> => 5, <<"content">> => <<"nwKdmww">>} + ] + } + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + % + % client get downlink command + % + MsgBody3 = <<3:8, 2:8, 56:8, 3:8, <<"111">>/binary, 7:8, 5:8, <<"nwKdmww">>/binary>>, + MsgId3 = ?MS_SET_EVENT, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + client_send_general_response(Socket, MsgId3, MsgSn3, PhoneBCD), + + % no retrasmition of downlink message + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case17_dl_0x8303_set_menu(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_SET_MENU}, + <<"body">> => #{ + <<"type">> => 3, + <<"length">> => 2, + <<"menus">> => + [ + #{<<"type">> => 56, <<"length">> => 3, <<"info">> => <<"111">>}, + #{<<"type">> => 7, <<"length">> => 5, <<"info">> => <<"nwKdmww">>} + ] + } + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + %% client get downlink command + MsgBody3 = <<3:8, 2:8, 56:8, 3:?WORD, <<"111">>/binary, 7:8, 5:?WORD, <<"nwKdmww">>/binary>>, + MsgId3 = ?MS_SET_MENU, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + client_send_general_response(Socket, MsgId3, MsgSn3, PhoneBCD), + + % no retrasmition of downlink message + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case18_dl_0x8304_info_content(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_INFO_CONTENT}, + <<"body">> => #{<<"type">> => 3, <<"length">> => 2, <<"info">> => <<"NY">>} + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + %% client get downlink command + MsgBody3 = <<3:8, 2:?WORD, <<"NY">>/binary>>, + MsgId3 = ?MS_INFO_CONTENT, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + client_send_general_response(Socket, MsgId3, MsgSn3, PhoneBCD), + + % no retrasmition of downlink message + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case19_dl_0x8400_phone_callback(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_PHONE_CALLBACK}, + <<"body">> => #{<<"type">> => 0, <<"phone">> => <<"15632597856">>} + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + %% client get downlink command + MsgBody3 = <<0:8, <<"15632597856">>/binary>>, + MsgId3 = ?MS_PHONE_CALLBACK, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + client_send_general_response(Socket, MsgId3, MsgSn3, PhoneBCD), + + % no retrasmition of downlink message + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case20_dl_0x8401_set_phone_number(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_SET_PHONE_NUMBER}, + <<"body">> => #{ + <<"type">> => 2, + <<"length">> => 2, + <<"contacts">> => + [ + #{ + <<"type">> => 2, + <<"phone_len">> => 10, + <<"phone">> => <<"13011112222">>, + <<"name_len">> => 3, + <<"name">> => <<"Tom">> + }, + #{ + <<"type">> => 3, + <<"phone_len">> => 11, + <<"phone">> => <<"013011113333">>, + <<"name_len">> => 4, + <<"name">> => <<"Mike">> + } + ] + } + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + % + % client get downlink command + % + MsgBody3 = + <<2:8, 2:8, 2:8, 10:8, <<"13011112222">>/binary, 3:8, <<"Tom">>/binary, 3:8, 11:8, + <<"013011113333">>/binary, 4:8, <<"Mike">>/binary>>, + MsgId3 = ?MS_SET_PHONE_NUMBER, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + client_send_general_response(Socket, MsgId3, MsgSn3, PhoneBCD), + + % no retrasmition of downlink message + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case21_dl_0x8600_set_circle_area(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_SET_CIRCLE_AREA}, + <<"body">> => #{ + <<"type">> => 0, + <<"length">> => 2, + <<"areas">> => + [ + #{ + <<"id">> => 267, + <<"flag">> => 36, + <<"center_latitude">> => 20057, + <<"center_longitude">> => 4529, + <<"radius">> => 279, + <<"start_time">> => <<"170912103253">>, + <<"end_time">> => <<"170913103253">>, + <<"max_speed">> => 120, + <<"overspeed_duration">> => 36 + }, + #{ + <<"id">> => 355, + <<"flag">> => 36, + <<"center_latitude">> => 20057, + <<"center_longitude">> => 4529, + <<"radius">> => 132, + <<"start_time">> => <<"170912103253">>, + <<"end_time">> => <<"170913103253">>, + <<"max_speed">> => 120, + <<"overspeed_duration">> => 36 + } + ] + } + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + %% client get downlink command + MsgBody3 = + <<0:8, 2:8, 267:?DWORD, 36:?WORD, 20057:?DWORD, 4529:?DWORD, 279:?DWORD, + <<16#17, 16#09, 16#12, 16#10, 16#32, 16#53>>/binary, + <<16#17, 16#09, 16#13, 16#10, 16#32, 16#53>>/binary, 120:?WORD, 36:8, 355:?DWORD, + 36:?WORD, 20057:?DWORD, 4529:?DWORD, 132:?DWORD, + <<16#17, 16#09, 16#12, 16#10, 16#32, 16#53>>/binary, + <<16#17, 16#09, 16#13, 16#10, 16#32, 16#53>>/binary, 120:?WORD, 36:8>>, + MsgId3 = ?MS_SET_CIRCLE_AREA, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + client_send_general_response(Socket, MsgId3, MsgSn3, PhoneBCD), + + % no retrasmition of downlink message + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case22_dl_0x8601_del_circle_area(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_DEL_CIRCLE_AREA}, + <<"body">> => #{<<"length">> => 2, <<"ids">> => [3, 78]} + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + % + % client get downlink command + % + MsgBody3 = <<2:8, 3:?DWORD, 78:?DWORD>>, + MsgId3 = ?MS_DEL_CIRCLE_AREA, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + client_send_general_response(Socket, MsgId3, MsgSn3, PhoneBCD), + + % no retrasmition of downlink message + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case23_dl_0x8602_set_rect_area(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_SET_RECT_AREA}, + <<"body">> => #{ + <<"type">> => 0, + <<"length">> => 2, + <<"areas">> => + [ + #{ + <<"id">> => 267, + <<"flag">> => 36, + <<"lt_lat">> => 20057, + <<"lt_lng">> => 4529, + <<"rb_lat">> => 30057, + <<"rb_lng">> => 5529, + <<"start_time">> => <<"170912103253">>, + <<"end_time">> => <<"170913103253">>, + <<"max_speed">> => 120, + <<"overspeed_duration">> => 36 + }, + #{ + <<"id">> => 355, + <<"flag">> => 36, + <<"lt_lat">> => 20057, + <<"lt_lng">> => 4529, + <<"rb_lat">> => 30057, + <<"rb_lng">> => 5529, + <<"start_time">> => <<"170912103253">>, + <<"end_time">> => <<"170913103253">>, + <<"max_speed">> => 120, + <<"overspeed_duration">> => 36 + } + ] + } + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + % + % client get downlink command + % + MsgBody3 = + <<0:8, 2:8, 267:?DWORD, 36:?WORD, 20057:?DWORD, 4529:?DWORD, 30057:?DWORD, 5529:?DWORD, + <<16#17, 16#09, 16#12, 16#10, 16#32, 16#53>>/binary, + <<16#17, 16#09, 16#13, 16#10, 16#32, 16#53>>/binary, 120:?WORD, 36:8, 355:?DWORD, + 36:?WORD, 20057:?DWORD, 4529:?DWORD, 30057:?DWORD, 5529:?DWORD, + <<16#17, 16#09, 16#12, 16#10, 16#32, 16#53>>/binary, + <<16#17, 16#09, 16#13, 16#10, 16#32, 16#53>>/binary, 120:?WORD, 36:8>>, + MsgId3 = ?MS_SET_RECT_AREA, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + client_send_general_response(Socket, MsgId3, MsgSn3, PhoneBCD), + + % no retrasmition of downlink message + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case24_dl_0x8603_del_circle_area(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_DEL_RECT_AREA}, + <<"body">> => #{<<"length">> => 2, <<"ids">> => [3, 78]} + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + % + % client get downlink command + % + MsgBody3 = <<2:8, 3:?DWORD, 78:?DWORD>>, + MsgId3 = ?MS_DEL_RECT_AREA, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + client_send_general_response(Socket, MsgId3, MsgSn3, PhoneBCD), + + % no retrasmition of downlink message + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case25_dl_0x8604_set_poly_area(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_SET_POLY_AREA}, + <<"body">> => #{ + <<"id">> => 267, + <<"flag">> => 36, + <<"start_time">> => <<"170912103253">>, + <<"end_time">> => <<"170913103253">>, + <<"max_speed">> => 120, + <<"overspeed_duration">> => 36, + <<"length">> => 3, + <<"points">> => + [ + #{<<"lat">> => 20057, <<"lng">> => 4529}, + #{<<"lat">> => 21057, <<"lng">> => 14569}, + #{<<"lat">> => 7032, <<"lng">> => 429} + ] + } + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + % + % client get downlink command + % + MsgBody3 = + <<267:?DWORD, 36:?WORD, <<16#17, 16#09, 16#12, 16#10, 16#32, 16#53>>/binary, + <<16#17, 16#09, 16#13, 16#10, 16#32, 16#53>>/binary, 120:?WORD, 36:8, 3:?WORD, + 20057:?DWORD, 4529:?DWORD, 21057:?DWORD, 14569:?DWORD, 7032:?DWORD, 429:?DWORD>>, + MsgId3 = ?MS_SET_POLY_AREA, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + client_send_general_response(Socket, MsgId3, MsgSn3, PhoneBCD), + + % no retrasmition of downlink message + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case26_dl_0x8605_del_poly_area(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_DEL_POLY_AREA}, + <<"body">> => #{<<"length">> => 2, <<"ids">> => [3, 78]} + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + % + % client get downlink command + % + MsgBody3 = <<2:8, 3:?DWORD, 78:?DWORD>>, + MsgId3 = ?MS_DEL_POLY_AREA, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + client_send_general_response(Socket, MsgId3, MsgSn3, PhoneBCD), + + % no retrasmition of downlink message + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case27_dl_0x8606_set_path(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_SET_PATH}, + <<"body">> => #{ + <<"id">> => 267, + <<"flag">> => 36, + <<"start_time">> => <<"170912103253">>, + <<"end_time">> => <<"170913103253">>, + <<"length">> => 2, + <<"points">> => + [ + #{ + <<"point_id">> => 3, + <<"path_id">> => 71, + <<"point_lat">> => 7324, + <<"point_lng">> => 9732, + <<"width">> => 54, + <<"attrib">> => 23, + <<"passed">> => 0, + <<"uncovered">> => 1, + <<"max_speed">> => 132, + <<"overspeed_duration">> => 4 + }, + #{ + <<"point_id">> => 4, + <<"path_id">> => 72, + <<"point_lat">> => 7324, + <<"point_lng">> => 9732, + <<"width">> => 54, + <<"attrib">> => 23, + <<"passed">> => 0, + <<"uncovered">> => 1, + <<"max_speed">> => 169, + <<"overspeed_duration">> => 69 + } + ] + } + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + % + % client get downlink command + % + MsgBody3 = + <<267:?DWORD, 36:?WORD, <<16#17, 16#09, 16#12, 16#10, 16#32, 16#53>>/binary, + <<16#17, 16#09, 16#13, 16#10, 16#32, 16#53>>/binary, 2:?WORD, 3:?DWORD, 71:?DWORD, + 7324:?DWORD, 9732:?DWORD, 54:8, 23:8, 0:?WORD, 1:?WORD, 132:?WORD, 4:8, 4:?DWORD, + 72:?DWORD, 7324:?DWORD, 9732:?DWORD, 54:8, 23:8, 0:?WORD, 1:?WORD, 169:?WORD, 69:8>>, + MsgId3 = ?MS_SET_PATH, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + client_send_general_response(Socket, MsgId3, MsgSn3, PhoneBCD), + + % no retrasmition of downlink message + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case26_dl_0x8607_del_path(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_DEL_PATH}, + <<"body">> => #{<<"length">> => 2, <<"ids">> => [3, 78]} + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + % + % client get downlink command + % + MsgBody3 = <<2:8, 3:?DWORD, 78:?DWORD>>, + MsgId3 = ?MS_DEL_PATH, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + client_send_general_response(Socket, MsgId3, MsgSn3, PhoneBCD), + + % no retrasmition of downlink message + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case27_dl_0x8700_drive_record_capture(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + ok = emqx:subscribe(<<"jt808/000123456789/up">>), + + CaptureCmd = 2, + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_DRIVE_RECORD_CAPTURE}, + <<"body">> => #{ + <<"command">> => CaptureCmd, <<"param">> => base64:encode(<<"000123456789">>) + } + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + % + % client get downlink command + % + MsgBody3 = <<2:8, <<"000123456789">>/binary>>, + MsgId3 = ?MS_DRIVE_RECORD_CAPTURE, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + % client send "drive record report" + UlPacket4 = <>/binary>>, + Size4 = size(UlPacket4), + MsgId4 = ?MC_DRIVE_RECORD_REPORT, + MsgSn4 = 2, + Header4 = + <>, + S4 = gen_packet(Header4, UlPacket4), + ?LOGT("S4 = ~p", [S4]), + + ok = gen_tcp:send(Socket, S4), + timer:sleep(100), + + {<<"jt808/000123456789/up">>, Payload} = receive_msg(), + ?assertEqual( + #{ + <<"header">> => #{ + <<"encrypt">> => 0, + <<"len">> => Size4, + <<"msg_id">> => ?MC_DRIVE_RECORD_REPORT, + <<"msg_sn">> => 2, + <<"phone">> => <<"000123456789">> + }, + <<"body">> => #{ + <<"seq">> => MsgSn3, + <<"command">> => CaptureCmd, + <<"data">> => base64:encode(<<"77777">>) + } + }, + emqx_utils_json:decode(Payload, [return_maps]) + ), + + % no retrasmition of downlink message + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case28_dl_0x8701_drive_record_param_send(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + CaptureCmd = 2, + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_DRIVE_REC_PARAM_SEND}, + <<"body">> => #{ + <<"command">> => CaptureCmd, <<"param">> => base64:encode(<<"000123456789">>) + } + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + % + % client get downlink command + % + MsgBody3 = <>/binary>>, + MsgId3 = ?MS_DRIVE_REC_PARAM_SEND, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + client_send_general_response(Socket, MsgId3, MsgSn3, PhoneBCD), + + % no retrasmition of downlink message + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case29_dl_0x8702_request_driver_id(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + ok = emqx:subscribe(<<"jt808/000123456789/up">>), + + DlCommand = #{<<"header">> => #{<<"msg_id">> => ?MS_REQ_DRIVER_ID}}, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + % + % client get downlink command + % + MsgBody3 = <<>>, + MsgId3 = ?MS_REQ_DRIVER_ID, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + % client send "drive record report" + UlPacket4 = + <<1:8, 16#17, 16#10, 16#12, 16#09, 16#03, 16#52, 0:8, 3:8, <<"Tom">>/binary, + <<"77778888999900001111">>/binary, 6:8, <<"org123">>/binary, 16#20, 16#30, 16#12, + 16#31>>, + Size4 = size(UlPacket4), + MsgId4 = ?MC_DRIVER_ID_REPORT, + MsgSn4 = 2, + Header4 = + <>, + S4 = gen_packet(Header4, UlPacket4), + ?LOGT("S4 = ~p", [S4]), + + ok = gen_tcp:send(Socket, S4), + timer:sleep(100), + + {<<"jt808/000123456789/up">>, Payload} = receive_msg(), + ?assertEqual( + #{ + <<"header">> => #{ + <<"encrypt">> => 0, + <<"len">> => Size4, + <<"msg_id">> => ?MC_DRIVER_ID_REPORT, + <<"msg_sn">> => 2, + <<"phone">> => <<"000123456789">> + }, + <<"body">> => #{ + <<"status">> => 1, + <<"time">> => <<"171012090352">>, + <<"ic_result">> => 0, + <<"driver_name">> => <<"Tom">>, + <<"certificate">> => <<"77778888999900001111">>, + <<"organization">> => <<"org123">>, + <<"cert_expiry">> => <<"20301231">> + } + }, + emqx_utils_json:decode(Payload, [return_maps]) + ), + + % no retrasmition of downlink message + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case30_dl_0x8801_camera_shot(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + ok = emqx:subscribe(<<"jt808/000123456789/up">>), + + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_CAMERA_SHOT}, + <<"body">> => #{ + <<"channel_id">> => 172, + <<"command">> => 5, + <<"period">> => 2, + <<"save">> => 1, + <<"resolution">> => 8, + <<"quality">> => 3, + <<"bright">> => 4, + <<"contrast">> => 5, + <<"saturate">> => 6, + <<"chromaticity">> => 7 + } + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + % + % client get downlink command + % + MsgBody3 = <<172:8, 5:?WORD, 2:?WORD, 1:8, 8:8, 3:8, 4:8, 5:8, 6:8, 7:8>>, + MsgId3 = ?MS_CAMERA_SHOT, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + % client send "camera shot ack" + UlPacket4 = <>, + Size4 = size(UlPacket4), + MsgId4 = ?MC_CAMERA_SHOT_ACK, + MsgSn4 = 2, + Header4 = + <>, + S4 = gen_packet(Header4, UlPacket4), + ?LOGT("S4 = ~p", [S4]), + + ok = gen_tcp:send(Socket, S4), + timer:sleep(100), + + {<<"jt808/000123456789/up">>, Payload} = receive_msg(), + ?assertEqual( + #{ + <<"header">> => #{ + <<"encrypt">> => 0, + <<"len">> => Size4, + <<"msg_id">> => ?MC_CAMERA_SHOT_ACK, + <<"msg_sn">> => 2, + <<"phone">> => <<"000123456789">> + }, + <<"body">> => #{ + <<"seq">> => MsgSn3, + <<"result">> => 0, + <<"length">> => 2, + <<"ids">> => [220, 221] + } + }, + emqx_utils_json:decode(Payload, [return_maps]) + ), + + % No retrasmition of downlink message + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case31_dl_0x8802_mm_data_search(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + ok = emqx:subscribe(<<"jt808/000123456789/up">>), + + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_MM_DATA_SEARCH}, + <<"body">> => #{ + <<"type">> => 0, + <<"channel">> => 17, + <<"event">> => 2, + <<"start_time">> => <<"170923144607">>, + <<"end_time">> => <<"170923145826">> + } + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + % + % client get downlink command + % + MsgBody3 = + <<0:8, 17:8, 2:8, 16#17, 16#09, 16#23, 16#14, 16#46, 16#07, 16#17, 16#09, 16#23, 16#14, + 16#58, 16#26>>, + MsgId3 = ?MS_MM_DATA_SEARCH, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + % client send "mm data search ack" + {LocBin, LocJson} = location_report_28bytes(), + UlPacket4 = + <>, + Size4 = size(UlPacket4), + MsgId4 = ?MC_MM_DATA_SEARCH_ACK, + MsgSn4 = 2, + Header4 = + <>, + S4 = gen_packet(Header4, UlPacket4), + ?LOGT("S4 = ~p", [S4]), + + ok = gen_tcp:send(Socket, S4), + timer:sleep(100), + + {<<"jt808/000123456789/up">>, Payload} = receive_msg(), + ?assertEqual( + #{ + <<"header">> => #{ + <<"encrypt">> => 0, + <<"len">> => Size4, + <<"msg_id">> => ?MC_MM_DATA_SEARCH_ACK, + <<"msg_sn">> => 2, + <<"phone">> => <<"000123456789">> + }, + <<"body">> => #{ + <<"seq">> => MsgSn3, + <<"length">> => 2, + <<"result">> => [ + #{ + <<"id">> => 25, + <<"type">> => 1, + <<"channel">> => 97, + <<"event">> => 1, + <<"location">> => LocJson + }, + #{ + <<"id">> => 26, + <<"type">> => 2, + <<"channel">> => 98, + <<"event">> => 3, + <<"location">> => LocJson + } + ] + } + }, + emqx_utils_json:decode(Payload, [return_maps]) + ), + + %% No retrasmition of downlink message + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case32_dl_0x8803_mm_data_upload(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_MM_DATA_UPLOAD}, + <<"body">> => #{ + <<"type">> => 0, + <<"channel">> => 17, + <<"event">> => 2, + <<"start_time">> => <<"170923144607">>, + <<"end_time">> => <<"170923145826">>, + <<"delete">> => 1 + } + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + % + % client get downlink command + % + MsgBody3 = + <<0:8, 17:8, 2:8, 16#17, 16#09, 16#23, 16#14, 16#46, 16#07, 16#17, 16#09, 16#23, 16#14, + 16#58, 16#26, 1:8>>, + MsgId3 = ?MS_MM_DATA_UPLOAD, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + client_send_general_response(Socket, MsgId3, MsgSn3, PhoneBCD), + + % no retrasmition of downlink message + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case33_dl_0x8804_voice_record(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_VOICE_RECORD}, + <<"body">> => #{ + <<"command">> => 1, + <<"time">> => 2, + <<"save">> => 3, + <<"rate">> => 4 + } + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + % + % client get downlink command + % + MsgBody3 = <<1:8, 2:?WORD, 3:8, 4:8>>, + MsgId3 = ?MS_VOICE_RECORD, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + client_send_general_response(Socket, MsgId3, MsgSn3, PhoneBCD), + + % no retrasmition of downlink message + %% timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +t_case34_dl_0x8805_single_mm_data_ctrl(_Config) -> + {ok, Socket} = gen_tcp:connect({127, 0, 0, 1}, ?PORT, [binary, {active, false}]), + {ok, AuthCode} = client_regi_procedure(Socket), + ok = client_auth_procedure(Socket, AuthCode), + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + ok = emqx:subscribe(<<"jt808/000123456789/up">>), + + DlCommand = #{ + <<"header">> => #{<<"msg_id">> => ?MS_SINGLE_MM_DATA_CTRL}, + <<"body">> => #{ + <<"id">> => 30, + <<"flag">> => 40 + } + }, + emqx:publish(emqx_message:make(<<"jt808/000123456789/dn">>, emqx_utils_json:encode(DlCommand))), + + %% client get downlink command + MsgBody3 = <<30:?DWORD, 40:8>>, + MsgId3 = ?MS_SINGLE_MM_DATA_CTRL, + MsgSn3 = 2, + Size3 = size(MsgBody3), + Header3 = + <>, + S3 = gen_packet(Header3, MsgBody3), + + %% timer:sleep(600), + {ok, Packet3} = gen_tcp:recv(Socket, 0, 500), + ?LOGT(" S3=~p", [binary_to_hex_string(S3)]), + ?LOGT("Packet3=~p", [binary_to_hex_string(Packet3)]), + ?assertEqual(S3, Packet3), + + ?LOGT("client receive command from server ~p", [S3]), + + {LocBin, LocJson} = location_report_28bytes(), + UlPacket4 = + <>, + Size4 = size(UlPacket4), + MsgId4 = ?MC_MM_DATA_SEARCH_ACK, + MsgSn4 = 2, + Header4 = + <>, + S4 = gen_packet(Header4, UlPacket4), + ?LOGT("S4 = ~p", [S4]), + + ok = gen_tcp:send(Socket, S4), + timer:sleep(100), + + {<<"jt808/000123456789/up">>, Payload} = receive_msg(), + ?assertEqual( + #{ + <<"header">> => #{ + <<"encrypt">> => 0, + <<"len">> => Size4, + <<"msg_id">> => ?MC_MM_DATA_SEARCH_ACK, + <<"msg_sn">> => 2, + <<"phone">> => <<"000123456789">> + }, + <<"body">> => #{ + <<"seq">> => MsgSn3, + <<"length">> => 2, + <<"result">> => [ + #{ + <<"id">> => 25, + <<"type">> => 1, + <<"channel">> => 97, + <<"event">> => 1, + <<"location">> => LocJson + }, + #{ + <<"id">> => 26, + <<"type">> => 2, + <<"channel">> => 98, + <<"event">> => 3, + <<"location">> => LocJson + } + ] + } + }, + emqx_utils_json:decode(Payload, [return_maps]) + ), + + % no retrasmition of downlink message + %%timer:sleep(10000), + {error, timeout} = gen_tcp:recv(Socket, 0, 500), + + ok = gen_tcp:close(Socket). + +receive_msg() -> + receive + {deliver, Topic, #message{payload = Payload}} -> + {Topic, Payload} + after 100 -> + {error, timeout} + end. diff --git a/apps/emqx_gateway_jt808/test/emqx_jt808_auth_http_test_server.erl b/apps/emqx_gateway_jt808/test/emqx_jt808_auth_http_test_server.erl new file mode 100644 index 000000000..22dc118b5 --- /dev/null +++ b/apps/emqx_gateway_jt808/test/emqx_jt808_auth_http_test_server.erl @@ -0,0 +1,100 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_jt808_auth_http_test_server). + +-behaviour(supervisor). +-behaviour(cowboy_handler). + +% cowboy_server callbacks +-export([init/2]). + +% supervisor callbacks +-export([init/1]). + +% API +-export([ + start_link/0, start_link/1, start_link/2, + stop/0, + set_handler/1 +]). + +%%------------------------------------------------------------------------------ +%% API +%%------------------------------------------------------------------------------ + +start_link() -> + start_link(8991). + +start_link(Port) -> + start_link(Port, "/[...]"). + +start_link(Port, Path) -> + supervisor:start_link({local, ?MODULE}, ?MODULE, [Port, Path]). + +stop() -> + gen_server:stop(?MODULE). + +set_handler(F) when is_function(F, 2) -> + true = ets:insert(?MODULE, {handler, F}), + ok. + +%%------------------------------------------------------------------------------ +%% supervisor API +%%------------------------------------------------------------------------------ + +init([Port, Path]) -> + Dispatch = cowboy_router:compile( + [ + {'_', [{Path, ?MODULE, []}]} + ] + ), + TransOpts = #{ + socket_opts => [{port, Port}], + connection_type => supervisor + }, + ProtoOpts = #{env => #{dispatch => Dispatch}}, + + Tab = ets:new(?MODULE, [set, named_table, public]), + ets:insert(Tab, {handler, fun default_handler/2}), + + ChildSpec = ranch:child_spec(?MODULE, ranch_tcp, TransOpts, cowboy_clear, ProtoOpts), + {ok, {{one_for_one, 10, 10}, [ChildSpec]}}. + +%%------------------------------------------------------------------------------ +%% cowboy_server API +%%------------------------------------------------------------------------------ + +init(Req, State) -> + [{handler, Handler}] = ets:lookup(?MODULE, handler), + Handler(Req, State). + +%%------------------------------------------------------------------------------ +%% Internal functions +%%------------------------------------------------------------------------------ + +default_handler(Req0 = #{method := <<"POST">>, path := <<"/jt808/registry">>}, State) -> + Req = cowboy_req:reply( + 200, + #{<<"content-type">> => <<"application/json">>}, + emqx_utils_json:encode(#{code => 0, authcode => <<"123456">>}), + Req0 + ), + {ok, Req, State}; +default_handler(Req0 = #{method := <<"POST">>, path := <<"/jt808/auth">>}, State) -> + Req = cowboy_req:reply( + 200, + #{<<"content-type">> => <<"application/json">>}, + emqx_utils_json:encode(#{client_id => <<"abcdef">>}), + Req0 + ), + {ok, Req, State}; +default_handler(Req0, State) -> + Req = cowboy_req:reply( + 400, + #{<<"content-type">> => <<"text/plain">>}, + <<"">>, + Req0 + ), + {ok, Req, State}. diff --git a/apps/emqx_gateway_jt808/test/emqx_jt808_parser_SUITE.erl b/apps/emqx_gateway_jt808/test/emqx_jt808_parser_SUITE.erl new file mode 100644 index 000000000..ea7bc3748 --- /dev/null +++ b/apps/emqx_gateway_jt808/test/emqx_jt808_parser_SUITE.erl @@ -0,0 +1,721 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_jt808_parser_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include("emqx_jt808.hrl"). + +-define(LOGT(Format, Args), ct:print("TEST_SUITE: " ++ Format, Args)). + +-define(FRM_FLAG, 16#7e:8). +-define(RESERVE, 0). +-define(NO_FRAGMENT, 0). +-define(WITH_FRAGMENT, 1). +-define(NO_ENCRYPT, 0). +-define(MSG_SIZE(X), X:10 / big - integer). + +-define(word, 16 / big - integer). +-define(dword, 32 / big - integer). + +-include_lib("eunit/include/eunit.hrl"). + +all() -> + emqx_common_test_helpers:all(?MODULE). + +init_per_suite(Config) -> + Config. + +end_per_suite(Config) -> + Config. + +t_case01_register(_Config) -> + Parser = emqx_jt808_frame:initial_parse_state(#{}), + Manuf = <<"examp">>, + Model = <<"33333333333333333333">>, + DevId = <<"1234567">>, + Color = 3, + Plate = <<"ujvl239">>, + RegisterPacket = + <<58:?word, 59:?word, Manuf/binary, Model/binary, DevId/binary, Color, Plate/binary>>, + MsgId = 16#0100, + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + MsgSn = 78, + Size = size(RegisterPacket), + Header = + <>, + Stream = encode(Header, RegisterPacket), + + {ok, Map, Rest, State} = emqx_jt808_frame:parse(Stream, Parser), + ?assertEqual( + #{ + <<"header">> => #{ + <<"msg_id">> => MsgId, + <<"encrypt">> => ?NO_ENCRYPT, + <<"phone">> => <<"000123456789">>, + <<"len">> => Size, + <<"msg_sn">> => MsgSn + }, + <<"body">> => #{ + <<"province">> => 58, + <<"city">> => 59, + <<"manufacturer">> => Manuf, + <<"model">> => Model, + <<"dev_id">> => DevId, + <<"color">> => Color, + <<"license_number">> => Plate + } + }, + Map + ), + ?assertEqual(<<>>, Rest), + ?assertEqual(#{data => <<>>, phase => searching_head_hex7e}, State), + ok. + +t_case02_register_ack(_Config) -> + % register ack + MsgId = 16#8100, + MsgSn = 35, + Seq = 22, + Result = 1, + Code = <<"abcdef">>, + DownlinkJson = #{ + <<"header">> => #{ + <<"msg_id">> => MsgId, + <<"encrypt">> => ?NO_ENCRYPT, + <<"phone">> => <<"000123456789">>, + <<"msg_sn">> => MsgSn + }, + <<"body">> => #{<<"seq">> => Seq, <<"result">> => Result, <<"auth_code">> => Code} + }, + Stream = emqx_jt808_frame:serialize_pkt(DownlinkJson, #{}), + + RegisterAckPacket = <>, + + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + + Size = size(RegisterAckPacket), + Header = + <>, + StreamByHand = encode(Header, RegisterAckPacket), + + ?assertEqual(StreamByHand, Stream), + ok. + +t_case04_MC_LOCATION_REPORT(_Config) -> + Parser = emqx_jt808_frame:initial_parse_state(#{}), + Data = + <<126, 2, 0, 0, 60, 1, 136, 118, 99, 137, 114, 0, 229, 0, 0, 0, 0, 0, 4, 0, 0, 1, 49, 122, + 103, 6, 147, 104, 81, 0, 14, 0, 0, 0, 39, 23, 16, 25, 25, 53, 56, 1, 4, 0, 0, 63, 178, + 3, 2, 0, 0, 37, 4, 0, 0, 0, 0, 42, 2, 0, 0, 43, 4, 0, 0, 0, 0, 48, 1, 31, 49, 1, 0, 171, + 126>>, + {ok, Map, Rest, State} = emqx_jt808_frame:parse(Data, Parser), + ?assertEqual( + #{ + <<"header">> => + #{ + <<"encrypt">> => 0, + <<"len">> => 60, + <<"msg_id">> => 512, + <<"msg_sn">> => 229, + <<"phone">> => <<"018876638972">> + }, + <<"body">> => + #{ + <<"alarm">> => 0, + <<"altitude">> => 14, + <<"direction">> => 39, + <<"extra">> => + #{ + <<"analog">> => #{<<"ad0">> => 0, <<"ad1">> => 0}, + <<"gnss_sat_num">> => 0, + <<"io_status">> => #{<<"deep_sleep">> => 0, <<"sleep">> => 0}, + <<"mileage">> => 16306, + <<"rssi">> => 31, + <<"signal">> => + #{ + <<"abs">> => 0, + <<"air_conditioner">> => 0, + <<"brake">> => 0, + <<"cluth">> => 0, + <<"fog">> => 0, + <<"heater">> => 0, + <<"high_beam">> => 0, + <<"horn">> => 0, + <<"left_turn">> => 0, + <<"low_beam">> => 0, + <<"neutral">> => 0, + <<"retarder">> => 0, + <<"reverse">> => 0, + <<"right_turn">> => 0, + <<"side_marker">> => 0 + }, + <<"speed">> => 0 + }, + <<"latitude">> => 20019815, + <<"longitude">> => 110323793, + <<"speed">> => 0, + <<"status">> => 262144, + <<"time">> => <<"171019193538">> + } + }, + Map + ), + ?assertEqual(<<>>, Rest), + ?assertEqual(#{data => <<>>, phase => searching_head_hex7e}, State), + ok. + +t_case05_MC_BULK_LOCATION_REPORT(_Config) -> + Parser = emqx_jt808_frame:initial_parse_state(#{}), + Data = + <<126, 7, 4, 1, 57, 1, 136, 118, 99, 137, 114, 0, 231, 0, 5, 1, 0, 60, 0, 0, 0, 0, 0, 4, 0, + 3, 1, 49, 115, 43, 6, 145, 211, 81, 0, 31, 0, 166, 0, 171, 23, 8, 49, 16, 73, 51, 1, 4, + 0, 0, 60, 85, 3, 2, 0, 0, 37, 4, 0, 0, 0, 0, 42, 2, 0, 0, 43, 4, 0, 0, 0, 0, 48, 1, 0, + 49, 1, 12, 0, 60, 0, 0, 0, 0, 0, 4, 0, 3, 1, 49, 114, 74, 6, 145, 212, 2, 0, 29, 0, 222, + 0, 125, 2, 23, 8, 49, 16, 73, 56, 1, 4, 0, 0, 60, 85, 3, 2, 0, 0, 37, 4, 0, 0, 0, 0, 42, + 2, 0, 0, 43, 4, 0, 0, 0, 0, 48, 1, 0, 49, 1, 12, 0, 60, 0, 0, 0, 0, 0, 4, 0, 3, 1, 49, + 109, 222, 6, 145, 225, 80, 0, 37, 1, 169, 0, 109, 23, 8, 49, 16, 80, 17, 1, 4, 0, 0, 60, + 89, 3, 2, 0, 0, 37, 4, 0, 0, 0, 0, 42, 2, 0, 0, 43, 4, 0, 0, 0, 0, 48, 1, 0, 49, 1, 12, + 0, 60, 0, 0, 0, 0, 0, 4, 0, 3, 1, 49, 90, 235, 6, 146, 0, 24, 0, 43, 2, 136, 0, 114, 23, + 8, 49, 16, 81, 17, 1, 4, 0, 0, 60, 99, 3, 2, 0, 0, 37, 4, 0, 0, 0, 0, 42, 2, 0, 0, 43, + 4, 0, 0, 0, 0, 48, 1, 0, 49, 1, 12, 0, 60, 0, 0, 0, 0, 0, 4, 0, 3, 1, 49, 91, 120, 6, + 146, 43, 21, 0, 43, 2, 247, 0, 83, 23, 8, 49, 16, 82, 20, 1, 4, 0, 0, 60, 111, 3, 2, 0, + 0, 37, 4, 0, 0, 0, 0, 42, 2, 0, 0, 43, 4, 0, 0, 0, 0, 48, 1, 0, 49, 1, 12, 132, 126>>, + {ok, Map, Rest, State} = emqx_jt808_frame:parse(Data, Parser), + ?assertEqual( + #{ + <<"body">> => + #{ + <<"length">> => 5, + <<"location">> => + [ + #{ + <<"alarm">> => 0, + <<"altitude">> => 31, + <<"direction">> => 171, + <<"extra">> => + #{ + <<"analog">> => + #{ + <<"ad0">> => 0, + <<"ad1">> => 0 + }, + <<"gnss_sat_num">> => 12, + <<"io_status">> => + #{ + <<"deep_sleep">> => 0, + <<"sleep">> => 0 + }, + <<"mileage">> => 15445, + <<"rssi">> => 0, + <<"signal">> => + #{ + <<"abs">> => 0, + <<"air_conditioner">> => + 0, + <<"brake">> => 0, + <<"cluth">> => 0, + <<"fog">> => 0, + <<"heater">> => 0, + <<"high_beam">> => 0, + <<"horn">> => 0, + <<"left_turn">> => 0, + <<"low_beam">> => 0, + <<"neutral">> => 0, + <<"retarder">> => 0, + <<"reverse">> => 0, + <<"right_turn">> => 0, + <<"side_marker">> => 0 + }, + <<"speed">> => 0 + }, + <<"latitude">> => 20017963, + <<"longitude">> => 110220113, + <<"speed">> => 166, + <<"status">> => 262147, + <<"time">> => <<"170831104933">> + }, + #{ + <<"alarm">> => 0, + <<"altitude">> => 29, + <<"direction">> => 126, + <<"extra">> => + #{ + <<"analog">> => + #{ + <<"ad0">> => 0, + <<"ad1">> => 0 + }, + <<"gnss_sat_num">> => 12, + <<"io_status">> => + #{ + <<"deep_sleep">> => 0, + <<"sleep">> => 0 + }, + <<"mileage">> => 15445, + <<"rssi">> => 0, + <<"signal">> => + #{ + <<"abs">> => 0, + <<"air_conditioner">> => + 0, + <<"brake">> => 0, + <<"cluth">> => 0, + <<"fog">> => 0, + <<"heater">> => 0, + <<"high_beam">> => 0, + <<"horn">> => 0, + <<"left_turn">> => 0, + <<"low_beam">> => 0, + <<"neutral">> => 0, + <<"retarder">> => 0, + <<"reverse">> => 0, + <<"right_turn">> => 0, + <<"side_marker">> => 0 + }, + <<"speed">> => 0 + }, + <<"latitude">> => 20017738, + <<"longitude">> => 110220290, + <<"speed">> => 222, + <<"status">> => 262147, + <<"time">> => <<"170831104938">> + }, + #{ + <<"alarm">> => 0, + <<"altitude">> => 37, + <<"direction">> => 109, + <<"extra">> => + #{ + <<"analog">> => + #{ + <<"ad0">> => 0, + <<"ad1">> => 0 + }, + <<"gnss_sat_num">> => 12, + <<"io_status">> => + #{ + <<"deep_sleep">> => 0, + <<"sleep">> => 0 + }, + <<"mileage">> => 15449, + <<"rssi">> => 0, + <<"signal">> => + #{ + <<"abs">> => 0, + <<"air_conditioner">> => + 0, + <<"brake">> => 0, + <<"cluth">> => 0, + <<"fog">> => 0, + <<"heater">> => 0, + <<"high_beam">> => 0, + <<"horn">> => 0, + <<"left_turn">> => 0, + <<"low_beam">> => 0, + <<"neutral">> => 0, + <<"retarder">> => 0, + <<"reverse">> => 0, + <<"right_turn">> => 0, + <<"side_marker">> => 0 + }, + <<"speed">> => 0 + }, + <<"latitude">> => 20016606, + <<"longitude">> => 110223696, + <<"speed">> => 425, + <<"status">> => 262147, + <<"time">> => <<"170831105011">> + }, + #{ + <<"alarm">> => 0, + <<"altitude">> => 43, + <<"direction">> => 114, + <<"extra">> => + #{ + <<"analog">> => + #{ + <<"ad0">> => 0, + <<"ad1">> => 0 + }, + <<"gnss_sat_num">> => 12, + <<"io_status">> => + #{ + <<"deep_sleep">> => 0, + <<"sleep">> => 0 + }, + <<"mileage">> => 15459, + <<"rssi">> => 0, + <<"signal">> => + #{ + <<"abs">> => 0, + <<"air_conditioner">> => + 0, + <<"brake">> => 0, + <<"cluth">> => 0, + <<"fog">> => 0, + <<"heater">> => 0, + <<"high_beam">> => 0, + <<"horn">> => 0, + <<"left_turn">> => 0, + <<"low_beam">> => 0, + <<"neutral">> => 0, + <<"retarder">> => 0, + <<"reverse">> => 0, + <<"right_turn">> => 0, + <<"side_marker">> => 0 + }, + <<"speed">> => 0 + }, + <<"latitude">> => 20011755, + <<"longitude">> => 110231576, + <<"speed">> => 648, + <<"status">> => 262147, + <<"time">> => <<"170831105111">> + }, + #{ + <<"alarm">> => 0, + <<"altitude">> => 43, + <<"direction">> => 83, + <<"extra">> => + #{ + <<"analog">> => + #{ + <<"ad0">> => 0, + <<"ad1">> => 0 + }, + <<"gnss_sat_num">> => 12, + <<"io_status">> => + #{ + <<"deep_sleep">> => 0, + <<"sleep">> => 0 + }, + <<"mileage">> => 15471, + <<"rssi">> => 0, + <<"signal">> => + #{ + <<"abs">> => 0, + <<"air_conditioner">> => + 0, + <<"brake">> => 0, + <<"cluth">> => 0, + <<"fog">> => 0, + <<"heater">> => 0, + <<"high_beam">> => 0, + <<"horn">> => 0, + <<"left_turn">> => 0, + <<"low_beam">> => 0, + <<"neutral">> => 0, + <<"retarder">> => 0, + <<"reverse">> => 0, + <<"right_turn">> => 0, + <<"side_marker">> => 0 + }, + <<"speed">> => 0 + }, + <<"latitude">> => 20011896, + <<"longitude">> => 110242581, + <<"speed">> => 759, + <<"status">> => 262147, + <<"time">> => <<"170831105214">> + } + ], + <<"type">> => 1 + }, + <<"header">> => + #{ + <<"encrypt">> => 0, + <<"len">> => 313, + <<"msg_id">> => 1796, + <<"msg_sn">> => 231, + <<"phone">> => <<"018876638972">> + } + }, + Map + ), + ?assertEqual(<<>>, Rest), + ?assertEqual(#{data => <<>>, phase => searching_head_hex7e}, State), + ok. + +t_case10_segmented_packet(_Config) -> + Parser = emqx_jt808_frame:initial_parse_state(#{}), + Manuf = <<"examp">>, + Model = <<"33333333333333333333">>, + DevId = <<"1234567">>, + Color = 3, + Plate = <<"ujvl239">>, + RegisterPacket = + <<58:?word, 59:?word, Manuf/binary, Model/binary, DevId/binary, Color, Plate/binary>>, + MsgId = 16#0100, + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + MsgSn = 78, + Size = size(RegisterPacket), + Header = + <>, + Stream = encode(Header, RegisterPacket), + + <> = Stream, + {more, Parser2} = emqx_jt808_frame:parse(Part1, Parser), + ?assertMatch(#{phase := escaping_hex7d}, Parser2), + {ok, Map, Rest, State} = emqx_jt808_frame:parse(Part2, Parser2), + ?assertEqual( + #{ + <<"header">> => #{ + <<"msg_id">> => MsgId, + <<"encrypt">> => ?NO_ENCRYPT, + <<"phone">> => <<"000123456789">>, + <<"len">> => Size, + <<"msg_sn">> => MsgSn + }, + <<"body">> => #{ + <<"province">> => 58, + <<"city">> => 59, + <<"manufacturer">> => Manuf, + <<"model">> => Model, + <<"dev_id">> => DevId, + <<"color">> => Color, + <<"license_number">> => Plate + } + }, + Map + ), + ?assertEqual(<<>>, Rest), + ?assertEqual(#{data => <<>>, phase => searching_head_hex7e}, State), + ok. + +t_case11_prefix_register(_Config) -> + Parser = emqx_jt808_frame:initial_parse_state(#{}), + Manuf = <<"examp">>, + Model = <<"33333333333333333333">>, + DevId = <<"1234567">>, + Color = 3, + Plate = <<"ujvl239">>, + RegisterPacket = + <<58:?word, 59:?word, Manuf/binary, Model/binary, DevId/binary, Color, Plate/binary>>, + MsgId = 16#0100, + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + MsgSn = 78, + Size = size(RegisterPacket), + Header = + <>, + Stream = encode(Header, RegisterPacket), + MessBinary = <<0, 1, 2, 3, Stream/binary>>, + ?LOGT("MessBinary=~p", [binary_to_hex_string(MessBinary)]), + + {ok, Map, Rest, State} = emqx_jt808_frame:parse(MessBinary, Parser), + ?assertEqual( + #{ + <<"header">> => #{ + <<"msg_id">> => MsgId, + <<"encrypt">> => ?NO_ENCRYPT, + <<"phone">> => <<"000123456789">>, + <<"len">> => Size, + <<"msg_sn">> => MsgSn + }, + <<"body">> => #{ + <<"province">> => 58, + <<"city">> => 59, + <<"manufacturer">> => Manuf, + <<"model">> => Model, + <<"dev_id">> => DevId, + <<"color">> => Color, + <<"license_number">> => Plate + } + }, + Map + ), + ?assertEqual(<<>>, Rest), + ?assertEqual(#{data => <<>>, phase => searching_head_hex7e}, State), + ok. + +t_case12_0x7e_in_message(_Config) -> + Parser = emqx_jt808_frame:initial_parse_state(#{}), + Manuf = <<"examp">>, + Model = <<"33333333333333333333">>, + DevId = <<"1234567">>, + Color = 3, + Plate = <<"ujvl239">>, + % pay attention to this + AlarmInt = 16#7e, + AlarmDigit = 16#7d, + RegisterPacket = + <>, + MsgId = 16#0100, + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + MsgSn = 78, + Size = size(RegisterPacket), + Header = + <>, + Stream = encode(Header, RegisterPacket), + + {ok, Map, Rest, State} = emqx_jt808_frame:parse(Stream, Parser), + ?assertEqual( + #{ + <<"header">> => #{ + <<"msg_id">> => MsgId, + <<"encrypt">> => ?NO_ENCRYPT, + <<"phone">> => <<"000123456789">>, + <<"len">> => Size, + <<"msg_sn">> => MsgSn + }, + <<"body">> => #{ + <<"province">> => AlarmInt, + <<"city">> => AlarmDigit, + <<"manufacturer">> => Manuf, + <<"model">> => Model, + <<"dev_id">> => DevId, + <<"color">> => Color, + <<"license_number">> => Plate + } + }, + Map + ), + ?assertEqual(<<>>, Rest), + ?assertEqual(#{data => <<>>, phase => searching_head_hex7e}, State), + ok. + +t_case13_partial_0x7d_in_message(_Config) -> + Parser = emqx_jt808_frame:initial_parse_state(#{}), + Manuf = <<"examp">>, + Model = <<"33333333333333333333">>, + DevId = <<"1234567">>, + Color = 3, + Plate = <<"ujvl239">>, + % pay attention to this + AlarmInt = 16#7e, + AlarmDigit = 16#7d, + RegisterPacket = + <>, + MsgId = 16#0100, + PhoneBCD = <<16#00, 16#01, 16#23, 16#45, 16#67, 16#89>>, + MsgSn = 78, + Size = size(RegisterPacket), + Header = + <>, + Stream = encode(Header, RegisterPacket), + + <> = Stream, + <<_:14/binary, 16#7d:8>> = Part1, + + {more, Parser2} = emqx_jt808_frame:parse(Part1, Parser), + ?assertMatch(#{phase := escaping_hex7d}, Parser2), + {ok, Map, Rest, State} = emqx_jt808_frame:parse(Part2, Parser2), + ?assertEqual( + #{ + <<"header">> => #{ + <<"msg_id">> => MsgId, + <<"encrypt">> => ?NO_ENCRYPT, + <<"phone">> => <<"000123456789">>, + <<"len">> => Size, + <<"msg_sn">> => MsgSn + }, + <<"body">> => #{ + <<"province">> => AlarmInt, + <<"city">> => AlarmDigit, + <<"manufacturer">> => Manuf, + <<"model">> => Model, + <<"dev_id">> => DevId, + <<"color">> => Color, + <<"license_number">> => Plate + } + }, + Map + ), + ?assertEqual(<<>>, Rest), + ?assertEqual(#{data => <<>>, phase => searching_head_hex7e}, State), + ok. + +t_case14_custome_location_data(_) -> + Bin = + <<126, 2, 0, 0, 64, 1, 65, 72, 7, 53, 80, 3, 106, 0, 0, 0, 0, 0, 0, 0, 1, 1, 195, 232, 22, + 6, 89, 10, 16, 0, 0, 0, 0, 0, 0, 33, 6, 34, 9, 21, 69, 1, 4, 0, 0, 0, 0, 48, 1, 23, 49, + 1, 10, 235, 22, 0, 12, 0, 178, 137, 134, 4, 66, 25, 25, 144, 147, 71, 153, 0, 6, 0, 137, + 255, 255, 255, 255, 36, 126>>, + Parser = emqx_jt808_frame:initial_parse_state(#{}), + {ok, Packet, Rest, State} = emqx_jt808_frame:parse(Bin, Parser), + ?assertEqual(<<>>, Rest), + ?assertEqual(#{data => <<>>, phase => searching_head_hex7e}, State), + _ = emqx_utils_json:encode(Packet). + +t_case14_reserved_location_data(_) -> + Bin = + <<126, 2, 0, 0, 49, 1, 145, 114, 3, 130, 104, 2, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 211, 181, + 215, 6, 51, 228, 71, 0, 4, 0, 0, 0, 138, 34, 4, 25, 22, 5, 70, 1, 4, 0, 0, 0, 0, 5, 3, + 0, 0, 0, 48, 1, 31, 49, 1, 15, 130, 2, 0, 125, 2, 23, 126>>, + Parser = emqx_jt808_frame:initial_parse_state(#{}), + {ok, Packet, Rest, State} = emqx_jt808_frame:parse(Bin, Parser), + ?assertEqual(<<>>, Rest), + ?assertEqual(#{data => <<>>, phase => searching_head_hex7e}, State), + _ = emqx_utils_json:encode(Packet). + +t_case15_custome_client_query_ack(_) -> + Bin = + <<126, 1, 4, 2, 17, 78, 244, 128, 18, 137, 25, 0, 43, 0, 42, 52, 0, 0, 0, 1, 4, 0, 0, 0, + 180, 0, 0, 0, 8, 4, 0, 0, 1, 44, 0, 0, 0, 16, 5, 99, 109, 110, 101, 116, 0, 0, 0, 17, 0, + 0, 0, 0, 18, 0, 0, 0, 0, 19, 12, 52, 55, 46, 57, 57, 46, 57, 56, 46, 50, 53, 52, 0, 0, + 0, 23, 7, 48, 46, 48, 46, 48, 46, 48, 0, 0, 0, 24, 4, 0, 0, 31, 249, 0, 0, 0, 32, 4, 0, + 0, 0, 0, 0, 0, 0, 39, 4, 0, 0, 0, 30, 0, 0, 0, 41, 4, 0, 0, 0, 30, 0, 0, 0, 48, 4, 0, 0, + 0, 20, 0, 0, 0, 64, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, 0, 68, 1, 0, 0, 0, 0, 69, 4, 0, 0, 0, + 0, 0, 0, 0, 72, 1, 0, 0, 0, 0, 73, 1, 0, 0, 0, 0, 85, 4, 0, 0, 0, 120, 0, 0, 0, 86, 4, + 0, 0, 0, 20, 0, 0, 0, 93, 2, 0, 30, 0, 0, 0, 128, 4, 0, 0, 136, 203, 0, 0, 0, 129, 2, 0, + 0, 0, 0, 0, 130, 2, 0, 0, 0, 0, 0, 131, 0, 0, 0, 0, 132, 1, 0, 0, 0, 240, 1, 2, 16, 0, + 0, 0, 240, 2, 2, 3, 57, 0, 0, 240, 3, 1, 41, 0, 0, 240, 4, 2, 0, 116, 0, 0, 240, 5, 4, + 0, 53, 111, 127, 0, 0, 240, 6, 4, 0, 3, 51, 18, 0, 0, 240, 7, 1, 1, 0, 0, 240, 8, 2, 0, + 30, 0, 0, 240, 10, 2, 0, 30, 0, 0, 240, 11, 6, 0, 0, 0, 0, 0, 0, 0, 0, 240, 12, 1, 1, 0, + 0, 240, 13, 18, 117, 112, 103, 114, 97, 100, 101, 46, 99, 112, 115, 100, 110, 97, 46, + 99, 111, 109, 0, 0, 240, 14, 4, 0, 0, 123, 40, 0, 0, 240, 15, 1, 1, 0, 0, 240, 16, 3, 0, + 16, 3, 0, 0, 240, 17, 2, 0, 32, 0, 0, 240, 18, 3, 0, 22, 40, 0, 0, 240, 19, 4, 0, 17, + 25, 32, 0, 0, 240, 20, 3, 55, 0, 10, 0, 0, 240, 21, 1, 50, 0, 0, 240, 22, 2, 3, 25, 0, + 0, 240, 23, 2, 30, 50, 0, 0, 240, 24, 21, 48, 44, 48, 44, 53, 56, 46, 50, 49, 53, 46, + 53, 48, 46, 53, 48, 44, 54, 48, 48, 56, 0, 0, 241, 1, 1, 160, 0, 0, 241, 2, 1, 130, 0, + 0, 255, 148, 93, 14, 22, 1, 56, 57, 56, 54, 48, 52, 57, 53, 49, 48, 50, 49, 55, 48, 51, + 48, 52, 53, 53, 56, 17, 2, 52, 54, 48, 48, 56, 49, 53, 56, 51, 52, 48, 52, 53, 53, 56, + 4, 3, 0, 1, 3, 5, 0, 3, 6, 0, 3, 7, 0, 5, 8, 3, 0, 1, 5, 8, 14, 0, 0, 5, 8, 15, 0, 0, 5, + 8, 41, 0, 0, 5, 8, 48, 0, 0, 5, 8, 42, 0, 0, 5, 8, 43, 0, 0, 5, 8, 4, 0, 0, 138, 126>>, + Parser = emqx_jt808_frame:initial_parse_state(#{}), + {ok, Packet, Rest, State} = emqx_jt808_frame:parse(Bin, Parser), + ?assertEqual(<<>>, Rest), + ?assertEqual(#{data => <<>>, phase => searching_head_hex7e}, State), + _ = emqx_utils_json:encode(Packet). + +encode(Header, Body) -> + S1 = <
>, + Crc = make_crc(S1, undefined), + S2 = do_escape(<>), + Stream = <<16#7e:8, S2/binary, 16#7e:8>>, + %?LOGT("encode a packet=~p", [binary_to_hex_string(Stream)]), + Stream. + +make_crc(<<>>, Xor) -> + ?LOGT("crc is ~p", [Xor]), + Xor; +make_crc(<>, undefined) -> + make_crc(Rest, C); +make_crc(<>, Xor) -> + make_crc(Rest, C bxor Xor). + +do_escape(Binary) -> + do_escape(Binary, <<>>). + +do_escape(<<>>, Acc) -> + Acc; +do_escape(<<16#7e, Rest/binary>>, Acc) -> + do_escape(Rest, <>); +do_escape(<<16#7d, Rest/binary>>, Acc) -> + do_escape(Rest, <>); +do_escape(<>, Acc) -> + do_escape(Rest, <>). + +binary_to_hex_string(Data) -> + lists:flatten([io_lib:format("~2.16.0B ", [X]) || <> <= Data]).