1 |
|
%%------------------------------------------------------------------------- |
2 |
|
%% Copyright (c) 2021-2022 EMQ Technologies Co., Ltd. All Rights Reserved. |
3 |
|
%% |
4 |
|
%% Licensed under the Apache License, Version 2.0 (the "License"); |
5 |
|
%% you may not use this file except in compliance with the License. |
6 |
|
%% You may obtain a copy of the License at |
7 |
|
%% |
8 |
|
%% http://www.apache.org/licenses/LICENSE-2.0 |
9 |
|
%% |
10 |
|
%% Unless required by applicable law or agreed to in writing, software |
11 |
|
%% distributed under the License is distributed on an "AS IS" BASIS, |
12 |
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 |
|
%% See the License for the specific language governing permissions and |
14 |
|
%% limitations under the License. |
15 |
|
%%------------------------------------------------------------------------- |
16 |
|
|
17 |
|
%% @doc low-level API working with socket, can be used |
18 |
|
%% standalone or with <i>emqttsn_send</i> |
19 |
|
%% |
20 |
|
%% @see emqttsn_send |
21 |
|
-module(emqttsn_udp). |
22 |
|
|
23 |
|
%% @headerfile "emqttsn.hrl" |
24 |
|
|
25 |
|
-include("logger.hrl"). |
26 |
|
-include("emqttsn.hrl"). |
27 |
|
|
28 |
|
-include_lib("eunit/include/eunit.hrl"). |
29 |
|
|
30 |
|
-export([init_port/1, init_port/0, connect/3, send/2, send_anywhere/4, broadcast/3, |
31 |
|
recv/1, recv/2]). |
32 |
|
|
33 |
|
%% @doc Start a udp socket at given port |
34 |
|
%% |
35 |
|
%% @param Port on which to open the socket |
36 |
|
%% |
37 |
|
%% @returns ok | {error, system_limit | inet:posix()} |
38 |
|
%% @end |
39 |
|
-spec init_port(inet:port_number()) -> {ok, inet:socket()} | {error, term()}. |
40 |
|
init_port(LocalPort) -> |
41 |
49 |
case gen_udp:open(LocalPort, [binary, {active, true}, {broadcast, true}]) of |
42 |
|
{ok, Socket} -> |
43 |
45 |
{ok, Socket}; |
44 |
|
{error, Reason} when LocalPort =:= 0 -> |
45 |
3 |
?LOGP(error, "Open random port failed, reason : ~p", [Reason]), |
46 |
3 |
{error, Reason}; |
47 |
|
{error, _Reason} when LocalPort =/= 0 -> |
48 |
1 |
?LOGP(warning, "Open port ~p failed, turn to random port", [LocalPort]), |
49 |
1 |
init_port() |
50 |
|
end. |
51 |
|
|
52 |
|
%% @doc Start a udp socket at auto pick port |
53 |
|
%% |
54 |
|
%% @equiv init_port(0) |
55 |
|
%% |
56 |
|
%% @returns ok | {error, system_limit | inet:posix()} |
57 |
|
%% @end |
58 |
|
-spec init_port() -> {ok, inet:socket()} | {error, term()}. |
59 |
|
init_port() -> |
60 |
11 |
init_port(0). |
61 |
|
|
62 |
|
%% @doc Connect to gateway |
63 |
|
%% |
64 |
|
%% Caution: not really <i>connect</i>, |
65 |
|
%% only store infomation for udp! |
66 |
|
%% |
67 |
|
%% @param Socket the socket object |
68 |
|
%% @param Host host of gateway to connect |
69 |
|
%% @param Port port of gateway to connect |
70 |
|
%% |
71 |
|
%% @end |
72 |
|
-spec connect(inet:socket(), host(), inet:port_number()) -> ok | {error, term()}. |
73 |
|
connect(Socket, Host, Port) -> |
74 |
31 |
case gen_udp:connect(Socket, Host, Port) of |
75 |
|
ok -> |
76 |
30 |
ok; |
77 |
|
{error, Reason} -> |
78 |
1 |
{error, Reason} |
79 |
|
end. |
80 |
|
|
81 |
|
%% @doc send binary packet to connected gateway |
82 |
|
%% |
83 |
|
%% Caution: need to connect first before call it! |
84 |
|
%% |
85 |
|
%% @param Socket the socket object |
86 |
|
%% @param Bin Binary packet send to gateway |
87 |
|
%% |
88 |
|
%% @returns ok | {error, not_owner | inet:posix()} |
89 |
|
%% @end |
90 |
|
-spec send(inet:socket(), bitstring()) -> ok | {error, term()}. |
91 |
|
send(Socket, Bin) -> |
92 |
125 |
gen_udp:send(Socket, Bin). |
93 |
|
|
94 |
|
%% @doc send binary packet to any host |
95 |
|
%% |
96 |
|
%% @param Socket the socket object |
97 |
|
%% @param Bin Binary packet send to gateway |
98 |
|
%% @param Host host of target to send packet |
99 |
|
%% @param RemotePort port of target to send packet |
100 |
|
%% |
101 |
|
%% @returns ok | {error, not_owner | inet:posix()} |
102 |
|
%% @end |
103 |
|
-spec send_anywhere(inet:socket(), bitstring(), host(), inet:port_number()) -> |
104 |
|
ok | {error, term()}. |
105 |
|
send_anywhere(Socket, Bin, Host, RemotePort) -> |
106 |
1 |
gen_udp:send(Socket, {Host, RemotePort}, Bin). |
107 |
|
|
108 |
|
%% @doc broadcast binary packet to local network |
109 |
|
%% |
110 |
|
%% @param Socket the socket object |
111 |
|
%% @param Bin Binary packet send to gateway |
112 |
|
%% @param RemotePort port of target to send packet |
113 |
|
%% |
114 |
|
%% @returns ok | {error, not_owner | inet:posix()} |
115 |
|
%% @end |
116 |
|
-spec broadcast(inet:socket(), bitstring(), inet:port_number()) -> ok | {error, term()}. |
117 |
|
broadcast(Socket, Bin, RemotePort) -> |
118 |
56 |
case gen_udp:send(Socket, '255.255.255.255', RemotePort, Bin) of |
119 |
|
{error, Reason} -> |
120 |
1 |
?LOGP(warning, "boardcast failed at send data:~p", [Reason]), |
121 |
1 |
{error, Reason}; |
122 |
|
ok -> |
123 |
55 |
ok |
124 |
|
end. |
125 |
|
|
126 |
|
%% @doc recv and parse incoming packet |
127 |
|
%% |
128 |
|
%% @param Socket the socket object |
129 |
|
%% @equiv recv(Socket, 2000) |
130 |
|
%% |
131 |
|
%% @returns ok | {error, not_owner | inet:posix()} |
132 |
|
%% @end |
133 |
|
-spec recv(inet:socket()) -> {ok, mqttsn_packet()} | udp_receive_timeout. |
134 |
|
recv(Socket) -> |
135 |
6 |
recv(Socket, 2000). |
136 |
|
|
137 |
|
%% @doc recv and parse incoming packet at given timeout |
138 |
|
%% |
139 |
|
%% @param Socket the socket object |
140 |
|
%% @param Timeout the timeout of recv packet |
141 |
|
%% |
142 |
|
%% @returns {ok, mqttsn_packet()} | {error, udp_receive_timeout} |
143 |
|
%% @end |
144 |
|
-spec recv(inet:socket(), pos_integer()) -> {ok, mqttsn_packet()} | {error, term()}. |
145 |
|
recv(Socket, Timeout) -> |
146 |
10 |
receive |
147 |
|
{udp, Socket, _, _, Bin} -> |
148 |
6 |
?LOGP(debug, "receive_response Bin=~p~n", [Bin]), |
149 |
6 |
case emqttsn_frame:parse(Bin) of |
150 |
|
{ok, Packet} -> |
151 |
4 |
{ok, Packet}; |
152 |
|
{error, Reason} -> |
153 |
2 |
?LOGP(warning, "parse packet ~p failed: ~p", [Bin, Reason]), |
154 |
2 |
{error, Reason} |
155 |
|
end; |
156 |
|
Other -> |
157 |
2 |
?LOGP(warning, "receive_response() Other message: ~p", [{unexpected_data, Other}]), |
158 |
2 |
{error, unexpected_data} |
159 |
|
after Timeout -> |
160 |
2 |
{error, udp_receive_timeout} |
161 |
|
end. |